diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml deleted file mode 100644 index 992a3d1f..00000000 --- a/.github/workflows/backend-ci.yml +++ /dev/null @@ -1,88 +0,0 @@ -name: Backend CI/CD -on: - pull_request: - paths: - - "apps/backend/**" - - "packages/**" - - "pnpm-lock.yaml" - - "turbo.json" - - "package.json" - -jobs: - test: - runs-on: ubuntu-latest - - services: - db: - image: bitnami/postgresql:latest - ports: - - 5432:5432 - env: - POSTGRESQL_USERNAME: postgres - POSTGRESQL_PASSWORD: postgres - POSTGRESQL_DATABASE: plotwist_test - POSTGRESQL_REPLICATION_USE_PASSFILE: "no" - - redis: - image: bitnami/redis:latest - ports: - - 6379:6379 - env: - REDIS_PASSWORD: redis - - localstack: - image: localstack/localstack - ports: - - 4566:4566 - - 4576:4576 - env: - SERVICES: sqs - DEFAULT_REGION: us-east-1 - AWS_ACCESS_KEY_ID: localkey - AWS_SECRET_ACCESS_KEY: localsecret - - steps: - - uses: actions/checkout@v4 - - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 10.0.0 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: "23" - cache: "pnpm" - - - name: Cache pnpm dependencies - uses: actions/cache@v4 - with: - path: ~/.pnpm-store - key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm- - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Run pretest (migrations) - working-directory: apps/backend - run: pnpm run pretest - env: - DATABASE_URL: postgresql://postgres:postgres@localhost:5432/plotwist_test - REDIS_URL: redis://default:redis@127.0.0.1:6379 - - - name: Run tests - working-directory: apps/backend - run: pnpm run test - env: - DATABASE_URL: postgresql://postgres:postgres@localhost:5432/plotwist_test - REDIS_URL: redis://default:redis@127.0.0.1:6379 - - - name: Type check - working-directory: apps/backend - run: pnpm run typecheck - - - name: Build - run: pnpm turbo build --filter=plotwist-api diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..bb13c536 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,42 @@ +name: Backend CI/CD +on: + pull_request: + paths: + - "apps/backend/**" + - "packages/**" + - "pnpm-lock.yaml" + - "turbo.json" + - "package.json" + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10.0.0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "23" + cache: "pnpm" + + - name: Cache pnpm dependencies + uses: actions/cache@v4 + with: + path: ~/.pnpm-store + key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm- + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Run tests + working-directory: apps/backend + run: pnpm run test diff --git a/apps/backend/.env.example b/apps/backend/.env.example index cdd7c202..2cc6ee31 100644 --- a/apps/backend/.env.example +++ b/apps/backend/.env.example @@ -39,14 +39,10 @@ IMPORT_SERIES_QUEUE=import-series-queue MAL_CLIENT_ID=banana # Feature Flags -ENABLE_CERTS=false ENABLE_IMPORT_MOVIES=true ENABLE_IMPORT_SERIES=true ENABLE_SQS=true ENABLE_CRON_JOBS=false -# Certs -ENABLE_CERTS=false - # OpenAI OPENAI_API_KEY= diff --git a/apps/backend/.env.test b/apps/backend/.env.test deleted file mode 100644 index bb4be00d..00000000 --- a/apps/backend/.env.test +++ /dev/null @@ -1,50 +0,0 @@ -APP_ENV=test - -# General -PORT=3333 -BASE_URL=http://localhost:3333 -JWT_SECRET=secret - -# Database -DATABASE_URL=postgresql://postgres:postgres@localhost:5432/plotwist_test - -# TMDB -TMDB_ACCESS_TOKEN=eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiI5MGYyYjQyNWU1ZmYxYjgwMWVkOWRjY2Y0YmFmYWRkZSIsIm5iZiI6MTczMTMyODU3Mi40NzUzNjgsInN1YiI6IjYwZjIxYTVkN2Q1ZGI1MDAyZmM5MTNiMyIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.TvR91AMkjferB6UMid5JWKk3bZRS92TuEYrRvsplfXM - -# Redis -REDIS_URL=redis://default:redis@127.0.0.1:6379 - -CLIENT_URL=http://localhost:3000 - -# Cloudflare -CLOUDFLARE_BUCKET=CLOUDFLARE_BUCKET -CLOUDFLARE_ACCESS_KEY_ID=CLOUDFLARE_ACCESS_KEY_ID -CLOUDFLARE_SECRET_ACCESS_KEY=CLOUDFLARE_SECRET_ACCESS_KEY -CLOUDFLARE_ACCOUNT_ID=CLOUDFLARE_ACCOUNT_ID -CLOUDFLARE_PUBLIC_URL=https://google.com - -# SQS -AWS_REGION=sa-east-1 -LOCALSTACK_ENDPOINT=http://localhost:4566 -AWS_ACCESS_KEY_ID=banana -AWS_SECRET_ACCESS_KEY=banana - -# Queues -IMPORT_MOVIES_QUEUE=import-movies-queue-test -IMPORT_SERIES_QUEUE=import-series-queue-test - -# MAL API Client ID -MAL_CLIENT_ID=banana - -# Feature Flags -ENABLE_CERTS=false -ENABLE_SQS=true -ENABLE_IMPORT_MOVIES=true -ENABLE_IMPORT_SERIES=true -ENABLE_CRON_JOBS=false - -# Certs -ENABLE_CERTS=false - -# OpenAI -OPENAI_API_KEY=open_api_key diff --git a/apps/backend/deploy/index.ts b/apps/backend/deploy/index.ts index 6f5a4bef..f17d55ec 100644 --- a/apps/backend/deploy/index.ts +++ b/apps/backend/deploy/index.ts @@ -195,10 +195,6 @@ const app = new awsx.classic.ecs.FargateService('aws-host-app', { name: 'IMPORT_SERIES_QUEUE', valueFrom: '/plotwist/prod/IMPORT_SERIES_QUEUE', }, - { - name: 'ENABLE_CERTS', - valueFrom: '/plotwist/prod/ENABLE_CERTS', - }, { name: 'ENABLE_SQS', valueFrom: '/plotwist/prod/ENABLE_SQS', diff --git a/apps/backend/docker-compose.yml b/apps/backend/docker-compose.yml index e6e5f626..956253be 100644 --- a/apps/backend/docker-compose.yml +++ b/apps/backend/docker-compose.yml @@ -24,11 +24,10 @@ services: environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres - POSTGRES_DB: POSTGRES_DB + POSTGRES_DB: plotwist_db POSTGRESQL_REPLICATION_USE_PASSFILE: "no" volumes: - postgres_data:/var/lib/postgresql/data - - ./priv/init-db.sh:/docker-entrypoint-initdb.d/init-db.sh ports: - 5432:5432 networks: diff --git a/apps/backend/drizzle.config.ts b/apps/backend/drizzle.config.ts index 7100bc3f..07d7976e 100644 --- a/apps/backend/drizzle.config.ts +++ b/apps/backend/drizzle.config.ts @@ -3,7 +3,7 @@ import { config } from '@/config' export default { schema: 'src/db/schema/index.ts', - out: 'priv/migrations', + out: 'src/db/migrations', dialect: 'postgresql', dbCredentials: { url: config.db.DATABASE_URL }, schemaFilter: ['auth', 'public'], diff --git a/apps/backend/package.json b/apps/backend/package.json index 6f187d05..40e66a9c 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -12,10 +12,9 @@ "db:migrate": "dotenv -e .env -- drizzle-kit migrate", "db:migrate:test": "dotenv -e .env.test -- drizzle-kit migrate", "db:studio": "dotenv -e .env -- drizzle-kit studio", - "pretest": "pnpm run db:migrate:test", - "test": "dotenv -e .env.test -- vitest", - "test:ui": "dotenv -e .env.test -- vitest --ui", - "test:coverage": "dotenv -e .env.test -- vitest --coverage", + "test": "dotenv -e .env.test -- vitest --config vitest.config.ts", + "test:ui": "dotenv -e .env.test -- vitest --config vitest.config.ts --ui", + "test:coverage": "dotenv -e .env.test -- vitest --config vitest.config.ts --coverage", "format": "biome check src --write", "email:dev": "email dev --dir src/emails", "typecheck": "tsc --noEmit", @@ -41,8 +40,6 @@ "@types/node-cron": "^3.0.11", "@types/react": "^19.2.7", "@types/react-dom": "^19.2.3", - "react": "^19.2.3", - "react-dom": "^19.2.3", "@vitest/coverage-v8": "^4.0.16", "adm-zip": "^0.5.16", "axios": "^1.13.2", @@ -64,6 +61,8 @@ "pino-pretty": "^13.1.3", "postgres": "^3.4.7", "puppeteer": "^24.34.0", + "react": "^19.2.3", + "react-dom": "^19.2.3", "resend": "^6.6.0", "stripe": "^20.1.0", "typeid-js": "^1.2.0" @@ -81,6 +80,7 @@ "ioredis-mock": "^8.13.1", "localstack": "^1.0.0", "react-email": "^5.1.1", + "testcontainers": "^11.11.0", "tsup": "^8.5.1", "tsx": "^4.21.0", "typescript": "^5.9.3", diff --git a/apps/backend/priv/init-db.sh b/apps/backend/priv/init-db.sh deleted file mode 100755 index 04194235..00000000 --- a/apps/backend/priv/init-db.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -set -e - -# Run the standard entrypoint setup from Postgres -export PGPASSWORD="$POSTGRES_PASSWORD" - -psql -v ON_ERROR_STOP=1 --username "$POSTGRESQL_USERNAME" <<-EOSQL - CREATE DATABASE plotwist_dev; - CREATE DATABASE plotwist_test; -EOSQL \ No newline at end of file diff --git a/apps/backend/src/config.ts b/apps/backend/src/config.ts index 48b09cb7..a96dfcf0 100644 --- a/apps/backend/src/config.ts +++ b/apps/backend/src/config.ts @@ -85,7 +85,6 @@ function loadSQSQueues() { function loadFeatureFlags() { const schema = z.object({ - ENABLE_CERTS: z.string(), ENABLE_IMPORT_MOVIES: z.string(), ENABLE_IMPORT_SERIES: z.string(), ENABLE_SQS: z.string(), diff --git a/apps/backend/priv/migrations/20241029111922_create_followers_and_profile_table.sql b/apps/backend/src/db/migrations/20241029111922_create_followers_and_profile_table.sql similarity index 100% rename from apps/backend/priv/migrations/20241029111922_create_followers_and_profile_table.sql rename to apps/backend/src/db/migrations/20241029111922_create_followers_and_profile_table.sql diff --git a/apps/backend/priv/migrations/20241029112257_create_likes_and_list_table.sql b/apps/backend/src/db/migrations/20241029112257_create_likes_and_list_table.sql similarity index 100% rename from apps/backend/priv/migrations/20241029112257_create_likes_and_list_table.sql rename to apps/backend/src/db/migrations/20241029112257_create_likes_and_list_table.sql diff --git a/apps/backend/priv/migrations/20241030135921_create_users_table.sql b/apps/backend/src/db/migrations/20241030135921_create_users_table.sql similarity index 100% rename from apps/backend/priv/migrations/20241030135921_create_users_table.sql rename to apps/backend/src/db/migrations/20241030135921_create_users_table.sql diff --git a/apps/backend/priv/migrations/20241031134108_alter_profiles_add_username_and_email_idx.sql b/apps/backend/src/db/migrations/20241031134108_alter_profiles_add_username_and_email_idx.sql similarity index 100% rename from apps/backend/priv/migrations/20241031134108_alter_profiles_add_username_and_email_idx.sql rename to apps/backend/src/db/migrations/20241031134108_alter_profiles_add_username_and_email_idx.sql diff --git a/apps/backend/priv/migrations/20241101123452_replace_profiles_relations_by_users.sql b/apps/backend/src/db/migrations/20241101123452_replace_profiles_relations_by_users.sql similarity index 100% rename from apps/backend/priv/migrations/20241101123452_replace_profiles_relations_by_users.sql rename to apps/backend/src/db/migrations/20241101123452_replace_profiles_relations_by_users.sql diff --git a/apps/backend/priv/migrations/20241101123556_drop_profiles_table.sql b/apps/backend/src/db/migrations/20241101123556_drop_profiles_table.sql similarity index 100% rename from apps/backend/priv/migrations/20241101123556_drop_profiles_table.sql rename to apps/backend/src/db/migrations/20241101123556_drop_profiles_table.sql diff --git a/apps/backend/priv/migrations/20241101123905_optional_image_path.sql b/apps/backend/src/db/migrations/20241101123905_optional_image_path.sql similarity index 100% rename from apps/backend/priv/migrations/20241101123905_optional_image_path.sql rename to apps/backend/src/db/migrations/20241101123905_optional_image_path.sql diff --git a/apps/backend/priv/migrations/20241101231512_rename_lists_table.sql b/apps/backend/src/db/migrations/20241101231512_rename_lists_table.sql similarity index 100% rename from apps/backend/priv/migrations/20241101231512_rename_lists_table.sql rename to apps/backend/src/db/migrations/20241101231512_rename_lists_table.sql diff --git a/apps/backend/priv/migrations/20241104150046_alter_reviews_set_non_null_fields.sql b/apps/backend/src/db/migrations/20241104150046_alter_reviews_set_non_null_fields.sql similarity index 100% rename from apps/backend/priv/migrations/20241104150046_alter_reviews_set_non_null_fields.sql rename to apps/backend/src/db/migrations/20241104150046_alter_reviews_set_non_null_fields.sql diff --git a/apps/backend/priv/migrations/20241105145640_list_item_id_not_null.sql b/apps/backend/src/db/migrations/20241105145640_list_item_id_not_null.sql similarity index 100% rename from apps/backend/priv/migrations/20241105145640_list_item_id_not_null.sql rename to apps/backend/src/db/migrations/20241105145640_list_item_id_not_null.sql diff --git a/apps/backend/priv/migrations/20241105155820_list_item_title_not_null.sql b/apps/backend/src/db/migrations/20241105155820_list_item_title_not_null.sql similarity index 100% rename from apps/backend/priv/migrations/20241105155820_list_item_title_not_null.sql rename to apps/backend/src/db/migrations/20241105155820_list_item_title_not_null.sql diff --git a/apps/backend/priv/migrations/20241105163538_list_item_backdrop.sql b/apps/backend/src/db/migrations/20241105163538_list_item_backdrop.sql similarity index 100% rename from apps/backend/priv/migrations/20241105163538_list_item_backdrop.sql rename to apps/backend/src/db/migrations/20241105163538_list_item_backdrop.sql diff --git a/apps/backend/priv/migrations/20241106005606_list_items_position.sql b/apps/backend/src/db/migrations/20241106005606_list_items_position.sql similarity index 100% rename from apps/backend/priv/migrations/20241106005606_list_items_position.sql rename to apps/backend/src/db/migrations/20241106005606_list_items_position.sql diff --git a/apps/backend/priv/migrations/20241106005740_list_item_position_not_null.sql b/apps/backend/src/db/migrations/20241106005740_list_item_position_not_null.sql similarity index 100% rename from apps/backend/priv/migrations/20241106005740_list_item_position_not_null.sql rename to apps/backend/src/db/migrations/20241106005740_list_item_position_not_null.sql diff --git a/apps/backend/priv/migrations/20241106011700_rename_list_banner.sql b/apps/backend/src/db/migrations/20241106011700_rename_list_banner.sql similarity index 100% rename from apps/backend/priv/migrations/20241106011700_rename_list_banner.sql rename to apps/backend/src/db/migrations/20241106011700_rename_list_banner.sql diff --git a/apps/backend/priv/migrations/20241106024020_list_item_position_nullable.sql b/apps/backend/src/db/migrations/20241106024020_list_item_position_nullable.sql similarity index 100% rename from apps/backend/priv/migrations/20241106024020_list_item_position_nullable.sql rename to apps/backend/src/db/migrations/20241106024020_list_item_position_nullable.sql diff --git a/apps/backend/priv/migrations/20241106125638_user_default_subscription_type.sql b/apps/backend/src/db/migrations/20241106125638_user_default_subscription_type.sql similarity index 100% rename from apps/backend/priv/migrations/20241106125638_user_default_subscription_type.sql rename to apps/backend/src/db/migrations/20241106125638_user_default_subscription_type.sql diff --git a/apps/backend/priv/migrations/20241110183817_watchlist_item_media_type.sql b/apps/backend/src/db/migrations/20241110183817_watchlist_item_media_type.sql similarity index 100% rename from apps/backend/priv/migrations/20241110183817_watchlist_item_media_type.sql rename to apps/backend/src/db/migrations/20241110183817_watchlist_item_media_type.sql diff --git a/apps/backend/priv/migrations/20241110230213_update_followers_coloumn_createdAt_to_created_at.sql b/apps/backend/src/db/migrations/20241110230213_update_followers_coloumn_createdAt_to_created_at.sql similarity index 100% rename from apps/backend/priv/migrations/20241110230213_update_followers_coloumn_createdAt_to_created_at.sql rename to apps/backend/src/db/migrations/20241110230213_update_followers_coloumn_createdAt_to_created_at.sql diff --git a/apps/backend/priv/migrations/20241111214519_add_legacy_flag_to_users.sql b/apps/backend/src/db/migrations/20241111214519_add_legacy_flag_to_users.sql similarity index 100% rename from apps/backend/priv/migrations/20241111214519_add_legacy_flag_to_users.sql rename to apps/backend/src/db/migrations/20241111214519_add_legacy_flag_to_users.sql diff --git a/apps/backend/priv/migrations/20241112015047_user_item.sql b/apps/backend/src/db/migrations/20241112015047_user_item.sql similarity index 100% rename from apps/backend/priv/migrations/20241112015047_user_item.sql rename to apps/backend/src/db/migrations/20241112015047_user_item.sql diff --git a/apps/backend/priv/migrations/20241112145547_remove_review_tmdb_fields.sql b/apps/backend/src/db/migrations/20241112145547_remove_review_tmdb_fields.sql similarity index 100% rename from apps/backend/priv/migrations/20241112145547_remove_review_tmdb_fields.sql rename to apps/backend/src/db/migrations/20241112145547_remove_review_tmdb_fields.sql diff --git a/apps/backend/priv/migrations/20241112151112_tmdb_id_media_type_not_null.sql b/apps/backend/src/db/migrations/20241112151112_tmdb_id_media_type_not_null.sql similarity index 100% rename from apps/backend/priv/migrations/20241112151112_tmdb_id_media_type_not_null.sql rename to apps/backend/src/db/migrations/20241112151112_tmdb_id_media_type_not_null.sql diff --git a/apps/backend/priv/migrations/20241112164529_has_spoilers_not_null.sql b/apps/backend/src/db/migrations/20241112164529_has_spoilers_not_null.sql similarity index 100% rename from apps/backend/priv/migrations/20241112164529_has_spoilers_not_null.sql rename to apps/backend/src/db/migrations/20241112164529_has_spoilers_not_null.sql diff --git a/apps/backend/priv/migrations/20241113152134_magic_tokens_table.sql b/apps/backend/src/db/migrations/20241113152134_magic_tokens_table.sql similarity index 100% rename from apps/backend/priv/migrations/20241113152134_magic_tokens_table.sql rename to apps/backend/src/db/migrations/20241113152134_magic_tokens_table.sql diff --git a/apps/backend/priv/migrations/20241118132144_socials_links_and_user_bio.sql b/apps/backend/src/db/migrations/20241118132144_socials_links_and_user_bio.sql similarity index 100% rename from apps/backend/priv/migrations/20241118132144_socials_links_and_user_bio.sql rename to apps/backend/src/db/migrations/20241118132144_socials_links_and_user_bio.sql diff --git a/apps/backend/priv/migrations/20241120122558_user_episode.sql b/apps/backend/src/db/migrations/20241120122558_user_episode.sql similarity index 100% rename from apps/backend/priv/migrations/20241120122558_user_episode.sql rename to apps/backend/src/db/migrations/20241120122558_user_episode.sql diff --git a/apps/backend/priv/migrations/20241120175244_unique_contraint_user_item.sql b/apps/backend/src/db/migrations/20241120175244_unique_contraint_user_item.sql similarity index 100% rename from apps/backend/priv/migrations/20241120175244_unique_contraint_user_item.sql rename to apps/backend/src/db/migrations/20241120175244_unique_contraint_user_item.sql diff --git a/apps/backend/priv/migrations/20241124025218_likes_table.sql b/apps/backend/src/db/migrations/20241124025218_likes_table.sql similarity index 100% rename from apps/backend/priv/migrations/20241124025218_likes_table.sql rename to apps/backend/src/db/migrations/20241124025218_likes_table.sql diff --git a/apps/backend/priv/migrations/20241124032024_setup_new_likes_table.sql b/apps/backend/src/db/migrations/20241124032024_setup_new_likes_table.sql similarity index 100% rename from apps/backend/priv/migrations/20241124032024_setup_new_likes_table.sql rename to apps/backend/src/db/migrations/20241124032024_setup_new_likes_table.sql diff --git a/apps/backend/priv/migrations/20241124121131_likes_table_entity_type.sql b/apps/backend/src/db/migrations/20241124121131_likes_table_entity_type.sql similarity index 100% rename from apps/backend/priv/migrations/20241124121131_likes_table_entity_type.sql rename to apps/backend/src/db/migrations/20241124121131_likes_table_entity_type.sql diff --git a/apps/backend/priv/migrations/20241125154201_likes_idx_entity_id.sql b/apps/backend/src/db/migrations/20241125154201_likes_idx_entity_id.sql similarity index 100% rename from apps/backend/priv/migrations/20241125154201_likes_idx_entity_id.sql rename to apps/backend/src/db/migrations/20241125154201_likes_idx_entity_id.sql diff --git a/apps/backend/priv/migrations/20241129163505_runtime_in_user_episodes.sql b/apps/backend/src/db/migrations/20241129163505_runtime_in_user_episodes.sql similarity index 100% rename from apps/backend/priv/migrations/20241129163505_runtime_in_user_episodes.sql rename to apps/backend/src/db/migrations/20241129163505_runtime_in_user_episodes.sql diff --git a/apps/backend/priv/migrations/20241204040215_real_rating.sql b/apps/backend/src/db/migrations/20241204040215_real_rating.sql similarity index 100% rename from apps/backend/priv/migrations/20241204040215_real_rating.sql rename to apps/backend/src/db/migrations/20241204040215_real_rating.sql diff --git a/apps/backend/priv/migrations/20241204191003_add_case_insensitive_indexes_to_users.sql b/apps/backend/src/db/migrations/20241204191003_add_case_insensitive_indexes_to_users.sql similarity index 100% rename from apps/backend/priv/migrations/20241204191003_add_case_insensitive_indexes_to_users.sql rename to apps/backend/src/db/migrations/20241204191003_add_case_insensitive_indexes_to_users.sql diff --git a/apps/backend/priv/migrations/20241205205803_rename_users_images_columns.sql b/apps/backend/src/db/migrations/20241205205803_rename_users_images_columns.sql similarity index 100% rename from apps/backend/priv/migrations/20241205205803_rename_users_images_columns.sql rename to apps/backend/src/db/migrations/20241205205803_rename_users_images_columns.sql diff --git a/apps/backend/priv/migrations/20241205205931_rename_list_banner_column.sql b/apps/backend/src/db/migrations/20241205205931_rename_list_banner_column.sql similarity index 100% rename from apps/backend/priv/migrations/20241205205931_rename_list_banner_column.sql rename to apps/backend/src/db/migrations/20241205205931_rename_list_banner_column.sql diff --git a/apps/backend/priv/migrations/20241212155603_not_null_list_item_position.sql b/apps/backend/src/db/migrations/20241212155603_not_null_list_item_position.sql similarity index 100% rename from apps/backend/priv/migrations/20241212155603_not_null_list_item_position.sql rename to apps/backend/src/db/migrations/20241212155603_not_null_list_item_position.sql diff --git a/apps/backend/priv/migrations/20241212172142_nullable_position.sql b/apps/backend/src/db/migrations/20241212172142_nullable_position.sql similarity index 100% rename from apps/backend/priv/migrations/20241212172142_nullable_position.sql rename to apps/backend/src/db/migrations/20241212172142_nullable_position.sql diff --git a/apps/backend/priv/migrations/20241212195126_initial_user_activities.sql b/apps/backend/src/db/migrations/20241212195126_initial_user_activities.sql similarity index 100% rename from apps/backend/priv/migrations/20241212195126_initial_user_activities.sql rename to apps/backend/src/db/migrations/20241212195126_initial_user_activities.sql diff --git a/apps/backend/priv/migrations/20241213211907_jsonb_metadata.sql b/apps/backend/src/db/migrations/20241213211907_jsonb_metadata.sql similarity index 100% rename from apps/backend/priv/migrations/20241213211907_jsonb_metadata.sql rename to apps/backend/src/db/migrations/20241213211907_jsonb_metadata.sql diff --git a/apps/backend/priv/migrations/20241213220438_create_account_activity_type.sql b/apps/backend/src/db/migrations/20241213220438_create_account_activity_type.sql similarity index 100% rename from apps/backend/priv/migrations/20241213220438_create_account_activity_type.sql rename to apps/backend/src/db/migrations/20241213220438_create_account_activity_type.sql diff --git a/apps/backend/priv/migrations/20241216025259_update_at_user_item.sql b/apps/backend/src/db/migrations/20241216025259_update_at_user_item.sql similarity index 100% rename from apps/backend/priv/migrations/20241216025259_update_at_user_item.sql rename to apps/backend/src/db/migrations/20241216025259_update_at_user_item.sql diff --git a/apps/backend/priv/migrations/20241216234805_dropped_status.sql b/apps/backend/src/db/migrations/20241216234805_dropped_status.sql similarity index 100% rename from apps/backend/priv/migrations/20241216234805_dropped_status.sql rename to apps/backend/src/db/migrations/20241216234805_dropped_status.sql diff --git a/apps/backend/priv/migrations/20241218101630_create_import_tables.sql b/apps/backend/src/db/migrations/20241218101630_create_import_tables.sql similarity index 100% rename from apps/backend/priv/migrations/20241218101630_create_import_tables.sql rename to apps/backend/src/db/migrations/20241218101630_create_import_tables.sql diff --git a/apps/backend/priv/migrations/20241223124713_alter-import-table-add-metadata-field.sql b/apps/backend/src/db/migrations/20241223124713_alter-import-table-add-metadata-field.sql similarity index 100% rename from apps/backend/priv/migrations/20241223124713_alter-import-table-add-metadata-field.sql rename to apps/backend/src/db/migrations/20241223124713_alter-import-table-add-metadata-field.sql diff --git a/apps/backend/priv/migrations/20250114014739_user_preferences_fk_key.sql b/apps/backend/src/db/migrations/20250114014739_user_preferences_fk_key.sql similarity index 100% rename from apps/backend/priv/migrations/20250114014739_user_preferences_fk_key.sql rename to apps/backend/src/db/migrations/20250114014739_user_preferences_fk_key.sql diff --git a/apps/backend/priv/migrations/20250114015649_unique_user_preferences.sql b/apps/backend/src/db/migrations/20250114015649_unique_user_preferences.sql similarity index 100% rename from apps/backend/priv/migrations/20250114015649_unique_user_preferences.sql rename to apps/backend/src/db/migrations/20250114015649_unique_user_preferences.sql diff --git a/apps/backend/priv/migrations/20250114134605_watch_providers_ids.sql b/apps/backend/src/db/migrations/20250114134605_watch_providers_ids.sql similarity index 100% rename from apps/backend/priv/migrations/20250114134605_watch_providers_ids.sql rename to apps/backend/src/db/migrations/20250114134605_watch_providers_ids.sql diff --git a/apps/backend/priv/migrations/20250118150128_set_metadata_fields_as_required.sql b/apps/backend/src/db/migrations/20250118150128_set_metadata_fields_as_required.sql similarity index 100% rename from apps/backend/priv/migrations/20250118150128_set_metadata_fields_as_required.sql rename to apps/backend/src/db/migrations/20250118150128_set_metadata_fields_as_required.sql diff --git a/apps/backend/priv/migrations/20250122175816_season_and_episodes_reviews.sql b/apps/backend/src/db/migrations/20250122175816_season_and_episodes_reviews.sql similarity index 100% rename from apps/backend/priv/migrations/20250122175816_season_and_episodes_reviews.sql rename to apps/backend/src/db/migrations/20250122175816_season_and_episodes_reviews.sql diff --git a/apps/backend/priv/migrations/20250205150159_remove_position.sql b/apps/backend/src/db/migrations/20250205150159_remove_position.sql similarity index 100% rename from apps/backend/priv/migrations/20250205150159_remove_position.sql rename to apps/backend/src/db/migrations/20250205150159_remove_position.sql diff --git a/apps/backend/priv/migrations/20250329175015_alter_users_remove_subscription_type.sql b/apps/backend/src/db/migrations/20250329175015_alter_users_remove_subscription_type.sql similarity index 100% rename from apps/backend/priv/migrations/20250329175015_alter_users_remove_subscription_type.sql rename to apps/backend/src/db/migrations/20250329175015_alter_users_remove_subscription_type.sql diff --git a/apps/backend/priv/migrations/20250331232502_alter_subscription_table.sql b/apps/backend/src/db/migrations/20250331232502_alter_subscription_table.sql similarity index 100% rename from apps/backend/priv/migrations/20250331232502_alter_subscription_table.sql rename to apps/backend/src/db/migrations/20250331232502_alter_subscription_table.sql diff --git a/apps/backend/priv/migrations/20250331233924_alter_subscription_table.sql b/apps/backend/src/db/migrations/20250331233924_alter_subscription_table.sql similarity index 100% rename from apps/backend/priv/migrations/20250331233924_alter_subscription_table.sql rename to apps/backend/src/db/migrations/20250331233924_alter_subscription_table.sql diff --git a/apps/backend/priv/migrations/meta/20241029111922_snapshot.json b/apps/backend/src/db/migrations/meta/20241029111922_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241029111922_snapshot.json rename to apps/backend/src/db/migrations/meta/20241029111922_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241029112257_snapshot.json b/apps/backend/src/db/migrations/meta/20241029112257_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241029112257_snapshot.json rename to apps/backend/src/db/migrations/meta/20241029112257_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241030135921_snapshot.json b/apps/backend/src/db/migrations/meta/20241030135921_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241030135921_snapshot.json rename to apps/backend/src/db/migrations/meta/20241030135921_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241031134108_snapshot.json b/apps/backend/src/db/migrations/meta/20241031134108_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241031134108_snapshot.json rename to apps/backend/src/db/migrations/meta/20241031134108_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241101123452_snapshot.json b/apps/backend/src/db/migrations/meta/20241101123452_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241101123452_snapshot.json rename to apps/backend/src/db/migrations/meta/20241101123452_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241101123556_snapshot.json b/apps/backend/src/db/migrations/meta/20241101123556_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241101123556_snapshot.json rename to apps/backend/src/db/migrations/meta/20241101123556_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241101123905_snapshot.json b/apps/backend/src/db/migrations/meta/20241101123905_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241101123905_snapshot.json rename to apps/backend/src/db/migrations/meta/20241101123905_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241101231512_snapshot.json b/apps/backend/src/db/migrations/meta/20241101231512_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241101231512_snapshot.json rename to apps/backend/src/db/migrations/meta/20241101231512_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241104150046_snapshot.json b/apps/backend/src/db/migrations/meta/20241104150046_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241104150046_snapshot.json rename to apps/backend/src/db/migrations/meta/20241104150046_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241105145640_snapshot.json b/apps/backend/src/db/migrations/meta/20241105145640_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241105145640_snapshot.json rename to apps/backend/src/db/migrations/meta/20241105145640_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241105155820_snapshot.json b/apps/backend/src/db/migrations/meta/20241105155820_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241105155820_snapshot.json rename to apps/backend/src/db/migrations/meta/20241105155820_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241105163538_snapshot.json b/apps/backend/src/db/migrations/meta/20241105163538_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241105163538_snapshot.json rename to apps/backend/src/db/migrations/meta/20241105163538_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241106005606_snapshot.json b/apps/backend/src/db/migrations/meta/20241106005606_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241106005606_snapshot.json rename to apps/backend/src/db/migrations/meta/20241106005606_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241106005740_snapshot.json b/apps/backend/src/db/migrations/meta/20241106005740_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241106005740_snapshot.json rename to apps/backend/src/db/migrations/meta/20241106005740_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241106011700_snapshot.json b/apps/backend/src/db/migrations/meta/20241106011700_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241106011700_snapshot.json rename to apps/backend/src/db/migrations/meta/20241106011700_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241106024020_snapshot.json b/apps/backend/src/db/migrations/meta/20241106024020_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241106024020_snapshot.json rename to apps/backend/src/db/migrations/meta/20241106024020_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241106125638_snapshot.json b/apps/backend/src/db/migrations/meta/20241106125638_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241106125638_snapshot.json rename to apps/backend/src/db/migrations/meta/20241106125638_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241110183817_snapshot.json b/apps/backend/src/db/migrations/meta/20241110183817_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241110183817_snapshot.json rename to apps/backend/src/db/migrations/meta/20241110183817_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241110230213_snapshot.json b/apps/backend/src/db/migrations/meta/20241110230213_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241110230213_snapshot.json rename to apps/backend/src/db/migrations/meta/20241110230213_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241111214519_snapshot.json b/apps/backend/src/db/migrations/meta/20241111214519_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241111214519_snapshot.json rename to apps/backend/src/db/migrations/meta/20241111214519_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241112015047_snapshot.json b/apps/backend/src/db/migrations/meta/20241112015047_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241112015047_snapshot.json rename to apps/backend/src/db/migrations/meta/20241112015047_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241112145547_snapshot.json b/apps/backend/src/db/migrations/meta/20241112145547_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241112145547_snapshot.json rename to apps/backend/src/db/migrations/meta/20241112145547_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241112151112_snapshot.json b/apps/backend/src/db/migrations/meta/20241112151112_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241112151112_snapshot.json rename to apps/backend/src/db/migrations/meta/20241112151112_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241112164529_snapshot.json b/apps/backend/src/db/migrations/meta/20241112164529_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241112164529_snapshot.json rename to apps/backend/src/db/migrations/meta/20241112164529_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241113152134_snapshot.json b/apps/backend/src/db/migrations/meta/20241113152134_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241113152134_snapshot.json rename to apps/backend/src/db/migrations/meta/20241113152134_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241118132144_snapshot.json b/apps/backend/src/db/migrations/meta/20241118132144_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241118132144_snapshot.json rename to apps/backend/src/db/migrations/meta/20241118132144_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241120122558_snapshot.json b/apps/backend/src/db/migrations/meta/20241120122558_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241120122558_snapshot.json rename to apps/backend/src/db/migrations/meta/20241120122558_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241120175244_snapshot.json b/apps/backend/src/db/migrations/meta/20241120175244_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241120175244_snapshot.json rename to apps/backend/src/db/migrations/meta/20241120175244_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241124025218_snapshot.json b/apps/backend/src/db/migrations/meta/20241124025218_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241124025218_snapshot.json rename to apps/backend/src/db/migrations/meta/20241124025218_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241124032024_snapshot.json b/apps/backend/src/db/migrations/meta/20241124032024_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241124032024_snapshot.json rename to apps/backend/src/db/migrations/meta/20241124032024_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241124121131_snapshot.json b/apps/backend/src/db/migrations/meta/20241124121131_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241124121131_snapshot.json rename to apps/backend/src/db/migrations/meta/20241124121131_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241125154201_snapshot.json b/apps/backend/src/db/migrations/meta/20241125154201_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241125154201_snapshot.json rename to apps/backend/src/db/migrations/meta/20241125154201_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241129163505_snapshot.json b/apps/backend/src/db/migrations/meta/20241129163505_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241129163505_snapshot.json rename to apps/backend/src/db/migrations/meta/20241129163505_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241204040215_snapshot.json b/apps/backend/src/db/migrations/meta/20241204040215_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241204040215_snapshot.json rename to apps/backend/src/db/migrations/meta/20241204040215_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241204191003_snapshot.json b/apps/backend/src/db/migrations/meta/20241204191003_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241204191003_snapshot.json rename to apps/backend/src/db/migrations/meta/20241204191003_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241205205803_snapshot.json b/apps/backend/src/db/migrations/meta/20241205205803_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241205205803_snapshot.json rename to apps/backend/src/db/migrations/meta/20241205205803_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241205205931_snapshot.json b/apps/backend/src/db/migrations/meta/20241205205931_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241205205931_snapshot.json rename to apps/backend/src/db/migrations/meta/20241205205931_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241212155603_snapshot.json b/apps/backend/src/db/migrations/meta/20241212155603_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241212155603_snapshot.json rename to apps/backend/src/db/migrations/meta/20241212155603_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241212172142_snapshot.json b/apps/backend/src/db/migrations/meta/20241212172142_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241212172142_snapshot.json rename to apps/backend/src/db/migrations/meta/20241212172142_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241212195126_snapshot.json b/apps/backend/src/db/migrations/meta/20241212195126_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241212195126_snapshot.json rename to apps/backend/src/db/migrations/meta/20241212195126_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241213211907_snapshot.json b/apps/backend/src/db/migrations/meta/20241213211907_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241213211907_snapshot.json rename to apps/backend/src/db/migrations/meta/20241213211907_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241213220438_snapshot.json b/apps/backend/src/db/migrations/meta/20241213220438_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241213220438_snapshot.json rename to apps/backend/src/db/migrations/meta/20241213220438_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241216025259_snapshot.json b/apps/backend/src/db/migrations/meta/20241216025259_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241216025259_snapshot.json rename to apps/backend/src/db/migrations/meta/20241216025259_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241216234805_snapshot.json b/apps/backend/src/db/migrations/meta/20241216234805_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241216234805_snapshot.json rename to apps/backend/src/db/migrations/meta/20241216234805_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241218101630_snapshot.json b/apps/backend/src/db/migrations/meta/20241218101630_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241218101630_snapshot.json rename to apps/backend/src/db/migrations/meta/20241218101630_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20241223124713_snapshot.json b/apps/backend/src/db/migrations/meta/20241223124713_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20241223124713_snapshot.json rename to apps/backend/src/db/migrations/meta/20241223124713_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20250114014739_snapshot.json b/apps/backend/src/db/migrations/meta/20250114014739_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20250114014739_snapshot.json rename to apps/backend/src/db/migrations/meta/20250114014739_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20250114015649_snapshot.json b/apps/backend/src/db/migrations/meta/20250114015649_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20250114015649_snapshot.json rename to apps/backend/src/db/migrations/meta/20250114015649_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20250114134605_snapshot.json b/apps/backend/src/db/migrations/meta/20250114134605_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20250114134605_snapshot.json rename to apps/backend/src/db/migrations/meta/20250114134605_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20250118150128_snapshot.json b/apps/backend/src/db/migrations/meta/20250118150128_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20250118150128_snapshot.json rename to apps/backend/src/db/migrations/meta/20250118150128_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20250122175816_snapshot.json b/apps/backend/src/db/migrations/meta/20250122175816_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20250122175816_snapshot.json rename to apps/backend/src/db/migrations/meta/20250122175816_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20250205150159_snapshot.json b/apps/backend/src/db/migrations/meta/20250205150159_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20250205150159_snapshot.json rename to apps/backend/src/db/migrations/meta/20250205150159_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20250329175015_snapshot.json b/apps/backend/src/db/migrations/meta/20250329175015_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20250329175015_snapshot.json rename to apps/backend/src/db/migrations/meta/20250329175015_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20250331232502_snapshot.json b/apps/backend/src/db/migrations/meta/20250331232502_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20250331232502_snapshot.json rename to apps/backend/src/db/migrations/meta/20250331232502_snapshot.json diff --git a/apps/backend/priv/migrations/meta/20250331233924_snapshot.json b/apps/backend/src/db/migrations/meta/20250331233924_snapshot.json similarity index 100% rename from apps/backend/priv/migrations/meta/20250331233924_snapshot.json rename to apps/backend/src/db/migrations/meta/20250331233924_snapshot.json diff --git a/apps/backend/priv/migrations/meta/_journal.json b/apps/backend/src/db/migrations/meta/_journal.json similarity index 100% rename from apps/backend/priv/migrations/meta/_journal.json rename to apps/backend/src/db/migrations/meta/_journal.json diff --git a/apps/backend/src/db/utils/postgres-errors.ts b/apps/backend/src/db/utils/postgres-errors.ts index 84d51a23..8069066d 100644 --- a/apps/backend/src/db/utils/postgres-errors.ts +++ b/apps/backend/src/db/utils/postgres-errors.ts @@ -383,3 +383,61 @@ export enum PgInternalError { DataCorrupted = 'XX001', IndexCorrupted = 'XX002', } + +// Utility functions to handle Drizzle-wrapped PostgresErrors + +import postgres from 'postgres' + +/** + * Extracts PostgresError from either the error itself or its cause. + * Drizzle ORM wraps PostgresError in a DrizzleQueryError with the original in `cause`. + */ +export function getPostgresError( + error: unknown +): postgres.PostgresError | null { + if (error instanceof postgres.PostgresError) { + return error + } + + const cause = (error as { cause?: unknown })?.cause + if (cause instanceof postgres.PostgresError) { + return cause + } + + return null +} + +/** + * Checks if the error is a unique constraint violation (duplicate key). + */ +export function isUniqueViolation(error: unknown): boolean { + const pgError = getPostgresError(error) + return pgError?.code === PgIntegrityConstraintViolation.UniqueViolation +} + +/** + * Checks if the error is a foreign key constraint violation. + */ +export function isForeignKeyViolation(error: unknown): boolean { + const pgError = getPostgresError(error) + return pgError?.code === PgIntegrityConstraintViolation.ForeignKeyViolation +} + +/** + * Checks if the error is a not null constraint violation. + */ +export function isNotNullViolation(error: unknown): boolean { + const pgError = getPostgresError(error) + return pgError?.code === PgIntegrityConstraintViolation.NotNullViolation +} + +/** + * Checks if the error matches a specific constraint name. + */ +export function isConstraintViolation( + error: unknown, + constraintName: string +): boolean { + const pgError = getPostgresError(error) + return pgError?.constraint_name === constraintName +} diff --git a/apps/backend/src/domain/services/follows/create-follow.ts b/apps/backend/src/domain/services/follows/create-follow.ts index c5cc3a8e..a03e45b0 100644 --- a/apps/backend/src/domain/services/follows/create-follow.ts +++ b/apps/backend/src/domain/services/follows/create-follow.ts @@ -1,6 +1,5 @@ -import postgres from 'postgres' import { insertFollow } from '@/db/repositories/followers-repository' -import { PgIntegrityConstraintViolation } from '@/db/utils/postgres-errors' +import { isUniqueViolation } from '@/db/utils/postgres-errors' import { FollowAlreadyRegisteredError } from '@/domain/errors/follow-already-registered' export type CreateFollowServiceInput = { @@ -17,10 +16,8 @@ export async function createFollowService({ return { follow } } catch (error) { - if (error instanceof postgres.PostgresError) { - if (error.code === PgIntegrityConstraintViolation.UniqueViolation) { - return new FollowAlreadyRegisteredError() - } + if (isUniqueViolation(error)) { + return new FollowAlreadyRegisteredError() } throw error diff --git a/apps/backend/src/domain/services/imports/create-user-import.ts b/apps/backend/src/domain/services/imports/create-user-import.ts index 155749bd..8bd7c29c 100644 --- a/apps/backend/src/domain/services/imports/create-user-import.ts +++ b/apps/backend/src/domain/services/imports/create-user-import.ts @@ -1,6 +1,5 @@ -import postgres from 'postgres' import { insertUserImport } from '@/db/repositories/user-import-repository' -import { PgIntegrityConstraintViolation } from '@/db/utils/postgres-errors' +import { isForeignKeyViolation } from '@/db/utils/postgres-errors' import type { InsertUserImportWithItems } from '@/domain/entities/import' import { FailedToInsertUserImport } from '@/domain/errors/failed-to-import-user-items' import { UserNotFoundError } from '@/domain/errors/user-not-found' @@ -9,10 +8,8 @@ export async function createUserImport(params: InsertUserImportWithItems) { try { return await insertUserImport(params) } catch (error) { - if (error instanceof postgres.PostgresError) { - if (error.code === PgIntegrityConstraintViolation.ForeignKeyViolation) { - return new UserNotFoundError() - } + if (isForeignKeyViolation(error)) { + return new UserNotFoundError() } throw new FailedToInsertUserImport() } diff --git a/apps/backend/src/domain/services/list-item/create-list-item.ts b/apps/backend/src/domain/services/list-item/create-list-item.ts index 13644417..f65e645c 100644 --- a/apps/backend/src/domain/services/list-item/create-list-item.ts +++ b/apps/backend/src/domain/services/list-item/create-list-item.ts @@ -1,6 +1,5 @@ -import postgres from 'postgres' import { insertListItem } from '@/db/repositories/list-item-repository' -import { PgIntegrityConstraintViolation } from '@/db/utils/postgres-errors' +import { isForeignKeyViolation } from '@/db/utils/postgres-errors' import type { InsertListItem } from '../../entities/list-item' import { ListNotFoundError } from '../../errors/list-not-found-error' @@ -13,10 +12,8 @@ export async function createListItemService( return { listItem } } catch (error) { - if (error instanceof postgres.PostgresError) { - if (error.code === PgIntegrityConstraintViolation.ForeignKeyViolation) { - return new ListNotFoundError() - } + if (isForeignKeyViolation(error)) { + return new ListNotFoundError() } throw error diff --git a/apps/backend/src/domain/services/lists/create-list.ts b/apps/backend/src/domain/services/lists/create-list.ts index 0312df4d..fe580f7d 100644 --- a/apps/backend/src/domain/services/lists/create-list.ts +++ b/apps/backend/src/domain/services/lists/create-list.ts @@ -1,8 +1,7 @@ import type { InferInsertModel } from 'drizzle-orm' -import postgres from 'postgres' import { insertList } from '@/db/repositories/list-repository' import type { schema } from '@/db/schema' -import { PgIntegrityConstraintViolation } from '@/db/utils/postgres-errors' +import { isForeignKeyViolation } from '@/db/utils/postgres-errors' import { UserNotFoundError } from '../../errors/user-not-found' export type CreateListInput = InferInsertModel @@ -18,10 +17,8 @@ export async function createList({ return { list } } catch (error) { - if (error instanceof postgres.PostgresError) { - if (error.code === PgIntegrityConstraintViolation.ForeignKeyViolation) { - return new UserNotFoundError() - } + if (isForeignKeyViolation(error)) { + return new UserNotFoundError() } throw error diff --git a/apps/backend/src/domain/services/review-replies/create-review-reply.ts b/apps/backend/src/domain/services/review-replies/create-review-reply.ts index 8a204059..c9f09649 100644 --- a/apps/backend/src/domain/services/review-replies/create-review-reply.ts +++ b/apps/backend/src/domain/services/review-replies/create-review-reply.ts @@ -1,6 +1,8 @@ -import postgres from 'postgres' import { insertReviewReply } from '@/db/repositories/review-replies-repository' -import { PgIntegrityConstraintViolation } from '@/db/utils/postgres-errors' +import { + getPostgresError, + isForeignKeyViolation, +} from '@/db/utils/postgres-errors' import type { InsertReviewReplyModel } from '@/domain/entities/review-reply' import { ReviewNotFoundError } from '@/domain/errors/review-not-found-error' import { UserNotFoundError } from '@/domain/errors/user-not-found' @@ -11,17 +13,16 @@ export async function createReviewReplyService(params: InsertReviewReplyModel) { return { reviewReply } } catch (error) { - if (error instanceof postgres.PostgresError) { - if (error.code === PgIntegrityConstraintViolation.ForeignKeyViolation) { - if ( - error.constraint_name === 'review_replies_review_id_reviews_id_fk' - ) { - return new ReviewNotFoundError() - } + if (isForeignKeyViolation(error)) { + const pgError = getPostgresError(error) + if ( + pgError?.constraint_name === 'review_replies_review_id_reviews_id_fk' + ) { + return new ReviewNotFoundError() + } - if (error.constraint_name === 'review_replies_user_id_users_id_fk') { - return new UserNotFoundError() - } + if (pgError?.constraint_name === 'review_replies_user_id_users_id_fk') { + return new UserNotFoundError() } } diff --git a/apps/backend/src/domain/services/reviews/create-review.ts b/apps/backend/src/domain/services/reviews/create-review.ts index 1b2b8d34..aede592b 100644 --- a/apps/backend/src/domain/services/reviews/create-review.ts +++ b/apps/backend/src/domain/services/reviews/create-review.ts @@ -1,6 +1,5 @@ -import postgres from 'postgres' import { insertReview } from '@/db/repositories/reviews-repository' -import { PgIntegrityConstraintViolation } from '@/db/utils/postgres-errors' +import { isForeignKeyViolation } from '@/db/utils/postgres-errors' import { UserNotFoundError } from '@/domain/errors/user-not-found' import type { InsertReviewModel } from '../../entities/review' @@ -10,10 +9,8 @@ export async function createReviewService(params: InsertReviewModel) { return { review } } catch (error) { - if (error instanceof postgres.PostgresError) { - if (error.code === PgIntegrityConstraintViolation.ForeignKeyViolation) { - return new UserNotFoundError() - } + if (isForeignKeyViolation(error)) { + return new UserNotFoundError() } throw error diff --git a/apps/backend/src/domain/services/user-items/create-user-item-episodes.spec.ts b/apps/backend/src/domain/services/user-items/create-user-item-episodes.spec.ts index d21d8660..cc94c9cc 100644 --- a/apps/backend/src/domain/services/user-items/create-user-item-episodes.spec.ts +++ b/apps/backend/src/domain/services/user-items/create-user-item-episodes.spec.ts @@ -1,4 +1,13 @@ -import { beforeAll, describe, expect, it } from 'vitest' +import { + beforeAll, + beforeEach, + describe, + expect, + it, + type Mock, + vi, +} from 'vitest' +import { tmdb } from '@/adapters/tmdb' import type { User } from '@/domain/entities/user' import type { UserItem } from '@/domain/entities/user-item' import { makeUser } from '@/test/factories/make-user' @@ -6,15 +15,73 @@ import { makeUserItem } from '@/test/factories/make-user-item' import { redisClient } from '@/test/mocks/redis' import { createUserItemEpisodesService } from './create-user-item-episodes' +vi.mock('@/adapters/tmdb', () => ({ + tmdb: { + tv: { + details: vi.fn(), + }, + season: { + details: vi.fn(), + }, + }, +})) + +const CHERNOBYL_TMDB_ID = 87108 + +const mockSeasons = [ + { + id: 114355, + name: 'Season 1', + overview: '', + poster_path: '/poster.jpg', + season_number: 1, + episode_count: 5, + air_date: '2019-05-06', + vote_average: 9.4, + }, +] + +const mockEpisodes = Array.from({ length: 5 }, (_, i) => ({ + id: 1000000 + i, + name: `Episode ${i + 1}`, + overview: '', + air_date: '2019-05-06', + episode_number: i + 1, + season_number: 1, + runtime: 60, + vote_average: 9.5, +})) + let user: User let userItem: UserItem describe('create user item episodes', () => { + beforeEach(() => { + ;(tmdb.tv.details as Mock).mockResolvedValue({ + id: CHERNOBYL_TMDB_ID, + name: 'Chernobyl', + overview: '', + poster_path: '/poster.jpg', + backdrop_path: '/backdrop.jpg', + first_air_date: '2019-05-06', + seasons: mockSeasons, + }) + + ;(tmdb.season.details as Mock).mockResolvedValue({ + id: 114355, + name: 'Season 1', + overview: '', + poster_path: '/poster.jpg', + season_number: 1, + episodes: mockEpisodes, + }) + }) + beforeAll(async () => { user = await makeUser() userItem = await makeUserItem({ userId: user.id, - tmdbId: 87108, + tmdbId: CHERNOBYL_TMDB_ID, mediaType: 'TV_SHOW', }) }) diff --git a/apps/backend/src/domain/services/user-stats/get-user-best-reviews.spec.ts b/apps/backend/src/domain/services/user-stats/get-user-best-reviews.spec.ts index 9b430793..3ee7ff81 100644 --- a/apps/backend/src/domain/services/user-stats/get-user-best-reviews.spec.ts +++ b/apps/backend/src/domain/services/user-stats/get-user-best-reviews.spec.ts @@ -1,9 +1,52 @@ +import { beforeEach, describe, expect, it, type Mock, vi } from 'vitest' +import { tmdb } from '@/adapters/tmdb' import { makeReview } from '@/test/factories/make-review' import { makeUser } from '@/test/factories/make-user' import { redisClient } from '@/test/mocks/redis' import { getUserBestReviewsService } from './get-user-best-reviews' +vi.mock('@/adapters/tmdb', () => ({ + tmdb: { + tv: { + details: vi.fn(), + }, + movies: { + details: vi.fn(), + }, + }, +})) + describe('get user best reviews', () => { + beforeEach(() => { + ;(tmdb.tv.details as Mock).mockImplementation((tmdbId: number) => { + if (tmdbId === 2316) { + return Promise.resolve({ + id: 2316, + name: 'The Office', + overview: '', + poster_path: '/poster.jpg', + backdrop_path: '/backdrop.jpg', + first_air_date: '2005-03-24', + }) + } + return Promise.resolve({}) + }) + + ;(tmdb.movies.details as Mock).mockImplementation((tmdbId: number) => { + if (tmdbId === 414906) { + return Promise.resolve({ + id: 414906, + title: 'The Batman', + overview: '', + poster_path: '/poster.jpg', + backdrop_path: '/backdrop.jpg', + release_date: '2022-03-04', + }) + } + return Promise.resolve({}) + }) + }) + it('should be able to get user best reviews', async () => { const user = await makeUser() diff --git a/apps/backend/src/domain/services/user-stats/get-user-most-watched-series.spec.ts b/apps/backend/src/domain/services/user-stats/get-user-most-watched-series.spec.ts index d9866d67..f3cafd46 100644 --- a/apps/backend/src/domain/services/user-stats/get-user-most-watched-series.spec.ts +++ b/apps/backend/src/domain/services/user-stats/get-user-most-watched-series.spec.ts @@ -1,16 +1,74 @@ +import { beforeEach, describe, expect, it, type Mock, vi } from 'vitest' +import { tmdb } from '@/adapters/tmdb' import { makeUser } from '@/test/factories/make-user' import { makeUserItem } from '@/test/factories/make-user-item' import { redisClient } from '@/test/mocks/redis' import { createUserItemEpisodesService } from '../user-items/create-user-item-episodes' import { getUserMostWatchedSeriesService } from './get-user-most-watched-series' +vi.mock('@/adapters/tmdb', () => ({ + tmdb: { + tv: { + details: vi.fn(), + }, + season: { + details: vi.fn(), + }, + }, +})) + const CHERNOBYL = { tmdbId: 87108, mediaType: 'TV_SHOW', episodes: 5, } as const +const mockSeasons = [ + { + id: 114355, + name: 'Season 1', + overview: '', + poster_path: '/poster.jpg', + season_number: 1, + episode_count: 5, + air_date: '2019-05-06', + vote_average: 9.4, + }, +] + +const mockEpisodes = Array.from({ length: 5 }, (_, i) => ({ + id: 1000000 + i, + name: `Episode ${i + 1}`, + overview: '', + air_date: '2019-05-06', + episode_number: i + 1, + season_number: 1, + runtime: 60, + vote_average: 9.5, +})) + describe('get user most watched series', () => { + beforeEach(() => { + ;(tmdb.tv.details as Mock).mockResolvedValue({ + id: CHERNOBYL.tmdbId, + name: 'Chernobyl', + overview: '', + poster_path: '/poster.jpg', + backdrop_path: '/backdrop.jpg', + first_air_date: '2019-05-06', + seasons: mockSeasons, + }) + + ;(tmdb.season.details as Mock).mockResolvedValue({ + id: 114355, + name: 'Season 1', + overview: '', + poster_path: '/poster.jpg', + season_number: 1, + episodes: mockEpisodes, + }) + }) + it('should be able to get most watched series', async () => { const user = await makeUser() diff --git a/apps/backend/src/domain/services/user-stats/get-user-total-hours.spec.ts b/apps/backend/src/domain/services/user-stats/get-user-total-hours.spec.ts index 722f4f8f..e4ab1c2e 100644 --- a/apps/backend/src/domain/services/user-stats/get-user-total-hours.spec.ts +++ b/apps/backend/src/domain/services/user-stats/get-user-total-hours.spec.ts @@ -1,9 +1,25 @@ +import { beforeEach, describe, expect, it, type Mock, vi } from 'vitest' +import { tmdb } from '@/adapters/tmdb' import { makeUser } from '@/test/factories/make-user' import { makeUserItem } from '@/test/factories/make-user-item' import { redisClient } from '@/test/mocks/redis' import { createUserItemEpisodesService } from '../user-items/create-user-item-episodes' import { getUserTotalHoursService } from './get-user-total-hours' +vi.mock('@/adapters/tmdb', () => ({ + tmdb: { + tv: { + details: vi.fn(), + }, + movies: { + details: vi.fn(), + }, + season: { + details: vi.fn(), + }, + }, +})) + const CHERNOBYL = { tmdbId: 87108, mediaType: 'TV_SHOW', @@ -16,7 +32,75 @@ const INCEPTION = { runtime: 148 / 60, } as const +const mockSeasons = [ + { + id: 114355, + name: 'Season 1', + overview: '', + poster_path: '/poster.jpg', + season_number: 1, + episode_count: 5, + air_date: '2019-05-06', + vote_average: 9.4, + }, +] + +const mockEpisodes = Array.from({ length: 5 }, (_, i) => ({ + id: 1000000 + i, + name: `Episode ${i + 1}`, + overview: '', + air_date: '2019-05-06', + episode_number: i + 1, + season_number: 1, + // CHERNOBYL total runtime: 331 minutes (5.516666666666667 hours) + // 331 / 5 = 66.2 minutes per episode + // Using 66 minutes for 4 episodes and 67 for 1 episode = 331 minutes total + runtime: i < 4 ? 66 : 67, + vote_average: 9.5, +})) + describe('get user total hours count', () => { + beforeEach(() => { + ;(tmdb.tv.details as Mock).mockImplementation((tmdbId: number) => { + if (tmdbId === CHERNOBYL.tmdbId) { + return Promise.resolve({ + id: CHERNOBYL.tmdbId, + name: 'Chernobyl', + overview: '', + poster_path: '/poster.jpg', + backdrop_path: '/backdrop.jpg', + first_air_date: '2019-05-06', + seasons: mockSeasons, + }) + } + return Promise.resolve({}) + }) + + ;(tmdb.movies.details as Mock).mockImplementation((tmdbId: number) => { + if (tmdbId === INCEPTION.tmdbId) { + return Promise.resolve({ + id: INCEPTION.tmdbId, + title: 'Inception', + overview: '', + poster_path: '/poster.jpg', + backdrop_path: '/backdrop.jpg', + release_date: '2010-07-16', + runtime: 148, + }) + } + return Promise.resolve({}) + }) + + ;(tmdb.season.details as Mock).mockResolvedValue({ + id: 114355, + name: 'Season 1', + overview: '', + poster_path: '/poster.jpg', + season_number: 1, + episodes: mockEpisodes, + }) + }) + it('should be able to get reviews count', async () => { const user = await makeUser() diff --git a/apps/backend/src/domain/services/user-stats/get-user-watched-cast.spec.ts b/apps/backend/src/domain/services/user-stats/get-user-watched-cast.spec.ts index d611cd42..10705700 100644 --- a/apps/backend/src/domain/services/user-stats/get-user-watched-cast.spec.ts +++ b/apps/backend/src/domain/services/user-stats/get-user-watched-cast.spec.ts @@ -1,12 +1,48 @@ +import { beforeEach, describe, expect, it, type Mock, vi } from 'vitest' +import { tmdb } from '@/adapters/tmdb' import { makeUser } from '@/test/factories/make-user' - import { makeUserItem } from '@/test/factories/make-user-item' import { redisClient } from '@/test/mocks/redis' import { getUserWatchedCastService } from './get-user-watched-cast' +vi.mock('@/adapters/tmdb', () => ({ + tmdb: { + movies: { + details: vi.fn(), + }, + credits: vi.fn(), + }, +})) + const ADAM_SANDLER_MOVIES = [9339, 1824] // Click, 50 First Dates describe('get user watched cast', () => { + beforeEach(() => { + ;(tmdb.movies.details as Mock).mockResolvedValue({ + id: 9339, + title: 'Click', + overview: '', + poster_path: '/poster.jpg', + backdrop_path: '/backdrop.jpg', + release_date: '2006-06-23', + }) + + ;(tmdb.credits as Mock).mockResolvedValue({ + id: 9339, + cast: [ + { + id: 19274, + name: 'Adam Sandler', + character: 'Michael Newman', + order: 0, + known_for_department: 'Acting', + profile_path: '/profile.jpg', + }, + ], + crew: [], + }) + }) + it('should be able to get user watched cast with right values', async () => { const user = await makeUser() diff --git a/apps/backend/src/domain/services/user-stats/get-user-watched-countries.spec.ts b/apps/backend/src/domain/services/user-stats/get-user-watched-countries.spec.ts index 6a63b60c..d141a856 100644 --- a/apps/backend/src/domain/services/user-stats/get-user-watched-countries.spec.ts +++ b/apps/backend/src/domain/services/user-stats/get-user-watched-countries.spec.ts @@ -1,12 +1,36 @@ +import { beforeEach, describe, expect, it, type Mock, vi } from 'vitest' +import { tmdb } from '@/adapters/tmdb' import { makeUser } from '@/test/factories/make-user' - import { makeUserItem } from '@/test/factories/make-user-item' import { redisClient } from '@/test/mocks/redis' import { getUserWatchedCountriesService } from './get-user-watched-countries' +vi.mock('@/adapters/tmdb', () => ({ + tmdb: { + movies: { + details: vi.fn(), + }, + }, +})) + const SCIENCE_FICTION_MOVIES = [157336] // Interestellar describe('get user watched countries', () => { + beforeEach(() => { + ;(tmdb.movies.details as Mock).mockResolvedValue({ + id: 157336, + title: 'Interstellar', + overview: '', + poster_path: '/poster.jpg', + backdrop_path: '/backdrop.jpg', + release_date: '2014-11-05', + production_countries: [ + { iso_3166_1: 'US', name: 'United States of America' }, + { iso_3166_1: 'GB', name: 'United Kingdom' }, + ], + }) + }) + it('should be able to get user watched countries with right values', async () => { const user = await makeUser() diff --git a/apps/backend/src/domain/services/user-stats/get-user-watched-genres.spec.ts b/apps/backend/src/domain/services/user-stats/get-user-watched-genres.spec.ts index 0fd633b8..a0850291 100644 --- a/apps/backend/src/domain/services/user-stats/get-user-watched-genres.spec.ts +++ b/apps/backend/src/domain/services/user-stats/get-user-watched-genres.spec.ts @@ -1,12 +1,36 @@ +import { beforeEach, describe, expect, it, type Mock, vi } from 'vitest' +import { tmdb } from '@/adapters/tmdb' import { makeUser } from '@/test/factories/make-user' - import { makeUserItem } from '@/test/factories/make-user-item' import { redisClient } from '@/test/mocks/redis' import { getUserWatchedGenresService } from './get-user-watched-genres' +vi.mock('@/adapters/tmdb', () => ({ + tmdb: { + movies: { + details: vi.fn(), + }, + }, +})) + const SCIENCE_FICTION_MOVIES = [157336] // Interestellar describe('get user watched genres', () => { + beforeEach(() => { + ;(tmdb.movies.details as Mock).mockResolvedValue({ + id: 157336, + title: 'Interstellar', + overview: '', + poster_path: '/poster.jpg', + backdrop_path: '/backdrop.jpg', + release_date: '2014-11-05', + genres: [ + { id: 878, name: 'Science Fiction' }, + { id: 18, name: 'Drama' }, + ], + }) + }) + it('should be able to get user watched genres with right values', async () => { const user = await makeUser() diff --git a/apps/backend/src/domain/services/users/create-user.ts b/apps/backend/src/domain/services/users/create-user.ts index cae3b405..0091fb49 100644 --- a/apps/backend/src/domain/services/users/create-user.ts +++ b/apps/backend/src/domain/services/users/create-user.ts @@ -1,6 +1,5 @@ -import postgres from 'postgres' import { insertUser } from '@/db/repositories/user-repository' -import { PgIntegrityConstraintViolation } from '@/db/utils/postgres-errors' +import { isUniqueViolation } from '@/db/utils/postgres-errors' import { hashPassword } from '@/utils/password' import { EmailOrUsernameAlreadyRegisteredError } from '../../errors/email-or-username-already-registered-error' import { HashPasswordError } from '../../errors/hash-password-error' @@ -35,11 +34,7 @@ export async function createUser({ return { user: { ...formattedUser, subscriptionType: 'MEMBER' } } } catch (error) { - const isEmailOrUsernameAlreadyRegistered = - error instanceof postgres.PostgresError && - error.code === PgIntegrityConstraintViolation.UniqueViolation - - if (isEmailOrUsernameAlreadyRegistered) { + if (isUniqueViolation(error)) { return new EmailOrUsernameAlreadyRegisteredError() } diff --git a/apps/backend/src/domain/services/users/update-user.ts b/apps/backend/src/domain/services/users/update-user.ts index e3c41f87..c7e1e29c 100644 --- a/apps/backend/src/domain/services/users/update-user.ts +++ b/apps/backend/src/domain/services/users/update-user.ts @@ -1,6 +1,5 @@ -import postgres from 'postgres' import { updateUser } from '@/db/repositories/user-repository' -import { PgIntegrityConstraintViolation } from '@/db/utils/postgres-errors' +import { isUniqueViolation } from '@/db/utils/postgres-errors' import { NoValidFieldsError } from '@/domain/errors/no-valid-fields' import { UserNotFoundError } from '@/domain/errors/user-not-found' import { UsernameAlreadyRegisteredError } from '@/domain/errors/username-already-registered' @@ -31,11 +30,7 @@ export async function updateUserService({ return { user } } catch (error) { - const isUsernameAlreadyRegistered = - error instanceof postgres.PostgresError && - error.code === PgIntegrityConstraintViolation.UniqueViolation - - if (isUsernameAlreadyRegistered) { + if (isUniqueViolation(error)) { return new UsernameAlreadyRegisteredError() } diff --git a/apps/backend/src/http/server.ts b/apps/backend/src/http/server.ts index c75b48ba..77b78cad 100644 --- a/apps/backend/src/http/server.ts +++ b/apps/backend/src/http/server.ts @@ -1,6 +1,3 @@ -import * as fs from 'node:fs' -import path from 'node:path' -import { fileURLToPath } from 'node:url' import fastifySwagger from '@fastify/swagger' import fastify from 'fastify' import type { FastifyInstance } from 'fastify/types/instance' @@ -15,7 +12,7 @@ import { config } from '../config' import { routes } from './routes' import { transformSwaggerSchema } from './transform-schema' -const app: FastifyInstance = buildFastifyInstance() +const app: FastifyInstance = fastify() export function startServer() { app.setValidatorCompiler(validatorCompiler) @@ -85,26 +82,3 @@ export function startServer() { logger.info(`HTTP server running at ${config.app.BASE_URL}`) }) } - -export function buildFastifyInstance() { - if (config.featureFlags.ENABLE_CERTS === 'true') { - const __filename = fileURLToPath(import.meta.url) - const __dirname = path.dirname(__filename) - - const CERT_CA = path.join(__dirname, '../../certs/ca.pem') - const CERT_KEY = path.join(__dirname, '../../certs/server.key') - const CERT_CRT = path.join(__dirname, '../../certs/server.crt') - - return fastify({ - https: { - key: fs.readFileSync(CERT_KEY), - cert: fs.readFileSync(CERT_CRT), - ca: fs.readFileSync(CERT_CA), - requestCert: true, - rejectUnauthorized: true, - }, - }) - } - - return fastify() -} diff --git a/apps/backend/src/test/global-setup.ts b/apps/backend/src/test/global-setup.ts new file mode 100644 index 00000000..4b6a0692 --- /dev/null +++ b/apps/backend/src/test/global-setup.ts @@ -0,0 +1,180 @@ +import { drizzle } from 'drizzle-orm/postgres-js' +import { migrate } from 'drizzle-orm/postgres-js/migrator' +import postgres from 'postgres' +import { + GenericContainer, + type StartedTestContainer, + Wait, +} from 'testcontainers' + +let dbConfig: { + host: string + port: number + user: string + password: string + database: string + container: StartedTestContainer +} + +let localstackConfig: { + host: string + port: number + container: StartedTestContainer +} + +let redisConfig: { + host: string + port: number + container: StartedTestContainer +} + +declare global { + // eslint-disable-next-line no-var + var __DB_CONFIG__: typeof dbConfig + // eslint-disable-next-line no-var + var __LOCALSTACK_CONFIG__: typeof localstackConfig + // eslint-disable-next-line no-var + var __REDIS_CONFIG__: typeof redisConfig +} + +export async function setup() { + // General + process.env.APP_ENV = 'test' + process.env.PORT = '3333' + process.env.BASE_URL = 'http://localhost:3333' + process.env.JWT_SECRET = 'secret' + process.env.API_KEY = 'test-api-key-for-testing' + + // TMDB + process.env.TMDB_ACCESS_TOKEN = 'sda' + + // Client + process.env.CLIENT_URL = 'http://localhost:3000' + + // Cloudflare + process.env.CLOUDFLARE_BUCKET = 'CLOUDFLARE_BUCKET' + process.env.CLOUDFLARE_ACCESS_KEY_ID = 'CLOUDFLARE_ACCESS_KEY_ID' + process.env.CLOUDFLARE_SECRET_ACCESS_KEY = 'CLOUDFLARE_SECRET_ACCESS_KEY' + process.env.CLOUDFLARE_ACCOUNT_ID = 'CLOUDFLARE_ACCOUNT_ID' + process.env.CLOUDFLARE_PUBLIC_URL = 'https://google.com' + + // SQS + process.env.AWS_REGION = 'sa-east-1' + process.env.AWS_ACCESS_KEY_ID = 'banana' + process.env.AWS_SECRET_ACCESS_KEY = 'banana' + + // Queues + process.env.IMPORT_MOVIES_QUEUE = 'import-movies-queue-test' + process.env.IMPORT_SERIES_QUEUE = 'import-series-queue-test' + + // MAL API + process.env.MAL_CLIENT_ID = 'banana' + + // Feature Flags + process.env.ENABLE_SQS = 'true' + process.env.ENABLE_IMPORT_MOVIES = 'true' + process.env.ENABLE_IMPORT_SERIES = 'true' + process.env.ENABLE_CRON_JOBS = 'false' + + // OpenAI + process.env.OPENAI_API_KEY = 'open_api_key' + + await setupDatabase() + await setupLocalStack() + await setupRedis() + + return async () => { + await global.__REDIS_CONFIG__?.container?.stop() + await global.__LOCALSTACK_CONFIG__?.container?.stop() + await global.__DB_CONFIG__?.container?.stop() + } +} + +async function setupDatabase() { + const container = await new GenericContainer('bitnami/postgresql:latest') + .withEnvironment({ + POSTGRES_PASSWORD: 'test', + POSTGRES_DB: 'plotwist_db', + }) + .withExposedPorts(5432) + .withWaitStrategy( + Wait.forLogMessage(/database system is ready to accept connections/i) + ) + .start() + + dbConfig = { + host: container.getHost(), + port: container.getMappedPort(5432), + user: 'postgres', + password: 'test', + database: 'plotwist_db', + container, + } + + const dbUrl = `postgres://${dbConfig.user}:${dbConfig.password}@${dbConfig.host}:${dbConfig.port}/${dbConfig.database}` + process.env.DATABASE_URL = dbUrl + + global.__DB_CONFIG__ = dbConfig + + // Wait for database to be fully ready by attempting a connection + const client = postgres(dbUrl) + let retries = 0 + const maxRetries = 30 + while (retries < maxRetries) { + try { + await client`SELECT 1` + break + } catch (error) { + if (retries === maxRetries - 1) { + await client.end() + throw error + } + await new Promise(resolve => setTimeout(resolve, 1000)) + retries++ + } + } + + const db = drizzle(client) + await migrate(db, { migrationsFolder: './src/db/migrations' }) + await client.end() +} + +async function setupLocalStack() { + const container = await new GenericContainer('localstack/localstack:latest') + .withEnvironment({ + SERVICES: 'sqs', + DOCKER_HOST: 'unix:///var/run/docker.sock', + }) + .withExposedPorts(4566) + .withWaitStrategy(Wait.forLogMessage(/.*Ready.*/)) + .start() + + localstackConfig = { + host: container.getHost(), + port: container.getMappedPort(4566), + container, + } + + const localstackEndpoint = `http://${localstackConfig.host}:${localstackConfig.port}` + process.env.LOCALSTACK_ENDPOINT = localstackEndpoint + + global.__LOCALSTACK_CONFIG__ = localstackConfig +} + +async function setupRedis() { + const container = await new GenericContainer('redis:latest') + .withExposedPorts(6379) + .withWaitStrategy(Wait.forLogMessage(/Ready to accept connections/i)) + .start() + + redisConfig = { + host: container.getHost(), + port: container.getMappedPort(6379), + container, + } + + const redisUrl = `redis://${redisConfig.host}:${redisConfig.port}` + process.env.REDIS_URL = redisUrl + + global.__REDIS_CONFIG__ = redisConfig +} diff --git a/apps/backend/vitest.config.ts b/apps/backend/vitest.config.ts index c328cc7d..8b77e929 100644 --- a/apps/backend/vitest.config.ts +++ b/apps/backend/vitest.config.ts @@ -6,6 +6,7 @@ export default defineConfig({ test: { globals: true, root: './', + globalSetup: './src/test/global-setup.ts', coverage: { provider: 'v8', reporter: ['text', 'json', 'html'], diff --git a/biome.json b/biome.json index f0fe33d8..27792ba1 100644 --- a/biome.json +++ b/biome.json @@ -47,7 +47,7 @@ "!apps/backend/dist", "!apps/web/src/api", "!packages/ui", - "!apps/backend/priv" + "!apps/backend/src/db/migrations" ] } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a523846e..afa35721 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -186,6 +186,9 @@ importers: react-email: specifier: ^5.1.1 version: 5.1.1 + testcontainers: + specifier: ^11.11.0 + version: 11.11.0 tsup: specifier: ^8.5.1 version: 8.5.1(@swc/core@1.15.8)(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) @@ -1494,6 +1497,9 @@ packages: resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} + '@balena/dockerignore@1.0.2': + resolution: {integrity: sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==} + '@bcoe/v8-coverage@1.0.2': resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} @@ -2172,6 +2178,20 @@ packages: '@gerrit0/mini-shiki@3.20.0': resolution: {integrity: sha512-Wa57i+bMpK6PGJZ1f2myxo3iO+K/kZikcyvH8NIqNNZhQUbDav7V9LQmWOXhf946mz5c1NZ19WMsGYiDKTryzQ==} + '@grpc/grpc-js@1.14.3': + resolution: {integrity: sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==} + engines: {node: '>=12.10.0'} + + '@grpc/proto-loader@0.7.15': + resolution: {integrity: sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==} + engines: {node: '>=6'} + hasBin: true + + '@grpc/proto-loader@0.8.0': + resolution: {integrity: sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==} + engines: {node: '>=6'} + hasBin: true + '@hookform/resolvers@5.2.2': resolution: {integrity: sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==} peerDependencies: @@ -2398,6 +2418,9 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@js-sdsl/ordered-map@4.4.2': + resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} + '@jsep-plugin/assignment@1.3.0': resolution: {integrity: sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==} engines: {node: '>= 10.16.0'} @@ -2569,12 +2592,46 @@ packages: '@pinojs/redact@0.4.0': resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + '@plotwist_app/tmdb@0.2.5': resolution: {integrity: sha512-wIS1l4VbJMUb+e7UEme+85eBU7evcFNhTr4iLvlxlH1baUJmcXF3fOuUr7g2gFzCZ/IabvgqmD7GABCGPlIBGw==} '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.4': + resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + + '@protobufjs/eventemitter@1.1.0': + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + + '@protobufjs/fetch@1.1.0': + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/inquire@1.1.0': + resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.0': + resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + '@puppeteer/browsers@2.11.0': resolution: {integrity: sha512-n6oQX6mYkG8TRPuPXmbPidkUbsSRalhmaaVAQxvH1IkQy63cwsH+kOjB3e4cpCDHg0aSvsiX9bQ4s2VB6mGWUQ==} engines: {node: '>=18'} @@ -4186,6 +4243,12 @@ packages: '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/docker-modem@3.0.6': + resolution: {integrity: sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==} + + '@types/dockerode@3.3.47': + resolution: {integrity: sha512-ShM1mz7rCjdssXt7Xz0u1/R2BJC7piWa3SJpUBiVjCf2A3XNn4cP6pUVaD8bLanpPVVn4IKzJuw3dOvkJ8IbYw==} + '@types/es-aggregate-error@1.0.6': resolution: {integrity: sha512-qJ7LIFp06h1QE1aVxbVd+zJP2wdaugYXYfd6JxsyRMrYHaxb6itXPogW2tz+ylUJ1n1b+JF1PHyYCfYHm0dvUg==} @@ -4230,6 +4293,9 @@ packages: '@types/node-cron@3.0.11': resolution: {integrity: sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg==} + '@types/node@18.19.130': + resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==} + '@types/node@22.19.3': resolution: {integrity: sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==} @@ -4256,6 +4322,15 @@ packages: '@types/sax@1.2.7': resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==} + '@types/ssh2-streams@0.1.13': + resolution: {integrity: sha512-faHyY3brO9oLEA0QlcO8N2wT7R0+1sHWZvQ+y3rMLwdY1ZyS1z0W3t65j9PqT4HmQ6ALzNe7RZlNuCNE0wBSWA==} + + '@types/ssh2@0.5.52': + resolution: {integrity: sha512-lbLLlXxdCZOSJMCInKH2+9V/77ET2J6NPQHpFI0kda61Dd1KglJs+fPQBchizmzYSOJBgdTajhPqBO1xxLywvg==} + + '@types/ssh2@1.15.5': + resolution: {integrity: sha512-N1ASjp/nXH3ovBHddRJpli4ozpk6UdDYIX4RJWFa9L1YKnzdhTlVmiGHm4DZnj/jLbqZpes4aeR30EFGQtvhQQ==} + '@types/statuses@2.0.6': resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} @@ -4434,6 +4509,14 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} + archiver-utils@5.0.2: + resolution: {integrity: sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==} + engines: {node: '>= 14'} + + archiver@7.0.1: + resolution: {integrity: sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==} + engines: {node: '>= 14'} + arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} @@ -4462,6 +4545,9 @@ packages: asn1.js@5.4.1: resolution: {integrity: sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==} + asn1@0.2.6: + resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -4481,6 +4567,12 @@ packages: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} + async-lock@1.4.1: + resolution: {integrity: sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==} + + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -4590,6 +4682,9 @@ packages: resolution: {integrity: sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==} engines: {node: '>=10.0.0'} + bcrypt-pbkdf@1.0.2: + resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} + bcryptjs@3.0.3: resolution: {integrity: sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==} hasBin: true @@ -4601,6 +4696,9 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + bn.js@4.12.2: resolution: {integrity: sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==} @@ -4628,18 +4726,33 @@ packages: buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + buffer-crc32@1.0.0: + resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==} + engines: {node: '>=8.0.0'} + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} buffer@5.6.0: resolution: {integrity: sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==} + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + + buildcheck@0.0.7: + resolution: {integrity: sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA==} + engines: {node: '>=10.0.0'} + bundle-require@5.1.0: resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} peerDependencies: esbuild: '>=0.18' + byline@5.0.0: + resolution: {integrity: sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q==} + engines: {node: '>=0.10.0'} + cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} @@ -4722,6 +4835,9 @@ packages: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + chromium-bidi@12.0.1: resolution: {integrity: sha512-fGg+6jr0xjQhzpy5N4ErZxQ4wF7KLEvhGZXD6EgvZKDhu7iOhZXnZhcDxPJDcwTcrD48NPzOCo84RP2lv3Z+Cg==} peerDependencies: @@ -4814,6 +4930,10 @@ packages: compare-versions@6.1.1: resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} + compress-commons@6.0.2: + resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==} + engines: {node: '>= 14'} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -4849,6 +4969,9 @@ packages: core-js-compat@3.47.0: resolution: {integrity: sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ==} + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + cors@2.8.5: resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} engines: {node: '>= 0.10'} @@ -4874,6 +4997,19 @@ packages: countup.js@2.9.0: resolution: {integrity: sha512-llqrvyXztRFPp6+i8jx25phHWcVWhrHO4Nlt0uAOSKHB8778zzQswa4MU3qKBvkXfJKftRYFJuVHez67lyKdHg==} + cpu-features@0.0.10: + resolution: {integrity: sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==} + engines: {node: '>=10.0.0'} + + crc-32@1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + + crc32-stream@6.0.0: + resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==} + engines: {node: '>= 14'} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -5109,6 +5245,18 @@ packages: dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + docker-compose@1.3.0: + resolution: {integrity: sha512-7Gevk/5eGD50+eMD+XDnFnOrruFkL0kSd7jEG4cjmqweDSUhB7i0g8is/nBdVpl+Bx338SqIB2GLKm32M+Vs6g==} + engines: {node: '>= 6.0.0'} + + docker-modem@5.0.6: + resolution: {integrity: sha512-ens7BiayssQz/uAxGzH8zGXCtiV24rRWXdjNha5V4zSOcxmAZsfGVm/PPFbwQdqEkDnhG+SyR9E3zSHUbOKXBQ==} + engines: {node: '>= 8.0'} + + dockerode@4.0.9: + resolution: {integrity: sha512-iND4mcOWhPaCNh54WmK/KoSb35AFqPAUWFMffTQcp52uQt36b5uNwEJTSXntJZBbeGad72Crbi/hvDIv6us/6Q==} + engines: {node: '>= 8.0'} + dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} @@ -5632,6 +5780,9 @@ packages: react-dom: optional: true + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + fs-extra@11.3.3: resolution: {integrity: sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==} engines: {node: '>=14.14'} @@ -5675,6 +5826,10 @@ packages: resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} engines: {node: '>=6'} + get-port@7.1.0: + resolution: {integrity: sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==} + engines: {node: '>=16'} + get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} @@ -5706,6 +5861,10 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + hasBin: true + glob@11.1.0: resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==} engines: {node: 20 || >=22} @@ -6055,6 +6214,9 @@ packages: resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} engines: {node: '>= 0.4'} + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} @@ -6081,6 +6243,9 @@ packages: resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} engines: {node: '>=8'} + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jackspeak@4.1.1: resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} engines: {node: 20 || >=22} @@ -6178,6 +6343,10 @@ packages: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} + lazystream@1.0.1: + resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} + engines: {node: '>= 0.6.3'} + leac@0.6.0: resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==} @@ -6209,6 +6378,9 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} @@ -6254,6 +6426,9 @@ packages: resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==} engines: {node: '>= 0.6.0'} + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -6264,6 +6439,9 @@ packages: lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@11.2.4: resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==} engines: {node: 20 || >=22} @@ -6495,6 +6673,10 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + minimatch@6.2.0: resolution: {integrity: sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg==} engines: {node: '>=10'} @@ -6513,6 +6695,14 @@ packages: mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + mlly@1.8.0: resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} @@ -6549,6 +6739,9 @@ packages: mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + nan@2.24.0: + resolution: {integrity: sha512-Vpf9qnVW1RaDkoNKFUvfxqAbtI8ncb8OJlqZ9wwpXzWPEsvsB1nvdUi6oYrHIkQ1Y/tMDnr1h4nczS0VB9Xykg==} + nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -6862,6 +7055,10 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + path-scurry@2.0.1: resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} engines: {node: 20 || >=22} @@ -7018,12 +7215,19 @@ packages: resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} engines: {node: '>=6'} + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + process-warning@4.0.1: resolution: {integrity: sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==} process-warning@5.0.0: resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + progress@2.0.3: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} engines: {node: '>=0.4.0'} @@ -7035,9 +7239,20 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + proper-lockfile@4.1.2: + resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} + + properties-reader@2.3.0: + resolution: {integrity: sha512-z597WicA7nDZxK12kZqHr2TcvwNU1GCfA5UwfDY/HDp3hXPoPlb5rlEx9bwGTiJnc0OqbBTkU975jDToth8Gxw==} + engines: {node: '>=14'} + property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + protobufjs@7.5.4: + resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} + engines: {node: '>=12.0.0'} + proxy-agent@6.5.0: resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==} engines: {node: '>= 14'} @@ -7228,10 +7443,20 @@ packages: read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} + readable-stream@4.7.0: + resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + readdir-glob@1.1.3: + resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} + readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -7374,6 +7599,10 @@ packages: resolution: {integrity: sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==} engines: {node: '>=10'} + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + rettime@0.7.0: resolution: {integrity: sha512-LPRKoHnLKd/r3dVxcwO7vhCW+orkOGj9ViueosEBK6ie89CijnfRlhaDhHq/3Hxu4CkWQtxwlBG0mzTQY6uQjw==} @@ -7396,6 +7625,9 @@ packages: resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -7596,6 +7828,9 @@ packages: space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + split-ca@1.0.1: + resolution: {integrity: sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==} + split2@4.2.0: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} @@ -7603,6 +7838,13 @@ packages: sprintf-js@1.1.3: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + ssh-remote-port-forward@1.0.4: + resolution: {integrity: sha512-x0LV1eVDwjf1gmG7TTnfqIzf+3VPRz7vrNIjX6oYLbeCrf/PeVY6hkT68Mg+q02qXxQhrLjB0jfgvhevoCRmLQ==} + + ssh2@1.17.0: + resolution: {integrity: sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ==} + engines: {node: '>=10.16.0'} + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -7664,6 +7906,9 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} @@ -7793,12 +8038,22 @@ packages: tailwindcss@4.1.18: resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==} + tar-fs@2.1.4: + resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} + tar-fs@3.1.1: resolution: {integrity: sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==} + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + tar-stream@3.1.7: resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + testcontainers@11.11.0: + resolution: {integrity: sha512-nKTJn3n/gkyGg/3SVkOwX+isPOGSHlfI+CWMobSmvQrsj7YW01aWvl2pYIfV4LMd+C8or783yYrzKSK2JlP+Qw==} + text-decoder@1.2.3: resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} @@ -7979,6 +8234,9 @@ packages: tween-functions@1.2.0: resolution: {integrity: sha512-PZBtLYcCLtEcjL14Fzb1gSxPBeL7nWvGhO5ZFPGqziCcr8uvHp0NDmdjBchp6KHL+tExcg0m3NISmKxhU394dA==} + tweetnacl@0.14.5: + resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} + type-fest@5.3.1: resolution: {integrity: sha512-VCn+LMHbd4t6sF3wfU/+HKT63C9OoyrSIf4b+vtWHpt2U7/4InZG467YDNMFMR70DdHjAdpPWmw2lzRdg0Xqqg==} engines: {node: '>=20'} @@ -8043,6 +8301,9 @@ packages: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -8430,6 +8691,10 @@ packages: resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} engines: {node: '>=18'} + zip-stream@6.0.1: + resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} + engines: {node: '>= 14'} + zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} @@ -9779,6 +10044,8 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@balena/dockerignore@1.0.2': {} + '@bcoe/v8-coverage@1.0.2': {} '@biomejs/biome@2.3.11': @@ -10249,6 +10516,25 @@ snapshots: '@shikijs/types': 3.20.0 '@shikijs/vscode-textmate': 10.0.2 + '@grpc/grpc-js@1.14.3': + dependencies: + '@grpc/proto-loader': 0.8.0 + '@js-sdsl/ordered-map': 4.4.2 + + '@grpc/proto-loader@0.7.15': + dependencies: + lodash.camelcase: 4.3.0 + long: 5.3.2 + protobufjs: 7.5.4 + yargs: 17.7.2 + + '@grpc/proto-loader@0.8.0': + dependencies: + lodash.camelcase: 4.3.0 + long: 5.3.2 + protobufjs: 7.5.4 + yargs: 17.7.2 + '@hookform/resolvers@5.2.2(react-hook-form@7.70.0(react@19.2.3))': dependencies: '@standard-schema/utils': 0.3.0 @@ -10439,6 +10725,8 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@js-sdsl/ordered-map@4.4.2': {} + '@jsep-plugin/assignment@1.3.0(jsep@1.4.0)': dependencies: jsep: 1.4.0 @@ -10700,6 +10988,9 @@ snapshots: '@pinojs/redact@0.4.0': {} + '@pkgjs/parseargs@0.11.0': + optional: true + '@plotwist_app/tmdb@0.2.5(@swc/core@1.15.8)(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(yaml@2.8.2)': dependencies: axios: 1.13.2 @@ -10718,6 +11009,29 @@ snapshots: '@polka/url@1.0.0-next.29': {} + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.4': {} + + '@protobufjs/eventemitter@1.1.0': {} + + '@protobufjs/fetch@1.1.0': + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/inquire@1.1.0': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.0': {} + '@puppeteer/browsers@2.11.0': dependencies: debug: 4.4.3 @@ -12485,6 +12799,17 @@ snapshots: '@types/deep-eql@4.0.2': {} + '@types/docker-modem@3.0.6': + dependencies: + '@types/node': 25.0.3 + '@types/ssh2': 1.15.5 + + '@types/dockerode@3.3.47': + dependencies: + '@types/docker-modem': 3.0.6 + '@types/node': 25.0.3 + '@types/ssh2': 1.15.5 + '@types/es-aggregate-error@1.0.6': dependencies: '@types/node': 25.0.3 @@ -12527,6 +12852,10 @@ snapshots: '@types/node-cron@3.0.11': {} + '@types/node@18.19.130': + dependencies: + undici-types: 5.26.5 + '@types/node@22.19.3': dependencies: undici-types: 6.21.0 @@ -12560,6 +12889,19 @@ snapshots: dependencies: '@types/node': 25.0.3 + '@types/ssh2-streams@0.1.13': + dependencies: + '@types/node': 25.0.3 + + '@types/ssh2@0.5.52': + dependencies: + '@types/node': 25.0.3 + '@types/ssh2-streams': 0.1.13 + + '@types/ssh2@1.15.5': + dependencies: + '@types/node': 18.19.130 + '@types/statuses@2.0.6': {} '@types/unist@2.0.11': {} @@ -12748,6 +13090,29 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 + archiver-utils@5.0.2: + dependencies: + glob: 10.5.0 + graceful-fs: 4.2.11 + is-stream: 2.0.1 + lazystream: 1.0.1 + lodash: 4.17.21 + normalize-path: 3.0.0 + readable-stream: 4.7.0 + + archiver@7.0.1: + dependencies: + archiver-utils: 5.0.2 + async: 3.2.6 + buffer-crc32: 1.0.0 + readable-stream: 4.7.0 + readdir-glob: 1.1.3 + tar-stream: 3.1.7 + zip-stream: 6.0.1 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + arg@5.0.2: {} argparse@2.0.1: {} @@ -12784,6 +13149,10 @@ snapshots: minimalistic-assert: 1.0.1 safer-buffer: 2.1.2 + asn1@0.2.6: + dependencies: + safer-buffer: 2.1.2 + assertion-error@2.0.1: {} ast-types@0.13.4: @@ -12800,6 +13169,10 @@ snapshots: async-function@1.0.0: {} + async-lock@1.4.1: {} + + async@3.2.6: {} + asynckit@0.4.0: {} atomic-sleep@1.0.0: {} @@ -12910,6 +13283,10 @@ snapshots: basic-ftp@5.1.0: {} + bcrypt-pbkdf@1.0.2: + dependencies: + tweetnacl: 0.14.5 + bcryptjs@3.0.3: {} bidi-js@1.0.3: @@ -12918,6 +13295,12 @@ snapshots: binary-extensions@2.3.0: {} + bl@4.1.0: + dependencies: + buffer: 5.6.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + bn.js@4.12.2: {} boolbase@1.0.0: {} @@ -12947,6 +13330,8 @@ snapshots: buffer-crc32@0.2.13: {} + buffer-crc32@1.0.0: {} + buffer-from@1.1.2: {} buffer@5.6.0: @@ -12954,11 +13339,21 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + buildcheck@0.0.7: + optional: true + bundle-require@5.1.0(esbuild@0.27.2): dependencies: esbuild: 0.27.2 load-tsconfig: 0.2.5 + byline@5.0.0: {} + cac@6.7.14: {} call-bind-apply-helpers@1.0.2: @@ -13050,6 +13445,8 @@ snapshots: dependencies: readdirp: 4.1.2 + chownr@1.1.4: {} + chromium-bidi@12.0.1(devtools-protocol@0.0.1534754): dependencies: devtools-protocol: 0.0.1534754 @@ -13130,6 +13527,14 @@ snapshots: compare-versions@6.1.1: {} + compress-commons@6.0.2: + dependencies: + crc-32: 1.2.2 + crc32-stream: 6.0.0 + is-stream: 2.0.1 + normalize-path: 3.0.0 + readable-stream: 4.7.0 + concat-map@0.0.1: {} conf@15.0.2: @@ -13162,6 +13567,8 @@ snapshots: dependencies: browserslist: 4.28.1 + core-util-is@1.0.3: {} + cors@2.8.5: dependencies: object-assign: 4.1.1 @@ -13187,6 +13594,19 @@ snapshots: countup.js@2.9.0: {} + cpu-features@0.0.10: + dependencies: + buildcheck: 0.0.7 + nan: 2.24.0 + optional: true + + crc-32@1.2.2: {} + + crc32-stream@6.0.0: + dependencies: + crc-32: 1.2.2 + readable-stream: 4.7.0 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -13414,6 +13834,31 @@ snapshots: dlv@1.1.3: {} + docker-compose@1.3.0: + dependencies: + yaml: 2.8.2 + + docker-modem@5.0.6: + dependencies: + debug: 4.4.3 + readable-stream: 3.6.2 + split-ca: 1.0.1 + ssh2: 1.17.0 + transitivePeerDependencies: + - supports-color + + dockerode@4.0.9: + dependencies: + '@balena/dockerignore': 1.0.2 + '@grpc/grpc-js': 1.14.3 + '@grpc/proto-loader': 0.7.15 + docker-modem: 5.0.6 + protobufjs: 7.5.4 + tar-fs: 2.1.4 + uuid: 10.0.0 + transitivePeerDependencies: + - supports-color + dom-accessibility-api@0.5.16: {} dom-serializer@2.0.0: @@ -14023,6 +14468,8 @@ snapshots: react: 19.2.3 react-dom: 19.2.3(react@19.2.3) + fs-constants@1.0.0: {} + fs-extra@11.3.3: dependencies: graceful-fs: 4.2.11 @@ -14068,6 +14515,8 @@ snapshots: get-nonce@1.0.1: {} + get-port@7.1.0: {} + get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 @@ -14105,6 +14554,15 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + glob@11.1.0: dependencies: foreground-child: 3.3.1 @@ -14496,6 +14954,8 @@ snapshots: call-bound: 1.0.4 get-intrinsic: 1.3.0 + isarray@1.0.0: {} + isarray@2.0.5: {} isexe@2.0.0: {} @@ -14531,6 +14991,12 @@ snapshots: html-escaper: 2.0.2 istanbul-lib-report: 3.0.1 + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + jackspeak@4.1.1: dependencies: '@isaacs/cliui': 8.0.2 @@ -14627,6 +15093,10 @@ snapshots: kleur@3.0.3: {} + lazystream@1.0.1: + dependencies: + readable-stream: 2.3.8 + leac@0.6.0: {} leven@3.1.0: {} @@ -14653,6 +15123,8 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash.camelcase@4.3.0: {} + lodash.debounce@4.0.8: {} lodash.defaults@4.2.0: {} @@ -14687,6 +15159,8 @@ snapshots: loglevel@1.9.2: {} + long@5.3.2: {} + longest-streak@3.1.0: {} loose-envify@1.4.0: @@ -14697,6 +15171,8 @@ snapshots: dependencies: tslib: 2.8.1 + lru-cache@10.4.3: {} + lru-cache@11.2.4: {} lru-cache@5.1.1: @@ -15098,6 +15574,10 @@ snapshots: dependencies: brace-expansion: 1.1.12 + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.2 + minimatch@6.2.0: dependencies: brace-expansion: 2.0.2 @@ -15112,6 +15592,10 @@ snapshots: mitt@3.0.1: {} + mkdirp-classic@0.5.3: {} + + mkdirp@1.0.4: {} + mlly@1.8.0: dependencies: acorn: 8.15.0 @@ -15166,6 +15650,9 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 + nan@2.24.0: + optional: true + nanoid@3.3.11: {} negotiator@0.6.3: {} @@ -15521,6 +16008,11 @@ snapshots: path-parse@1.0.7: {} + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + path-scurry@2.0.1: dependencies: lru-cache: 11.2.4 @@ -15684,10 +16176,14 @@ snapshots: prismjs@1.30.0: {} + process-nextick-args@2.0.1: {} + process-warning@4.0.1: {} process-warning@5.0.0: {} + process@0.11.10: {} + progress@2.0.3: {} prompts@2.4.2: @@ -15701,8 +16197,33 @@ snapshots: object-assign: 4.1.1 react-is: 19.2.3 + proper-lockfile@4.1.2: + dependencies: + graceful-fs: 4.2.11 + retry: 0.12.0 + signal-exit: 3.0.7 + + properties-reader@2.3.0: + dependencies: + mkdirp: 1.0.4 + property-information@7.1.0: {} + protobufjs@7.5.4: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 25.0.3 + long: 5.3.2 + proxy-agent@6.5.0: dependencies: agent-base: 7.1.4 @@ -15921,12 +16442,34 @@ snapshots: dependencies: pify: 2.3.0 + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + readable-stream@3.6.2: dependencies: inherits: 2.0.4 string_decoder: 1.3.0 util-deprecate: 1.0.2 + readable-stream@4.7.0: + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + + readdir-glob@1.1.3: + dependencies: + minimatch: 5.1.6 + readdirp@3.6.0: dependencies: picomatch: 2.3.1 @@ -16106,6 +16649,8 @@ snapshots: ret@0.5.0: {} + retry@0.12.0: {} + rettime@0.7.0: {} reusify@1.1.0: {} @@ -16152,6 +16697,8 @@ snapshots: has-symbols: 1.1.0 isarray: 2.0.5 + safe-buffer@5.1.2: {} + safe-buffer@5.2.1: {} safe-push-apply@1.0.0: @@ -16415,10 +16962,25 @@ snapshots: space-separated-tokens@2.0.2: {} + split-ca@1.0.1: {} + split2@4.2.0: {} sprintf-js@1.1.3: {} + ssh-remote-port-forward@1.0.4: + dependencies: + '@types/ssh2': 0.5.52 + ssh2: 1.17.0 + + ssh2@1.17.0: + dependencies: + asn1: 0.2.6 + bcrypt-pbkdf: 1.0.2 + optionalDependencies: + cpu-features: 0.0.10 + nan: 2.24.0 + stackback@0.0.2: {} standard-as-callback@2.1.0: {} @@ -16501,6 +17063,10 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + string_decoder@1.3.0: dependencies: safe-buffer: 5.2.1 @@ -16662,6 +17228,13 @@ snapshots: tailwindcss@4.1.18: {} + tar-fs@2.1.4: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.3 + tar-stream: 2.2.0 + tar-fs@3.1.1: dependencies: pump: 3.0.3 @@ -16674,6 +17247,14 @@ snapshots: - bare-buffer - react-native-b4a + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + tar-stream@3.1.7: dependencies: b4a: 1.7.3 @@ -16683,6 +17264,29 @@ snapshots: - bare-abort-controller - react-native-b4a + testcontainers@11.11.0: + dependencies: + '@balena/dockerignore': 1.0.2 + '@types/dockerode': 3.3.47 + archiver: 7.0.1 + async-lock: 1.4.1 + byline: 5.0.0 + debug: 4.4.3 + docker-compose: 1.3.0 + dockerode: 4.0.9 + get-port: 7.1.0 + proper-lockfile: 4.1.2 + properties-reader: 2.3.0 + ssh-remote-port-forward: 1.0.4 + tar-fs: 3.1.1 + tmp: 0.2.5 + undici: 7.16.0 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + - supports-color + text-decoder@1.2.3: dependencies: b4a: 1.7.3 @@ -16839,6 +17443,8 @@ snapshots: tween-functions@1.2.0: {} + tweetnacl@0.14.5: {} + type-fest@5.3.1: dependencies: tagged-tag: 1.0.0 @@ -16914,6 +17520,8 @@ snapshots: has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 + undici-types@5.26.5: {} + undici-types@6.21.0: {} undici-types@7.16.0: {} @@ -17299,6 +17907,12 @@ snapshots: yoctocolors@2.1.2: {} + zip-stream@6.0.1: + dependencies: + archiver-utils: 5.0.2 + compress-commons: 6.0.2 + readable-stream: 4.7.0 + zod@3.25.76: {} zod@4.3.5: {}