From c57b913cb53ba88938a4681de4ffbdbed7a55011 Mon Sep 17 00:00:00 2001 From: Richard R Date: Mon, 26 Jan 2026 17:01:36 -0700 Subject: [PATCH] feat(db/auth): migrate auth storage to Drizzle and add claimable data flow - replace custom DB adapter with Drizzle setup (SQLite/Postgres schemas) and add `drizzle.config.ts` plus migrations in `drizzle/` and `drizzle_pg/` - switch better-auth to drizzleAdapter, add auth helpers (`getAuthContext`, `requireAuthContext`, `requireAudiobookOwned`), and make `useAuth`/`useAuthSession` safe no-ops when auth is disabled - persist documents/audiobooks in DB with ownership checks, unclaimed fallback, and ref-counted deletes; add FS scan helper for no-auth mode - gate docx-to-pdf, library, voices, whisper, and migration endpoints behind auth when enabled - add unclaimed data scan/claim flow with `/api/user/claim`, `ClaimDataModal`, and server-side scan/claim helpers - refactor rate limiting and account deletion to use Drizzle tables (`user_tts_chars`, `user`) - run migrations via `scripts/migrate-if-auth.mjs` (auto on `pnpm start` + `pnpm migrate`), remove Docker entrypoint and old better-auth migration file - update README and lockfile for the new migration workflow and dependencies --- Dockerfile | 5 - README.md | 13 +- .../2026-01-24T20-06-28.748Z.sql | 13 - docker-entrypoint.sh | 11 - drizzle.config.ts | 14 + drizzle/0000_hard_mandarin.sql | 93 + drizzle/meta/0000_snapshot.json | 621 +++++++ drizzle/meta/_journal.json | 13 + drizzle_pg/0000_military_nemesis.sql | 94 + drizzle_pg/meta/0000_snapshot.json | 592 +++++++ drizzle_pg/meta/_journal.json | 13 + package.json | 10 +- pnpm-lock.yaml | 1506 +++++++---------- scripts/migrate-if-auth.mjs | 38 + src/app/api/account/delete/route.ts | 11 +- src/app/api/audiobook/chapter/route.ts | 47 +- src/app/api/audiobook/route.ts | 118 +- src/app/api/audiobook/status/route.ts | 22 +- src/app/api/documents/docx-to-pdf/route.ts | 11 +- .../api/documents/library/content/route.ts | 7 + src/app/api/documents/library/route.ts | 15 +- src/app/api/documents/route.ts | 291 ++-- src/app/api/migrations/v1/route.ts | 11 +- src/app/api/tts/voices/route.ts | 25 +- src/app/api/user/claim/route.ts | 32 + src/app/api/whisper/route.ts | 7 + src/app/layout.tsx | 2 + src/components/ClaimDataModal.tsx | 154 ++ src/components/auth/UserMenu.tsx | 15 +- src/db/index.ts | 42 + src/db/schema.ts | 14 + src/db/schema_postgres.ts | 92 + src/db/schema_sqlite.ts | 93 + src/hooks/useAuth.ts | 29 +- src/lib/server/auth.ts | 125 +- src/lib/server/claim-data.ts | 178 ++ src/lib/server/db-adapter.ts | 143 -- src/lib/server/db.ts | 45 - src/lib/server/docstore.ts | 105 +- src/lib/server/migration-manager.ts | 178 ++ src/lib/server/rate-limiter.ts | 199 +-- 41 files changed, 3556 insertions(+), 1491 deletions(-) delete mode 100644 better-auth_migrations/2026-01-24T20-06-28.748Z.sql delete mode 100755 docker-entrypoint.sh create mode 100644 drizzle.config.ts create mode 100644 drizzle/0000_hard_mandarin.sql create mode 100644 drizzle/meta/0000_snapshot.json create mode 100644 drizzle/meta/_journal.json create mode 100644 drizzle_pg/0000_military_nemesis.sql create mode 100644 drizzle_pg/meta/0000_snapshot.json create mode 100644 drizzle_pg/meta/_journal.json create mode 100644 scripts/migrate-if-auth.mjs create mode 100644 src/app/api/user/claim/route.ts create mode 100644 src/components/ClaimDataModal.tsx create mode 100644 src/db/index.ts create mode 100644 src/db/schema.ts create mode 100644 src/db/schema_postgres.ts create mode 100644 src/db/schema_sqlite.ts create mode 100644 src/lib/server/claim-data.ts delete mode 100644 src/lib/server/db-adapter.ts delete mode 100644 src/lib/server/db.ts create mode 100644 src/lib/server/migration-manager.ts diff --git a/Dockerfile b/Dockerfile index 94aef56..255275d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -65,9 +65,4 @@ ENV LD_LIBRARY_PATH=/opt/whisper.cpp/build EXPOSE 3003 # Start the application -# Copy entrypoint script -COPY docker-entrypoint.sh /usr/local/bin/ -RUN chmod +x /usr/local/bin/docker-entrypoint.sh - -ENTRYPOINT ["docker-entrypoint.sh"] CMD ["pnpm", "start"] diff --git a/README.md b/README.md index ed6dc30..047b37d 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ OpenReader WebUI is an open source text to speech document reader web app built > - `API_BASE` should point to your TTS API server's base URL (if running Kokoro-FastAPI locally in Docker, use `http://host.docker.internal:8880/v1`). > - `BETTER_AUTH_URL` should be your externally-facing URL for this app (for example `https://reader.example.com` or `http://localhost:3003`). > - To enable auth, set **both** `BETTER_AUTH_URL` and `BETTER_AUTH_SECRET` generated with `openssl rand -base64 32`. - > - If you set `POSTGRES_URL`, the container will not auto-run migrations for you; run `npx @better-auth/cli migrate -y` against your Postgres database. + > - If you set `POSTGRES_URL`, the container will attempt to run migrations against it. Ensure the database is accessible.
Docker environment variables (Click to expand) @@ -238,10 +238,13 @@ Optionally required for different features: > > Note: The base URL for the TTS API should be accessible and relative to the Next.js server -4. Run auth DB migrations (required when auth is enabled): - ```bash - npx @better-auth/cli migrate -y - ``` +4. Run auth DB migrations: + + - **Production / Docker**: Migrations run automatically on startup via `pnpm start`. + - **Development**: Run explicitly: + ```bash + pnpm migrate + ``` > Note: If you set `POSTGRES_URL` in `.env`, migrations will target Postgres instead of local SQLite. 5. Start the development server: diff --git a/better-auth_migrations/2026-01-24T20-06-28.748Z.sql b/better-auth_migrations/2026-01-24T20-06-28.748Z.sql deleted file mode 100644 index e57bdc3..0000000 --- a/better-auth_migrations/2026-01-24T20-06-28.748Z.sql +++ /dev/null @@ -1,13 +0,0 @@ -create table "user" ("id" text not null primary key, "name" text not null, "email" text not null unique, "emailVerified" boolean not null, "image" text, "createdAt" timestamptz default CURRENT_TIMESTAMP not null, "updatedAt" timestamptz default CURRENT_TIMESTAMP not null, "isAnonymous" boolean); - -create table "session" ("id" text not null primary key, "expiresAt" timestamptz not null, "token" text not null unique, "createdAt" timestamptz default CURRENT_TIMESTAMP not null, "updatedAt" timestamptz not null, "ipAddress" text, "userAgent" text, "userId" text not null references "user" ("id") on delete cascade); - -create table "account" ("id" text not null primary key, "accountId" text not null, "providerId" text not null, "userId" text not null references "user" ("id") on delete cascade, "accessToken" text, "refreshToken" text, "idToken" text, "accessTokenExpiresAt" timestamptz, "refreshTokenExpiresAt" timestamptz, "scope" text, "password" text, "createdAt" timestamptz default CURRENT_TIMESTAMP not null, "updatedAt" timestamptz not null); - -create table "verification" ("id" text not null primary key, "identifier" text not null, "value" text not null, "expiresAt" timestamptz not null, "createdAt" timestamptz default CURRENT_TIMESTAMP not null, "updatedAt" timestamptz default CURRENT_TIMESTAMP not null); - -create index "session_userId_idx" on "session" ("userId"); - -create index "account_userId_idx" on "account" ("userId"); - -create index "verification_identifier_idx" on "verification" ("identifier"); \ No newline at end of file diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh deleted file mode 100755 index c913a3f..0000000 --- a/docker-entrypoint.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh -set -e - -# Run migrations if we have the SQLite file path set or default -if [ -z "$POSTGRES_URL" ]; then - echo "Running SQLite migrations..." - npx @better-auth/cli migrate -y -fi - -# Start the application -exec "$@" diff --git a/drizzle.config.ts b/drizzle.config.ts new file mode 100644 index 0000000..47e21b7 --- /dev/null +++ b/drizzle.config.ts @@ -0,0 +1,14 @@ +import type { Config } from 'drizzle-kit'; +import * as dotenv from 'dotenv'; +dotenv.config(); + +const isPostgres = !!process.env.POSTGRES_URL; + +export default { + schema: './src/db/schema.ts', + out: isPostgres ? './drizzle_pg' : './drizzle', + dialect: isPostgres ? 'postgresql' : 'sqlite', + dbCredentials: { + url: process.env.POSTGRES_URL || 'docstore/sqlite3.db', + }, +} satisfies Config; diff --git a/drizzle/0000_hard_mandarin.sql b/drizzle/0000_hard_mandarin.sql new file mode 100644 index 0000000..fbea2df --- /dev/null +++ b/drizzle/0000_hard_mandarin.sql @@ -0,0 +1,93 @@ +CREATE TABLE `account` ( + `id` text PRIMARY KEY NOT NULL, + `account_id` text NOT NULL, + `provider_id` text NOT NULL, + `user_id` text NOT NULL, + `access_token` text, + `refresh_token` text, + `id_token` text, + `access_token_expires_at` integer, + `refresh_token_expires_at` integer, + `scope` text, + `password` text, + `created_at` integer NOT NULL, + `updated_at` integer NOT NULL, + FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +CREATE TABLE `audiobook_chapters` ( + `id` text PRIMARY KEY NOT NULL, + `book_id` text NOT NULL, + `user_id` text, + `chapter_index` integer NOT NULL, + `title` text NOT NULL, + `duration` real DEFAULT 0, + `file_path` text NOT NULL, + `format` text NOT NULL, + FOREIGN KEY (`book_id`) REFERENCES `audiobooks`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE TABLE `audiobooks` ( + `id` text PRIMARY KEY NOT NULL, + `user_id` text, + `title` text NOT NULL, + `author` text, + `description` text, + `cover_path` text, + `duration` real DEFAULT 0, + `created_at` integer DEFAULT (cast(strftime('%s','now') as int) * 1000) +); +--> statement-breakpoint +CREATE TABLE `documents` ( + `id` text, + `user_id` text NOT NULL, + `name` text NOT NULL, + `type` text NOT NULL, + `size` integer NOT NULL, + `last_modified` integer NOT NULL, + `file_path` text NOT NULL, + `created_at` integer DEFAULT (cast(strftime('%s','now') as int) * 1000), + PRIMARY KEY(`id`, `user_id`) +); +--> statement-breakpoint +CREATE TABLE `session` ( + `id` text PRIMARY KEY NOT NULL, + `expires_at` integer NOT NULL, + `token` text NOT NULL, + `created_at` integer NOT NULL, + `updated_at` integer NOT NULL, + `ip_address` text, + `user_agent` text, + `user_id` text NOT NULL, + FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +CREATE UNIQUE INDEX `session_token_unique` ON `session` (`token`);--> statement-breakpoint +CREATE TABLE `user` ( + `id` text PRIMARY KEY NOT NULL, + `name` text NOT NULL, + `email` text NOT NULL, + `email_verified` integer NOT NULL, + `image` text, + `created_at` integer NOT NULL, + `updated_at` integer NOT NULL, + `is_anonymous` integer +); +--> statement-breakpoint +CREATE UNIQUE INDEX `user_email_unique` ON `user` (`email`);--> statement-breakpoint +CREATE TABLE `user_tts_chars` ( + `user_id` text NOT NULL, + `date` text NOT NULL, + `char_count` integer DEFAULT 0, + `created_at` integer DEFAULT (cast(strftime('%s','now') as int) * 1000), + `updated_at` integer DEFAULT (cast(strftime('%s','now') as int) * 1000), + PRIMARY KEY(`user_id`, `date`) +); +--> statement-breakpoint +CREATE INDEX `idx_user_tts_chars_date` ON `user_tts_chars` (`date`);--> statement-breakpoint +CREATE TABLE `verification` ( + `id` text PRIMARY KEY NOT NULL, + `identifier` text NOT NULL, + `value` text NOT NULL, + `updated_at` integer +); diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json new file mode 100644 index 0000000..04103cf --- /dev/null +++ b/drizzle/meta/0000_snapshot.json @@ -0,0 +1,621 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "f5c06478-a702-4809-ab9b-3a63f11b8b0c", + "prevId": "00000000-0000-0000-0000-000000000000", + "tables": { + "account": { + "name": "account", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "audiobook_chapters": { + "name": "audiobook_chapters", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "book_id": { + "name": "book_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "chapter_index": { + "name": "chapter_index", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "duration": { + "name": "duration", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "file_path": { + "name": "file_path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "audiobook_chapters_book_id_audiobooks_id_fk": { + "name": "audiobook_chapters_book_id_audiobooks_id_fk", + "tableFrom": "audiobook_chapters", + "tableTo": "audiobooks", + "columnsFrom": [ + "book_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "audiobooks": { + "name": "audiobooks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "author": { + "name": "author", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cover_path": { + "name": "cover_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "duration": { + "name": "duration", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(cast(strftime('%s','now') as int) * 1000)" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "documents": { + "name": "documents", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_modified": { + "name": "last_modified", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "file_path": { + "name": "file_path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(cast(strftime('%s','now') as int) * 1000)" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "documents_id_user_id_pk": { + "columns": [ + "id", + "user_id" + ], + "name": "documents_id_user_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "session_token_unique": { + "name": "session_token_unique", + "columns": [ + "token" + ], + "isUnique": true + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email_verified": { + "name": "email_verified", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_anonymous": { + "name": "is_anonymous", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_tts_chars": { + "name": "user_tts_chars", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "char_count": { + "name": "char_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(cast(strftime('%s','now') as int) * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(cast(strftime('%s','now') as int) * 1000)" + } + }, + "indexes": { + "idx_user_tts_chars_date": { + "name": "idx_user_tts_chars_date", + "columns": [ + "date" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "user_tts_chars_user_id_date_pk": { + "columns": [ + "user_id", + "date" + ], + "name": "user_tts_chars_user_id_date_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "verification": { + "name": "verification", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json new file mode 100644 index 0000000..68e333c --- /dev/null +++ b/drizzle/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "sqlite", + "entries": [ + { + "idx": 0, + "version": "6", + "when": 1769395269830, + "tag": "0000_hard_mandarin", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/drizzle_pg/0000_military_nemesis.sql b/drizzle_pg/0000_military_nemesis.sql new file mode 100644 index 0000000..824aaff --- /dev/null +++ b/drizzle_pg/0000_military_nemesis.sql @@ -0,0 +1,94 @@ +CREATE TABLE "account" ( + "id" text PRIMARY KEY NOT NULL, + "account_id" text NOT NULL, + "provider_id" text NOT NULL, + "user_id" text NOT NULL, + "access_token" text, + "refresh_token" text, + "id_token" text, + "access_token_expires_at" timestamp, + "refresh_token_expires_at" timestamp, + "scope" text, + "password" text, + "created_at" timestamp NOT NULL, + "updated_at" timestamp NOT NULL +); +--> statement-breakpoint +CREATE TABLE "audiobook_chapters" ( + "id" text PRIMARY KEY NOT NULL, + "book_id" text NOT NULL, + "user_id" text, + "chapter_index" integer NOT NULL, + "title" text NOT NULL, + "duration" real DEFAULT 0, + "file_path" text NOT NULL, + "format" text NOT NULL +); +--> statement-breakpoint +CREATE TABLE "audiobooks" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" text, + "title" text NOT NULL, + "author" text, + "description" text, + "cover_path" text, + "duration" real DEFAULT 0, + "created_at" timestamp DEFAULT now() +); +--> statement-breakpoint +CREATE TABLE "documents" ( + "id" text, + "user_id" text NOT NULL, + "name" text NOT NULL, + "type" text NOT NULL, + "size" bigint NOT NULL, + "last_modified" bigint NOT NULL, + "file_path" text NOT NULL, + "created_at" timestamp DEFAULT now(), + CONSTRAINT "documents_id_user_id_pk" PRIMARY KEY("id","user_id") +); +--> statement-breakpoint +CREATE TABLE "session" ( + "id" text PRIMARY KEY NOT NULL, + "expires_at" timestamp NOT NULL, + "token" text NOT NULL, + "created_at" timestamp NOT NULL, + "updated_at" timestamp NOT NULL, + "ip_address" text, + "user_agent" text, + "user_id" text NOT NULL, + CONSTRAINT "session_token_unique" UNIQUE("token") +); +--> statement-breakpoint +CREATE TABLE "user" ( + "id" text PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "email" text NOT NULL, + "email_verified" boolean NOT NULL, + "image" text, + "created_at" timestamp NOT NULL, + "updated_at" timestamp NOT NULL, + "is_anonymous" boolean, + CONSTRAINT "user_email_unique" UNIQUE("email") +); +--> statement-breakpoint +CREATE TABLE "user_tts_chars" ( + "user_id" text NOT NULL, + "date" date NOT NULL, + "char_count" bigint DEFAULT 0, + "created_at" timestamp DEFAULT now(), + "updated_at" timestamp DEFAULT now(), + CONSTRAINT "user_tts_chars_user_id_date_pk" PRIMARY KEY("user_id","date") +); +--> statement-breakpoint +CREATE TABLE "verification" ( + "id" text PRIMARY KEY NOT NULL, + "identifier" text NOT NULL, + "value" text NOT NULL, + "updated_at" timestamp +); +--> statement-breakpoint +ALTER TABLE "account" ADD CONSTRAINT "account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "audiobook_chapters" ADD CONSTRAINT "audiobook_chapters_book_id_audiobooks_id_fk" FOREIGN KEY ("book_id") REFERENCES "public"."audiobooks"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "idx_user_tts_chars_date" ON "user_tts_chars" USING btree ("date"); \ No newline at end of file diff --git a/drizzle_pg/meta/0000_snapshot.json b/drizzle_pg/meta/0000_snapshot.json new file mode 100644 index 0000000..5f7ff78 --- /dev/null +++ b/drizzle_pg/meta/0000_snapshot.json @@ -0,0 +1,592 @@ +{ + "id": "5ac6552c-824b-4b79-8cb6-f0ec3d121031", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audiobook_chapters": { + "name": "audiobook_chapters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "chapter_index": { + "name": "chapter_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "duration": { + "name": "duration", + "type": "real", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "file_path": { + "name": "file_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "audiobook_chapters_book_id_audiobooks_id_fk": { + "name": "audiobook_chapters_book_id_audiobooks_id_fk", + "tableFrom": "audiobook_chapters", + "tableTo": "audiobooks", + "columnsFrom": [ + "book_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audiobooks": { + "name": "audiobooks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author": { + "name": "author", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cover_path": { + "name": "cover_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "duration": { + "name": "duration", + "type": "real", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.documents": { + "name": "documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "last_modified": { + "name": "last_modified", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "file_path": { + "name": "file_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "documents_id_user_id_pk": { + "name": "documents_id_user_id_pk", + "columns": [ + "id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "is_anonymous": { + "name": "is_anonymous", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_tts_chars": { + "name": "user_tts_chars", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "char_count": { + "name": "char_count", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_user_tts_chars_date": { + "name": "idx_user_tts_chars_date", + "columns": [ + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "user_tts_chars_user_id_date_pk": { + "name": "user_tts_chars_user_id_date_pk", + "columns": [ + "user_id", + "date" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle_pg/meta/_journal.json b/drizzle_pg/meta/_journal.json new file mode 100644 index 0000000..c9b3a38 --- /dev/null +++ b/drizzle_pg/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1769393893675, + "tag": "0000_military_nemesis", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/package.json b/package.json index fa3a5f2..5c6c0e1 100644 --- a/package.json +++ b/package.json @@ -5,12 +5,13 @@ "scripts": { "dev": "next dev --turbopack -p 3003", "build": "next build", - "start": "next start -p 3003", + "start": "node scripts/migrate-if-auth.mjs && next start -p 3003", "lint": "next lint", - "test": "playwright test" + "test": "playwright test", + "migrate": "node scripts/migrate-if-auth.mjs", + "migrate:force": "drizzle-kit migrate" }, "dependencies": { - "@better-auth/cli": "1.4.17", "@headlessui/react": "^2.2.9", "@types/howler": "^2.2.12", "@types/uuid": "^10.0.0", @@ -22,6 +23,9 @@ "core-js": "^3.48.0", "dexie": "^4.2.1", "dexie-react-hooks": "^4.2.0", + "dotenv": "^17.2.3", + "drizzle-kit": "^0.31.8", + "drizzle-orm": "^0.45.1", "epubjs": "^0.3.93", "howler": "^2.2.4", "lru-cache": "^11.2.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 32800bb..8cc42a0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,9 +13,6 @@ importers: .: dependencies: - '@better-auth/cli': - specifier: 1.4.17 - version: 1.4.17(@better-fetch/fetch@1.1.21)(@types/better-sqlite3@7.6.13)(better-call@1.1.8(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0)(next@15.5.9(@babel/core@7.28.6)(@playwright/test@1.58.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@headlessui/react': specifier: ^2.2.9 version: 2.2.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -27,10 +24,10 @@ importers: version: 10.0.0 '@vercel/analytics': specifier: ^1.6.1 - version: 1.6.1(next@15.5.9(@babel/core@7.28.6)(@playwright/test@1.58.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3) + version: 1.6.1(next@15.5.9(@playwright/test@1.58.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3) better-auth: specifier: ^1.4.17 - version: 1.4.17(@prisma/client@5.22.0)(better-sqlite3@12.6.2)(drizzle-orm@0.41.0(@prisma/client@5.22.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.10)(pg@8.17.2))(next@15.5.9(@babel/core@7.28.6)(@playwright/test@1.58.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(pg@8.17.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 1.4.17(@prisma/client@5.22.0)(better-sqlite3@12.6.2)(drizzle-kit@0.31.8)(drizzle-orm@0.45.1(@prisma/client@5.22.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.10)(pg@8.17.2))(next@15.5.9(@playwright/test@1.58.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(pg@8.17.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) better-sqlite3: specifier: ^12.6.2 version: 12.6.2 @@ -49,6 +46,15 @@ importers: dexie-react-hooks: specifier: ^4.2.0 version: 4.2.0(@types/react@19.2.9)(dexie@4.2.1)(react@19.2.3) + dotenv: + specifier: ^17.2.3 + version: 17.2.3 + drizzle-kit: + specifier: ^0.31.8 + version: 0.31.8 + drizzle-orm: + specifier: ^0.45.1 + version: 0.45.1(@prisma/client@5.22.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.10)(pg@8.17.2) epubjs: specifier: ^0.3.93 version: 0.3.93 @@ -60,7 +66,7 @@ importers: version: 11.2.4 next: specifier: ^15.5.9 - version: 15.5.9(@babel/core@7.28.6)(@playwright/test@1.58.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 15.5.9(@playwright/test@1.58.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) openai: specifier: ^6.16.0 version: 6.16.0(zod@4.3.6) @@ -150,173 +156,10 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} - '@babel/code-frame@7.28.6': - resolution: {integrity: sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==} - engines: {node: '>=6.9.0'} - - '@babel/compat-data@7.28.6': - resolution: {integrity: sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==} - engines: {node: '>=6.9.0'} - - '@babel/core@7.28.6': - resolution: {integrity: sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==} - engines: {node: '>=6.9.0'} - - '@babel/generator@7.28.6': - resolution: {integrity: sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-annotate-as-pure@7.27.3': - resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-compilation-targets@7.28.6': - resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-create-class-features-plugin@7.28.6': - resolution: {integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-globals@7.28.0': - resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-member-expression-to-functions@7.28.5': - resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-imports@7.28.6': - resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-transforms@7.28.6': - resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-optimise-call-expression@7.27.1': - resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-plugin-utils@7.28.6': - resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} - engines: {node: '>=6.9.0'} - - '@babel/helper-replace-supers@7.28.6': - resolution: {integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-skip-transparent-expression-wrappers@7.27.1': - resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.28.5': - resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-option@7.27.1': - resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.28.6': - resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.28.6': - resolution: {integrity: sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/plugin-syntax-jsx@7.28.6': - resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-typescript@7.28.6': - resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-modules-commonjs@7.28.6': - resolution: {integrity: sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-react-display-name@7.28.0': - resolution: {integrity: sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-react-jsx-development@7.27.1': - resolution: {integrity: sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-react-jsx@7.28.6': - resolution: {integrity: sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-react-pure-annotations@7.27.1': - resolution: {integrity: sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-typescript@7.28.6': - resolution: {integrity: sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/preset-react@7.28.5': - resolution: {integrity: sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/preset-typescript@7.28.5': - resolution: {integrity: sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/runtime@7.28.6': resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} engines: {node: '>=6.9.0'} - '@babel/template@7.28.6': - resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} - engines: {node: '>=6.9.0'} - - '@babel/traverse@7.28.6': - resolution: {integrity: sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.28.6': - resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==} - engines: {node: '>=6.9.0'} - - '@better-auth/cli@1.4.17': - resolution: {integrity: sha512-GT0epRZCRAxwDnDNVsAVXx2W4PNjBfm8NwrtjxfYKRHsnrCHkT0N/cZyKCYG1sReN/wlsyYk8bJ6f1N1P4VT8w==} - hasBin: true - '@better-auth/core@1.4.17': resolution: {integrity: sha512-WSaEQDdUO6B1CzAmissN6j0lx9fM9lcslEYzlApB5UzFaBeAOHNUONTdglSyUs6/idiZBoRvt0t/qMXCgIU8ug==} peerDependencies: @@ -338,23 +181,8 @@ packages: '@better-fetch/fetch@1.1.21': resolution: {integrity: sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A==} - '@chevrotain/cst-dts-gen@10.5.0': - resolution: {integrity: sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw==} - - '@chevrotain/gast@10.5.0': - resolution: {integrity: sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A==} - - '@chevrotain/types@10.5.0': - resolution: {integrity: sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A==} - - '@chevrotain/utils@10.5.0': - resolution: {integrity: sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==} - - '@clack/core@0.5.0': - resolution: {integrity: sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==} - - '@clack/prompts@0.11.0': - resolution: {integrity: sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw==} + '@drizzle-team/brocli@0.10.2': + resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} '@emnapi/core@1.8.1': resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} @@ -365,6 +193,302 @@ packages: '@emnapi/wasi-threads@1.1.0': resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + '@esbuild-kit/core-utils@3.3.2': + resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} + deprecated: 'Merged into tsx: https://tsx.is' + + '@esbuild-kit/esm-loader@2.6.5': + resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==} + deprecated: 'Merged into tsx: https://tsx.is' + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.18.20': + resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.18.20': + resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.18.20': + resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.18.20': + resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.18.20': + resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.18.20': + resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.18.20': + resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.18.20': + resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.18.20': + resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.18.20': + resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.18.20': + resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.18.20': + resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.18.20': + resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.18.20': + resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.18.20': + resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.18.20': + resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.18.20': + resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.18.20': + resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.18.20': + resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.18.20': + resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.18.20': + resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.18.20': + resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.9.1': resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -587,9 +711,6 @@ packages: '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - '@jridgewell/remapping@2.3.5': - resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} - '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} @@ -600,10 +721,6 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@mrleebo/prisma-ast@0.13.1': - resolution: {integrity: sha512-XyroGQXcHrZdvmrGJvsA9KNeOOgGMg1Vg9OlheUsBOSKznLMDl+YChxbkboRHvtFYJEMRYmlV3uoo/njCw05iw==} - engines: {node: '>=16'} - '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} @@ -1115,10 +1232,6 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.9.18: - resolution: {integrity: sha512-e23vBV1ZLfjb9apvfPk4rHVu2ry6RIr2Wfs+O324okSidrX7pTAnEJPCh/O5BtRlr7QtZI7ktOP3vsqr7Z5XoA==} - hasBin: true - better-auth@1.4.17: resolution: {integrity: sha512-VmHGQyKsEahkEs37qguROKg/6ypYpNF13D7v/lkbO7w7Aivz0Bv2h+VyUkH4NzrGY0QBKXi1577mGhDCVwp0ew==} peerDependencies: @@ -1213,26 +1326,12 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.28.1: - resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - bundle-name@4.1.0: - resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} - engines: {node: '>=18'} - - c12@3.3.3: - resolution: {integrity: sha512-750hTRvgBy5kcMNPdh95Qo+XUBeGo8C7nsKSmedDmaQI+E0r82DwHeM6vBewDe4rGFbnxoa4V9pw+sPh5+Iz8Q==} - peerDependencies: - magicast: '*' - peerDependenciesMeta: - magicast: - optional: true - call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -1267,10 +1366,6 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - chalk@5.6.2: - resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - character-entities-html4@2.1.0: resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} @@ -1283,26 +1378,13 @@ packages: character-reference-invalid@2.0.1: resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} - chevrotain@10.5.0: - resolution: {integrity: sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==} - chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} - chokidar@5.0.0: - resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} - engines: {node: '>= 20.19.0'} - chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} - citty@0.1.6: - resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} - - citty@0.2.0: - resolution: {integrity: sha512-8csy5IBFI2ex2hTVpaHN2j+LNE199AgiI7y4dMintrr8i0lQiFn+0AWMZrWdHKIgMOer65f8IThysYhoReqjWA==} - client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} @@ -1324,10 +1406,6 @@ packages: comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} - commander@12.1.0: - resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} - engines: {node: '>=18'} - commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -1339,16 +1417,6 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - confbox@0.2.2: - resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} - - consola@3.4.2: - resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} - engines: {node: ^14.18.0 || >=16.10.0} - - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - core-js@3.48.0: resolution: {integrity: sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==} @@ -1417,22 +1485,10 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - default-browser-id@5.0.1: - resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} - engines: {node: '>=18'} - - default-browser@5.4.0: - resolution: {integrity: sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==} - engines: {node: '>=18'} - define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} - define-lazy-prop@3.0.0: - resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} - engines: {node: '>=12'} - define-properties@1.2.1: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} @@ -1444,9 +1500,6 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} - destr@2.0.5: - resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} - detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -1481,8 +1534,12 @@ packages: resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} engines: {node: '>=12'} - drizzle-orm@0.41.0: - resolution: {integrity: sha512-7A4ZxhHk9gdlXmTdPj/lREtP+3u8KvZ4yEN6MYVxBzZGex5Wtdc+CWSbu7btgF6TB0N+MNPrvW7RKBbxJchs/Q==} + drizzle-kit@0.31.8: + resolution: {integrity: sha512-O9EC/miwdnRDY10qRxM8P3Pg8hXe3LyU4ZipReKOgTwn4OqANmftj8XJz1UPUAS6NMHf0E2htjsbQujUTkncCg==} + hasBin: true + + drizzle-orm@0.45.1: + resolution: {integrity: sha512-Te0FOdKIistGNPMq2jscdqngBRfBpC8uMFVwqjf6gtTVJHIQ/dosgV/CLBU2N4ZJBsXL5savCba9b0YJskKdcA==} peerDependencies: '@aws-sdk/client-rds-data': '>=3' '@cloudflare/workers-types': '>=4' @@ -1492,12 +1549,13 @@ packages: '@neondatabase/serverless': '>=0.10.0' '@op-engineering/op-sqlite': '>=2' '@opentelemetry/api': ^1.4.1 - '@planetscale/database': '>=1' + '@planetscale/database': '>=1.13' '@prisma/client': '*' '@tidbcloud/serverless': '*' '@types/better-sqlite3': '*' '@types/pg': '*' '@types/sql.js': '*' + '@upstash/redis': '>=1.34.7' '@vercel/postgres': '>=0.8.0' '@xata.io/client': '*' better-sqlite3: '>=7' @@ -1541,6 +1599,8 @@ packages: optional: true '@types/sql.js': optional: true + '@upstash/redis': + optional: true '@vercel/postgres': optional: true '@xata.io/client': @@ -1578,9 +1638,6 @@ packages: resolution: {integrity: sha512-/RInbCy1d4P6Zdfa+TMVsf/ufZVotat5hCw3QXmWtjU+3pFEOvOQ7ibo3aIxyCJw2leIeAMjmPj+1SLJiCpdrQ==} engines: {node: '>=12.0.0'} - electron-to-chromium@1.5.278: - resolution: {integrity: sha512-dQ0tM1svDRQOwxnXxm+twlGTjr9Upvt8UFWAgmLsxEzFQxhbti4VwxmMjsDxVC51Zo84swW7FVCXEV+VAkhuPw==} - emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} @@ -1636,9 +1693,20 @@ packages: resolution: {integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==} engines: {node: '>=0.12'} - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} + esbuild-register@3.6.0: + resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==} + peerDependencies: + esbuild: '>=0.12 <1' + + esbuild@0.18.20: + resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} @@ -1778,9 +1846,6 @@ packages: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} - exsolve@1.0.8: - resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} - ext@1.7.0: resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} @@ -1873,10 +1938,6 @@ packages: resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} engines: {node: '>= 0.4'} - gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -1892,10 +1953,6 @@ packages: get-tsconfig@4.13.0: resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} - giget@2.0.0: - resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} - hasBin: true - github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} @@ -2053,11 +2110,6 @@ packages: is-decimal@2.0.1: resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} - is-docker@3.0.0: - resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - hasBin: true - is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -2077,11 +2129,6 @@ packages: is-hexadecimal@2.0.1: resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} - is-inside-container@1.0.0: - resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} - engines: {node: '>=14.16'} - hasBin: true - is-map@2.0.3: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} @@ -2138,10 +2185,6 @@ packages: resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} engines: {node: '>= 0.4'} - is-wsl@3.1.0: - resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} - engines: {node: '>=16'} - isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} @@ -2159,10 +2202,6 @@ packages: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true - jiti@2.6.1: - resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} - hasBin: true - jose@6.1.3: resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} @@ -2173,11 +2212,6 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true - jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} - hasBin: true - json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -2191,11 +2225,6 @@ packages: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} @@ -2206,10 +2235,6 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - kleur@3.0.3: - resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} - engines: {node: '>=6'} - kysely@0.28.10: resolution: {integrity: sha512-ksNxfzIW77OcZ+QWSAPC7yDqUSaIVwkTWnTPNiIy//vifNbwsSgQ57OkkncHxxpcBHM3LRfLAZVEh7kjq5twVA==} engines: {node: '>=20.0.0'} @@ -2231,10 +2256,6 @@ packages: lie@3.3.0: resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} - lilconfig@2.1.0: - resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} - engines: {node: '>=10'} - lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -2266,9 +2287,6 @@ packages: resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==} engines: {node: 20 || >=22} - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - make-cancellable-promise@1.3.2: resolution: {integrity: sha512-GCXh3bq/WuMbS+Ky4JBPW1hYTOU+znU+Q5m9Pu+pI8EoUqIHk9+tviOKC6/qhHh8C4/As3tzJ69IF32kdz85ww==} @@ -2504,21 +2522,10 @@ packages: node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} - node-fetch-native@1.6.7: - resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} - - node-releases@2.0.27: - resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} - normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - nypm@0.6.4: - resolution: {integrity: sha512-1TvCKjZyyklN+JJj2TS3P4uSQEInrM/HkkuSXsEzm1ApPgBffOn8gFguNnZf07r/1X6vlryfIqMUkJKQMzlZiw==} - engines: {node: '>=18'} - hasBin: true - object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -2555,16 +2562,9 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} - ohash@2.0.11: - resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} - once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - open@10.2.0: - resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} - engines: {node: '>=18'} - openai@6.16.0: resolution: {integrity: sha512-fZ1uBqjFUjXzbGc35fFtYKEOxd20kd9fDpFeqWtsOZWiubY8CZ1NAlXHW3iathaFvqmNtCWMIsosCuyeI7Joxg==} hasBin: true @@ -2621,16 +2621,10 @@ packages: resolution: {integrity: sha512-+vnG6S4dYcYxZd+CZxzXCNKdELYZSKfohrk98yajCo1PtRoDgCTrrwOvK1GT0UoAdVszagDVllQc0U1vaX4NUQ==} engines: {node: '>=6'} - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - pdfjs-dist@4.8.69: resolution: {integrity: sha512-IHZsA4T7YElCKNNXtiLgqScw4zPd3pG9do8UrznC757gMd7UPeHSL2qwNNMJo4r79fl8oj1Xx+1nh2YkzdMpLQ==} engines: {node: '>=18'} - perfect-debounce@2.1.0: - resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==} - pg-cloudflare@1.3.0: resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==} @@ -2684,9 +2678,6 @@ packages: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} - pkg-types@2.3.0: - resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} - playwright-core@1.58.0: resolution: {integrity: sha512-aaoB1RWrdNi3//rOeKuMiS65UCcgOVljU46At6eFcOFPFHWtd2weHRRow6z/n+Lec0Lvu0k9ZPKJSjPugikirw==} engines: {node: '>=18'} @@ -2781,18 +2772,9 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prettier@3.8.1: - resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} - engines: {node: '>=14'} - hasBin: true - process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - prompts@2.4.2: - resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} - engines: {node: '>= 6'} - prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -2809,9 +2791,6 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - rc9@2.1.2: - resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} - rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true @@ -2897,10 +2876,6 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} - readdirp@5.0.0: - resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} - engines: {node: '>= 20.19.0'} - redux@4.2.1: resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==} @@ -2908,9 +2883,6 @@ packages: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} - regexp-to-ast@0.5.0: - resolution: {integrity: sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==} - regexp.prototype.flags@1.5.4: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} @@ -2950,10 +2922,6 @@ packages: rou3@0.7.12: resolution: {integrity: sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==} - run-applescript@7.1.0: - resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} - engines: {node: '>=18'} - run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -3039,13 +3007,17 @@ packages: simple-get@4.0.1: resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} - sisteransi@1.0.5: - resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} @@ -3164,10 +3136,6 @@ packages: tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} - tinyexec@1.0.2: - resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} - engines: {node: '>=18'} - tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} @@ -3256,12 +3224,6 @@ packages: unrs-resolver@1.11.1: resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} - update-browserslist-db@1.2.3: - resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -3314,29 +3276,14 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - wsl-utils@0.1.0: - resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} - engines: {node: '>=18'} - xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} - yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - yocto-spinner@0.2.3: - resolution: {integrity: sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ==} - engines: {node: '>=18.19'} - - yoctocolors@2.1.2: - resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} - engines: {node: '>=18'} - zod@4.3.6: resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} @@ -3347,307 +3294,8 @@ snapshots: '@alloc/quick-lru@5.2.0': {} - '@babel/code-frame@7.28.6': - dependencies: - '@babel/helper-validator-identifier': 7.28.5 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/compat-data@7.28.6': {} - - '@babel/core@7.28.6': - dependencies: - '@babel/code-frame': 7.28.6 - '@babel/generator': 7.28.6 - '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) - '@babel/helpers': 7.28.6 - '@babel/parser': 7.28.6 - '@babel/template': 7.28.6 - '@babel/traverse': 7.28.6 - '@babel/types': 7.28.6 - '@jridgewell/remapping': 2.3.5 - convert-source-map: 2.0.0 - debug: 4.4.3 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/generator@7.28.6': - dependencies: - '@babel/parser': 7.28.6 - '@babel/types': 7.28.6 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.1.0 - - '@babel/helper-annotate-as-pure@7.27.3': - dependencies: - '@babel/types': 7.28.6 - - '@babel/helper-compilation-targets@7.28.6': - dependencies: - '@babel/compat-data': 7.28.6 - '@babel/helper-validator-option': 7.27.1 - browserslist: 4.28.1 - lru-cache: 5.1.1 - semver: 6.3.1 - - '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.28.6)': - dependencies: - '@babel/core': 7.28.6 - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-member-expression-to-functions': 7.28.5 - '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/helper-replace-supers': 7.28.6(@babel/core@7.28.6) - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/traverse': 7.28.6 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/helper-globals@7.28.0': {} - - '@babel/helper-member-expression-to-functions@7.28.5': - dependencies: - '@babel/traverse': 7.28.6 - '@babel/types': 7.28.6 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-imports@7.28.6': - dependencies: - '@babel/traverse': 7.28.6 - '@babel/types': 7.28.6 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.28.6(@babel/core@7.28.6)': - dependencies: - '@babel/core': 7.28.6 - '@babel/helper-module-imports': 7.28.6 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.28.6 - transitivePeerDependencies: - - supports-color - - '@babel/helper-optimise-call-expression@7.27.1': - dependencies: - '@babel/types': 7.28.6 - - '@babel/helper-plugin-utils@7.28.6': {} - - '@babel/helper-replace-supers@7.28.6(@babel/core@7.28.6)': - dependencies: - '@babel/core': 7.28.6 - '@babel/helper-member-expression-to-functions': 7.28.5 - '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.28.6 - transitivePeerDependencies: - - supports-color - - '@babel/helper-skip-transparent-expression-wrappers@7.27.1': - dependencies: - '@babel/traverse': 7.28.6 - '@babel/types': 7.28.6 - transitivePeerDependencies: - - supports-color - - '@babel/helper-string-parser@7.27.1': {} - - '@babel/helper-validator-identifier@7.28.5': {} - - '@babel/helper-validator-option@7.27.1': {} - - '@babel/helpers@7.28.6': - dependencies: - '@babel/template': 7.28.6 - '@babel/types': 7.28.6 - - '@babel/parser@7.28.6': - dependencies: - '@babel/types': 7.28.6 - - '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.28.6)': - dependencies: - '@babel/core': 7.28.6 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.28.6)': - dependencies: - '@babel/core': 7.28.6 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.28.6)': - dependencies: - '@babel/core': 7.28.6 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) - '@babel/helper-plugin-utils': 7.28.6 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-react-display-name@7.28.0(@babel/core@7.28.6)': - dependencies: - '@babel/core': 7.28.6 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.28.6)': - dependencies: - '@babel/core': 7.28.6 - '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.28.6) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-react-jsx@7.28.6(@babel/core@7.28.6)': - dependencies: - '@babel/core': 7.28.6 - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-module-imports': 7.28.6 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.28.6) - '@babel/types': 7.28.6 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.28.6)': - dependencies: - '@babel/core': 7.28.6 - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.28.6)': - dependencies: - '@babel/core': 7.28.6 - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.6) - '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.28.6) - transitivePeerDependencies: - - supports-color - - '@babel/preset-react@7.28.5(@babel/core@7.28.6)': - dependencies: - '@babel/core': 7.28.6 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.28.6) - '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.28.6) - '@babel/plugin-transform-react-pure-annotations': 7.27.1(@babel/core@7.28.6) - transitivePeerDependencies: - - supports-color - - '@babel/preset-typescript@7.28.5(@babel/core@7.28.6)': - dependencies: - '@babel/core': 7.28.6 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.28.6) - transitivePeerDependencies: - - supports-color - '@babel/runtime@7.28.6': {} - '@babel/template@7.28.6': - dependencies: - '@babel/code-frame': 7.28.6 - '@babel/parser': 7.28.6 - '@babel/types': 7.28.6 - - '@babel/traverse@7.28.6': - dependencies: - '@babel/code-frame': 7.28.6 - '@babel/generator': 7.28.6 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.6 - '@babel/template': 7.28.6 - '@babel/types': 7.28.6 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - '@babel/types@7.28.6': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - - '@better-auth/cli@1.4.17(@better-fetch/fetch@1.1.21)(@types/better-sqlite3@7.6.13)(better-call@1.1.8(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0)(next@15.5.9(@babel/core@7.28.6)(@playwright/test@1.58.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': - dependencies: - '@babel/core': 7.28.6 - '@babel/preset-react': 7.28.5(@babel/core@7.28.6) - '@babel/preset-typescript': 7.28.5(@babel/core@7.28.6) - '@better-auth/core': 1.4.17(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) - '@better-auth/telemetry': 1.4.17(@better-auth/core@1.4.17(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0)) - '@better-auth/utils': 0.3.0 - '@clack/prompts': 0.11.0 - '@mrleebo/prisma-ast': 0.13.1 - '@prisma/client': 5.22.0 - '@types/pg': 8.16.0 - better-auth: 1.4.17(@prisma/client@5.22.0)(better-sqlite3@12.6.2)(drizzle-orm@0.41.0(@prisma/client@5.22.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.10)(pg@8.17.2))(next@15.5.9(@babel/core@7.28.6)(@playwright/test@1.58.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(pg@8.17.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - better-sqlite3: 12.6.2 - c12: 3.3.3 - chalk: 5.6.2 - commander: 12.1.0 - dotenv: 17.2.3 - drizzle-orm: 0.41.0(@prisma/client@5.22.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.10)(pg@8.17.2) - open: 10.2.0 - pg: 8.17.2 - prettier: 3.8.1 - prompts: 2.4.2 - semver: 7.7.3 - yocto-spinner: 0.2.3 - zod: 4.3.6 - transitivePeerDependencies: - - '@aws-sdk/client-rds-data' - - '@better-fetch/fetch' - - '@cloudflare/workers-types' - - '@electric-sql/pglite' - - '@libsql/client' - - '@libsql/client-wasm' - - '@lynx-js/react' - - '@neondatabase/serverless' - - '@op-engineering/op-sqlite' - - '@opentelemetry/api' - - '@planetscale/database' - - '@sveltejs/kit' - - '@tanstack/react-start' - - '@tanstack/solid-start' - - '@tidbcloud/serverless' - - '@types/better-sqlite3' - - '@types/sql.js' - - '@vercel/postgres' - - '@xata.io/client' - - better-call - - bun-types - - drizzle-kit - - expo-sqlite - - gel - - jose - - knex - - kysely - - magicast - - mongodb - - mysql2 - - nanostores - - next - - pg-native - - postgres - - prisma - - react - - react-dom - - solid-js - - sql.js - - sqlite3 - - supports-color - - svelte - - vitest - - vue - '@better-auth/core@1.4.17(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0)': dependencies: '@better-auth/utils': 0.3.0 @@ -3669,31 +3317,7 @@ snapshots: '@better-fetch/fetch@1.1.21': {} - '@chevrotain/cst-dts-gen@10.5.0': - dependencies: - '@chevrotain/gast': 10.5.0 - '@chevrotain/types': 10.5.0 - lodash: 4.17.23 - - '@chevrotain/gast@10.5.0': - dependencies: - '@chevrotain/types': 10.5.0 - lodash: 4.17.23 - - '@chevrotain/types@10.5.0': {} - - '@chevrotain/utils@10.5.0': {} - - '@clack/core@0.5.0': - dependencies: - picocolors: 1.1.1 - sisteransi: 1.0.5 - - '@clack/prompts@0.11.0': - dependencies: - '@clack/core': 0.5.0 - picocolors: 1.1.1 - sisteransi: 1.0.5 + '@drizzle-team/brocli@0.10.2': {} '@emnapi/core@1.8.1': dependencies: @@ -3711,6 +3335,160 @@ snapshots: tslib: 2.8.1 optional: true + '@esbuild-kit/core-utils@3.3.2': + dependencies: + esbuild: 0.18.20 + source-map-support: 0.5.21 + + '@esbuild-kit/esm-loader@2.6.5': + dependencies: + '@esbuild-kit/core-utils': 3.3.2 + get-tsconfig: 4.13.0 + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.18.20': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm@0.18.20': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-x64@0.18.20': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.18.20': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.18.20': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.18.20': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.18.20': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.18.20': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm@0.18.20': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.18.20': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.18.20': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.18.20': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.18.20': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.18.20': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.18.20': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-x64@0.18.20': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.18.20': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.18.20': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.18.20': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.18.20': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.18.20': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-x64@0.18.20': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2(jiti@1.21.7))': dependencies: eslint: 9.39.2(jiti@1.21.7) @@ -3905,11 +3683,6 @@ snapshots: '@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/trace-mapping': 0.3.31 - '@jridgewell/remapping@2.3.5': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - '@jridgewell/resolve-uri@3.1.2': {} '@jridgewell/sourcemap-codec@1.5.5': {} @@ -3919,11 +3692,6 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@mrleebo/prisma-ast@0.13.1': - dependencies: - chevrotain: 10.5.0 - lilconfig: 2.1.0 - '@napi-rs/wasm-runtime@0.2.12': dependencies: '@emnapi/core': 1.8.1 @@ -3983,7 +3751,8 @@ snapshots: dependencies: playwright: 1.58.0 - '@prisma/client@5.22.0': {} + '@prisma/client@5.22.0': + optional: true '@react-aria/focus@3.21.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: @@ -4278,9 +4047,9 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true - '@vercel/analytics@1.6.1(next@15.5.9(@babel/core@7.28.6)(@playwright/test@1.58.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)': + '@vercel/analytics@1.6.1(next@15.5.9(@playwright/test@1.58.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)': optionalDependencies: - next: 15.5.9(@babel/core@7.28.6)(@playwright/test@1.58.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + next: 15.5.9(@playwright/test@1.58.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 '@xmldom/xmldom@0.9.8': {} @@ -4402,9 +4171,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.9.18: {} - - better-auth@1.4.17(@prisma/client@5.22.0)(better-sqlite3@12.6.2)(drizzle-orm@0.41.0(@prisma/client@5.22.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.10)(pg@8.17.2))(next@15.5.9(@babel/core@7.28.6)(@playwright/test@1.58.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(pg@8.17.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + better-auth@1.4.17(@prisma/client@5.22.0)(better-sqlite3@12.6.2)(drizzle-kit@0.31.8)(drizzle-orm@0.45.1(@prisma/client@5.22.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.10)(pg@8.17.2))(next@15.5.9(@playwright/test@1.58.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(pg@8.17.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: '@better-auth/core': 1.4.17(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) '@better-auth/telemetry': 1.4.17(@better-auth/core@1.4.17(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0)) @@ -4421,8 +4188,9 @@ snapshots: optionalDependencies: '@prisma/client': 5.22.0 better-sqlite3: 12.6.2 - drizzle-orm: 0.41.0(@prisma/client@5.22.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.10)(pg@8.17.2) - next: 15.5.9(@babel/core@7.28.6)(@playwright/test@1.58.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + drizzle-kit: 0.31.8 + drizzle-orm: 0.45.1(@prisma/client@5.22.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.10)(pg@8.17.2) + next: 15.5.9(@playwright/test@1.58.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) pg: 8.17.2 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -4466,38 +4234,13 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.28.1: - dependencies: - baseline-browser-mapping: 2.9.18 - caniuse-lite: 1.0.30001766 - electron-to-chromium: 1.5.278 - node-releases: 2.0.27 - update-browserslist-db: 1.2.3(browserslist@4.28.1) + buffer-from@1.1.2: {} buffer@5.7.1: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 - bundle-name@4.1.0: - dependencies: - run-applescript: 7.1.0 - - c12@3.3.3: - dependencies: - chokidar: 5.0.0 - confbox: 0.2.2 - defu: 6.1.4 - dotenv: 17.2.3 - exsolve: 1.0.8 - giget: 2.0.0 - jiti: 2.6.1 - ohash: 2.0.11 - pathe: 2.0.3 - perfect-debounce: 2.1.0 - pkg-types: 2.3.0 - rc9: 2.1.2 - call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -4534,8 +4277,6 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 - chalk@5.6.2: {} - character-entities-html4@2.1.0: {} character-entities-legacy@3.0.0: {} @@ -4544,15 +4285,6 @@ snapshots: character-reference-invalid@2.0.1: {} - chevrotain@10.5.0: - dependencies: - '@chevrotain/cst-dts-gen': 10.5.0 - '@chevrotain/gast': 10.5.0 - '@chevrotain/types': 10.5.0 - '@chevrotain/utils': 10.5.0 - lodash: 4.17.23 - regexp-to-ast: 0.5.0 - chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -4565,18 +4297,8 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - chokidar@5.0.0: - dependencies: - readdirp: 5.0.0 - chownr@1.1.4: {} - citty@0.1.6: - dependencies: - consola: 3.4.2 - - citty@0.2.0: {} - client-only@0.0.1: {} clsx@2.1.1: {} @@ -4591,8 +4313,6 @@ snapshots: comma-separated-tokens@2.0.3: {} - commander@12.1.0: {} - commander@4.1.1: {} compromise@14.14.5: @@ -4603,12 +4323,6 @@ snapshots: concat-map@0.0.1: {} - confbox@0.2.2: {} - - consola@3.4.2: {} - - convert-source-map@2.0.0: {} - core-js@3.48.0: {} core-util-is@1.0.3: {} @@ -4668,21 +4382,12 @@ snapshots: deep-is@0.1.4: {} - default-browser-id@5.0.1: {} - - default-browser@5.4.0: - dependencies: - bundle-name: 4.1.0 - default-browser-id: 5.0.1 - define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 es-errors: 1.3.0 gopd: 1.2.0 - define-lazy-prop@3.0.0: {} - define-properties@1.2.1: dependencies: define-data-property: 1.1.4 @@ -4693,8 +4398,6 @@ snapshots: dequal@2.0.3: {} - destr@2.0.5: {} - detect-libc@2.1.2: {} devlop@1.1.0: @@ -4725,7 +4428,16 @@ snapshots: dotenv@17.2.3: {} - drizzle-orm@0.41.0(@prisma/client@5.22.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.10)(pg@8.17.2): + drizzle-kit@0.31.8: + dependencies: + '@drizzle-team/brocli': 0.10.2 + '@esbuild-kit/esm-loader': 2.6.5 + esbuild: 0.25.12 + esbuild-register: 3.6.0(esbuild@0.25.12) + transitivePeerDependencies: + - supports-color + + drizzle-orm@0.45.1(@prisma/client@5.22.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.10)(pg@8.17.2): optionalDependencies: '@prisma/client': 5.22.0 '@types/better-sqlite3': 7.6.13 @@ -4742,8 +4454,6 @@ snapshots: efrt@2.7.0: {} - electron-to-chromium@1.5.278: {} - emoji-regex@9.2.2: {} empty-module@0.0.2: {} @@ -4883,7 +4593,66 @@ snapshots: d: 1.0.2 ext: 1.7.0 - escalade@3.2.0: {} + esbuild-register@3.6.0(esbuild@0.25.12): + dependencies: + debug: 4.4.3 + esbuild: 0.25.12 + transitivePeerDependencies: + - supports-color + + esbuild@0.18.20: + optionalDependencies: + '@esbuild/android-arm': 0.18.20 + '@esbuild/android-arm64': 0.18.20 + '@esbuild/android-x64': 0.18.20 + '@esbuild/darwin-arm64': 0.18.20 + '@esbuild/darwin-x64': 0.18.20 + '@esbuild/freebsd-arm64': 0.18.20 + '@esbuild/freebsd-x64': 0.18.20 + '@esbuild/linux-arm': 0.18.20 + '@esbuild/linux-arm64': 0.18.20 + '@esbuild/linux-ia32': 0.18.20 + '@esbuild/linux-loong64': 0.18.20 + '@esbuild/linux-mips64el': 0.18.20 + '@esbuild/linux-ppc64': 0.18.20 + '@esbuild/linux-riscv64': 0.18.20 + '@esbuild/linux-s390x': 0.18.20 + '@esbuild/linux-x64': 0.18.20 + '@esbuild/netbsd-x64': 0.18.20 + '@esbuild/openbsd-x64': 0.18.20 + '@esbuild/sunos-x64': 0.18.20 + '@esbuild/win32-arm64': 0.18.20 + '@esbuild/win32-ia32': 0.18.20 + '@esbuild/win32-x64': 0.18.20 + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 escape-string-regexp@4.0.0: {} @@ -5101,8 +4870,6 @@ snapshots: expand-template@2.0.3: {} - exsolve@1.0.8: {} - ext@1.7.0: dependencies: type: 2.7.3 @@ -5192,8 +4959,6 @@ snapshots: generator-function@2.0.1: {} - gensync@1.0.0-beta.2: {} - get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -5222,15 +4987,6 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 - giget@2.0.0: - dependencies: - citty: 0.1.6 - consola: 3.4.2 - defu: 6.1.4 - node-fetch-native: 1.6.7 - nypm: 0.6.4 - pathe: 2.0.3 - github-from-package@0.0.0: {} glob-parent@5.1.2: @@ -5394,8 +5150,6 @@ snapshots: is-decimal@2.0.1: {} - is-docker@3.0.0: {} - is-extglob@2.1.1: {} is-finalizationregistry@1.1.1: @@ -5416,10 +5170,6 @@ snapshots: is-hexadecimal@2.0.1: {} - is-inside-container@1.0.0: - dependencies: - is-docker: 3.0.0 - is-map@2.0.3: {} is-negative-zero@2.0.3: {} @@ -5472,10 +5222,6 @@ snapshots: call-bound: 1.0.4 get-intrinsic: 1.3.0 - is-wsl@3.1.0: - dependencies: - is-inside-container: 1.0.0 - isarray@1.0.0: {} isarray@2.0.5: {} @@ -5493,8 +5239,6 @@ snapshots: jiti@1.21.7: {} - jiti@2.6.1: {} - jose@6.1.3: {} js-tokens@4.0.0: {} @@ -5503,8 +5247,6 @@ snapshots: dependencies: argparse: 2.0.1 - jsesc@3.1.0: {} - json-buffer@3.0.1: {} json-schema-traverse@0.4.1: {} @@ -5515,8 +5257,6 @@ snapshots: dependencies: minimist: 1.2.8 - json5@2.2.3: {} - jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.9 @@ -5535,8 +5275,6 @@ snapshots: dependencies: json-buffer: 3.0.1 - kleur@3.0.3: {} - kysely@0.28.10: {} language-subtag-registry@0.3.23: {} @@ -5558,8 +5296,6 @@ snapshots: dependencies: immediate: 3.0.6 - lilconfig@2.1.0: {} - lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} @@ -5584,10 +5320,6 @@ snapshots: lru-cache@11.2.4: {} - lru-cache@5.1.1: - dependencies: - yallist: 3.1.1 - make-cancellable-promise@1.3.2: {} make-event-props@1.6.2: {} @@ -5987,7 +5719,7 @@ snapshots: next-tick@1.1.0: {} - next@15.5.9(@babel/core@7.28.6)(@playwright/test@1.58.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + next@15.5.9(@playwright/test@1.58.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: '@next/env': 15.5.9 '@swc/helpers': 0.5.15 @@ -5995,7 +5727,7 @@ snapshots: postcss: 8.4.31 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - styled-jsx: 5.1.6(@babel/core@7.28.6)(react@19.2.3) + styled-jsx: 5.1.6(react@19.2.3) optionalDependencies: '@next/swc-darwin-arm64': 15.5.7 '@next/swc-darwin-x64': 15.5.7 @@ -6018,18 +5750,8 @@ snapshots: node-addon-api@7.1.1: optional: true - node-fetch-native@1.6.7: {} - - node-releases@2.0.27: {} - normalize-path@3.0.0: {} - nypm@0.6.4: - dependencies: - citty: 0.2.0 - pathe: 2.0.3 - tinyexec: 1.0.2 - object-assign@4.1.1: {} object-hash@3.0.0: {} @@ -6074,19 +5796,10 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 - ohash@2.0.11: {} - once@1.4.0: dependencies: wrappy: 1.0.2 - open@10.2.0: - dependencies: - default-browser: 5.4.0 - define-lazy-prop: 3.0.0 - is-inside-container: 1.0.0 - wsl-utils: 0.1.0 - openai@6.16.0(zod@4.3.6): optionalDependencies: zod: 4.3.6 @@ -6141,15 +5854,11 @@ snapshots: path2d@0.2.2: optional: true - pathe@2.0.3: {} - pdfjs-dist@4.8.69: optionalDependencies: canvas: 3.2.1 path2d: 0.2.2 - perfect-debounce@2.1.0: {} - pg-cloudflare@1.3.0: optional: true @@ -6195,12 +5904,6 @@ snapshots: pirates@4.0.7: {} - pkg-types@2.3.0: - dependencies: - confbox: 0.2.2 - exsolve: 1.0.8 - pathe: 2.0.3 - playwright-core@1.58.0: {} playwright@1.58.0: @@ -6286,15 +5989,8 @@ snapshots: prelude-ls@1.2.1: {} - prettier@3.8.1: {} - process-nextick-args@2.0.1: {} - prompts@2.4.2: - dependencies: - kleur: 3.0.3 - sisteransi: 1.0.5 - prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -6312,11 +6008,6 @@ snapshots: queue-microtask@1.2.3: {} - rc9@2.1.2: - dependencies: - defu: 6.1.4 - destr: 2.0.5 - rc@1.2.8: dependencies: deep-extend: 0.6.0 @@ -6431,8 +6122,6 @@ snapshots: dependencies: picomatch: 2.3.1 - readdirp@5.0.0: {} - redux@4.2.1: dependencies: '@babel/runtime': 7.28.6 @@ -6448,8 +6137,6 @@ snapshots: get-proto: 1.0.1 which-builtin-type: 1.2.1 - regexp-to-ast@0.5.0: {} - regexp.prototype.flags@1.5.4: dependencies: call-bind: 1.0.8 @@ -6513,8 +6200,6 @@ snapshots: rou3@0.7.12: {} - run-applescript@7.1.0: {} - run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -6648,10 +6333,15 @@ snapshots: once: 1.4.0 simple-concat: 1.0.1 - sisteransi@1.0.5: {} - source-map-js@1.2.1: {} + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + space-separated-tokens@2.0.2: {} split2@4.2.0: {} @@ -6740,12 +6430,10 @@ snapshots: dependencies: inline-style-parser: 0.2.7 - styled-jsx@5.1.6(@babel/core@7.28.6)(react@19.2.3): + styled-jsx@5.1.6(react@19.2.3): dependencies: client-only: 0.0.1 react: 19.2.3 - optionalDependencies: - '@babel/core': 7.28.6 sucrase@3.35.1: dependencies: @@ -6820,8 +6508,6 @@ snapshots: tiny-invariant@1.3.3: {} - tinyexec@1.0.2: {} - tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) @@ -6961,12 +6647,6 @@ snapshots: '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 - update-browserslist-db@1.2.3(browserslist@4.28.1): - dependencies: - browserslist: 4.28.1 - escalade: 3.2.0 - picocolors: 1.1.1 - uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -7042,22 +6722,10 @@ snapshots: wrappy@1.0.2: {} - wsl-utils@0.1.0: - dependencies: - is-wsl: 3.1.0 - xtend@4.0.2: {} - yallist@3.1.1: {} - yocto-queue@0.1.0: {} - yocto-spinner@0.2.3: - dependencies: - yoctocolors: 2.1.2 - - yoctocolors@2.1.2: {} - zod@4.3.6: {} zwitch@2.0.4: {} diff --git a/scripts/migrate-if-auth.mjs b/scripts/migrate-if-auth.mjs new file mode 100644 index 0000000..28b6d4e --- /dev/null +++ b/scripts/migrate-if-auth.mjs @@ -0,0 +1,38 @@ +import { spawnSync } from 'node:child_process'; +import path from 'node:path'; +import fs from 'node:fs'; +import * as dotenv from 'dotenv'; + +function loadEnvFiles() { + // Approximate Next.js behavior enough for server-side scripts. + // Load .env first, then .env.local (local overrides). + const cwd = process.cwd(); + const envPath = path.join(cwd, '.env'); + const envLocalPath = path.join(cwd, '.env.local'); + + if (fs.existsSync(envPath)) { + dotenv.config({ path: envPath }); + } + if (fs.existsSync(envLocalPath)) { + dotenv.config({ path: envLocalPath, override: true }); + } +} + +loadEnvFiles(); + +const authEnabled = Boolean(process.env.BETTER_AUTH_SECRET && process.env.BETTER_AUTH_URL); + +if (!authEnabled) { + // When auth is disabled, the app must not touch sqlite/postgres at all. + // That includes running migrations which can create/open DB files. + console.log('[migrate] Skipping (auth disabled). Missing BETTER_AUTH_SECRET and/or BETTER_AUTH_URL.'); + process.exit(0); +} + +const extraArgs = process.argv.slice(2); +const result = spawnSync('drizzle-kit', ['migrate', ...extraArgs], { + stdio: 'inherit', + env: process.env, +}); + +process.exit(result.status ?? 1); diff --git a/src/app/api/account/delete/route.ts b/src/app/api/account/delete/route.ts index cc7d01d..abed1f5 100644 --- a/src/app/api/account/delete/route.ts +++ b/src/app/api/account/delete/route.ts @@ -1,7 +1,9 @@ import { headers } from 'next/headers'; import { NextResponse } from 'next/server'; import { auth } from '@/lib/server/auth'; -import { db } from '@/lib/server/db'; +import { db } from '@/db'; +import { user } from '@/db/schema'; +import { eq } from 'drizzle-orm'; import { isAuthEnabled } from '@/lib/server/auth-config'; export async function DELETE() { @@ -19,11 +21,8 @@ export async function DELETE() { try { // Directly delete user from database - await db.transaction(async (client) => { - // Deleting user usually cascades to sessions, accounts, etc if FKs are set up correctly - // But better-auth schemas do cascade. - await client.query('DELETE FROM "user" WHERE id = $1', [session.user.id]); - }); + // Drizzle will handle cascade if configured in foreign keys, but typical cascading happens in DB engine + await db.delete(user).where(eq(user.id, session.user.id)); return NextResponse.json({ success: true }); } catch (error) { diff --git a/src/app/api/audiobook/chapter/route.ts b/src/app/api/audiobook/chapter/route.ts index 2c1e6ce..8f09cd8 100644 --- a/src/app/api/audiobook/chapter/route.ts +++ b/src/app/api/audiobook/chapter/route.ts @@ -4,6 +4,10 @@ import { readdir, unlink } from 'fs/promises'; import { join } from 'path'; import { AUDIOBOOKS_V1_DIR, isAudiobooksV1Ready } from '@/lib/server/docstore'; import { findStoredChapterByIndex } from '@/lib/server/audiobook'; +import { db } from '@/db'; +import { audiobookChapters } from '@/db/schema'; +import { and, eq } from 'drizzle-orm'; +import { requireAuthContext, requireAudiobookOwned } from '@/lib/server/auth'; export const dynamic = 'force-dynamic'; @@ -18,10 +22,10 @@ export async function GET(request: NextRequest) { try { const bookId = request.nextUrl.searchParams.get('bookId'); const chapterIndexStr = request.nextUrl.searchParams.get('chapterIndex'); - + if (!bookId || !chapterIndexStr) { return NextResponse.json( - { error: 'Missing bookId or chapterIndex parameter' }, + { error: 'Missing bookId or chapterIndex parameter' }, { status: 400 } ); } @@ -29,7 +33,7 @@ export async function GET(request: NextRequest) { const chapterIndex = parseInt(chapterIndexStr); if (isNaN(chapterIndex)) { return NextResponse.json( - { error: 'Invalid chapterIndex parameter' }, + { error: 'Invalid chapterIndex parameter' }, { status: 400 } ); } @@ -40,8 +44,16 @@ export async function GET(request: NextRequest) { { status: 409 }, ); } + + const ctxOrRes = await requireAuthContext(request); + if (ctxOrRes instanceof Response) return ctxOrRes; + if (ctxOrRes.authEnabled) { + const denied = await requireAudiobookOwned(bookId, ctxOrRes.userId!, { onDenied: 'notFound' }); + if (denied) return denied; + } + const intermediateDir = join(getAudiobooksRootDir(request), `${bookId}-audiobook`); - + const chapter = await findStoredChapterByIndex(intermediateDir, chapterIndex, request.signal); if (!chapter || !existsSync(chapter.filePath)) { return NextResponse.json({ error: 'Chapter not found' }, { status: 404 }); @@ -113,22 +125,38 @@ export async function DELETE(request: NextRequest) { { status: 409 }, ); } + + const ctxOrRes = await requireAuthContext(request); + if (ctxOrRes instanceof Response) return ctxOrRes; + + if (ctxOrRes.authEnabled) { + const denied = await requireAudiobookOwned(bookId, ctxOrRes.userId!, { onDenied: 'forbidden' }); + if (denied) return denied; + + // Delete from DB first + if (db) { + await db.delete(audiobookChapters).where( + and(eq(audiobookChapters.bookId, bookId), eq(audiobookChapters.chapterIndex, chapterIndex)), + ); + } + } + const intermediateDir = join(getAudiobooksRootDir(request), `${bookId}-audiobook`); const chapterPrefix = `${String(chapterIndex + 1).padStart(4, '0')}__`; const files = await readdir(intermediateDir).catch(() => []); for (const file of files) { if (!file.startsWith(chapterPrefix)) continue; if (!file.endsWith('.mp3') && !file.endsWith('.m4b')) continue; - await unlink(join(intermediateDir, file)).catch(() => {}); + await unlink(join(intermediateDir, file)).catch(() => { }); } // Invalidate any combined "complete" files const completeM4b = join(intermediateDir, `complete.m4b`); const completeMp3 = join(intermediateDir, `complete.mp3`); - if (existsSync(completeM4b)) await unlink(completeM4b).catch(() => {}); - if (existsSync(completeMp3)) await unlink(completeMp3).catch(() => {}); - await unlink(join(intermediateDir, 'complete.mp3.manifest.json')).catch(() => {}); - await unlink(join(intermediateDir, 'complete.m4b.manifest.json')).catch(() => {}); + if (existsSync(completeM4b)) await unlink(completeM4b).catch(() => { }); + if (existsSync(completeMp3)) await unlink(completeMp3).catch(() => { }); + await unlink(join(intermediateDir, 'complete.mp3.manifest.json')).catch(() => { }); + await unlink(join(intermediateDir, 'complete.m4b.manifest.json')).catch(() => { }); return NextResponse.json({ success: true }); } catch (error) { @@ -139,3 +167,4 @@ export async function DELETE(request: NextRequest) { ); } } + diff --git a/src/app/api/audiobook/route.ts b/src/app/api/audiobook/route.ts index 15aa4cb..a086da4 100644 --- a/src/app/api/audiobook/route.ts +++ b/src/app/api/audiobook/route.ts @@ -8,6 +8,11 @@ import { AUDIOBOOKS_V1_DIR, isAudiobooksV1Ready } from '@/lib/server/docstore'; import { encodeChapterFileName, encodeChapterTitleTag, listStoredChapters, ffprobeAudio, escapeFFMetadata } from '@/lib/server/audiobook'; import type { TTSAudioBytes, TTSAudiobookFormat } from '@/types/tts'; import type { AudiobookGenerationSettings } from '@/types/client'; +import { db } from '@/db'; +import { audiobooks, audiobookChapters } from '@/db/schema'; +import { eq } from 'drizzle-orm'; +import { isAuthEnabled } from '@/lib/server/auth-config'; +import { requireAuthContext } from '@/lib/server/auth'; export const dynamic = 'force-dynamic'; @@ -51,7 +56,7 @@ async function getAudioDuration(filePath: string, signal?: AbortSignal): Promise finished = true; try { ffprobe.kill('SIGKILL'); - } catch {} + } catch { } reject(new Error('ABORTED')); }; @@ -103,7 +108,7 @@ async function runFFmpeg(args: string[], signal?: AbortSignal): Promise { finished = true; try { ffmpeg.kill('SIGKILL'); - } catch {} + } catch { } reject(new Error('ABORTED')); }; @@ -158,7 +163,7 @@ export async function POST(request: NextRequest) { // Parse the request body const data: ConversionRequest = await request.json(); const requestedFormat = data.format || 'm4b'; - + if (!(await isAudiobooksV1Ready())) { return NextResponse.json( { error: 'Audiobooks storage is not migrated; run /api/migrations/v1 first.' }, @@ -166,15 +171,36 @@ export async function POST(request: NextRequest) { ); } + const ctxOrRes = await requireAuthContext(request); + if (ctxOrRes instanceof Response) return ctxOrRes; + const userId = ctxOrRes.userId; + // Generate or use existing book ID const bookId = data.bookId || randomUUID(); - + if (!isSafeId(bookId)) { return NextResponse.json({ error: 'Invalid bookId parameter' }, { status: 400 }); } + // DB Check / Insert Audiobook + if (isAuthEnabled() && db) { + const [existingBook] = await db.select().from(audiobooks).where(eq(audiobooks.id, bookId)); + + if (!existingBook) { + await db.insert(audiobooks).values({ + id: bookId, + userId, + title: data.chapterTitle || 'Untitled Audiobook', + }); + } else { + if (existingBook.userId && existingBook.userId !== userId) { + return NextResponse.json({ error: 'Forbidden' }, { status: 403 }); + } + } + } + const intermediateDir = join(getAudiobooksRootDir(request), `${bookId}-audiobook`); - + // Create intermediate directory await mkdir(intermediateDir, { recursive: true }); @@ -254,7 +280,7 @@ export async function POST(request: NextRequest) { const inputPath = join(intermediateDir, `${chapterIndex}-input.mp3`); const chapterOutputTempPath = join(intermediateDir, `${chapterIndex}-chapter.tmp.${format}`); const titleTag = encodeChapterTitleTag(chapterIndex, data.chapterTitle); - + // Write the chapter audio to a temp file await writeFile(inputPath, Buffer.from(new Uint8Array(data.buffer))); @@ -292,7 +318,7 @@ export async function POST(request: NextRequest) { const duration = probe.durationSec ?? (await getAudioDuration(chapterOutputTempPath, request.signal)); const finalChapterPath = join(intermediateDir, encodeChapterFileName(chapterIndex, data.chapterTitle, format)); - await unlink(finalChapterPath).catch(() => {}); + await unlink(finalChapterPath).catch(() => { }); await rename(chapterOutputTempPath, finalChapterPath); // Remove any existing chapter files for this index (e.g., if the title changed and the @@ -304,22 +330,39 @@ export async function POST(request: NextRequest) { if (!file.startsWith(chapterPrefix)) continue; if (!file.endsWith('.mp3') && !file.endsWith('.m4b')) continue; if (file === finalChapterName) continue; - await unlink(join(intermediateDir, file)).catch(() => {}); + await unlink(join(intermediateDir, file)).catch(() => { }); } - await unlink(join(intermediateDir, 'complete.mp3')).catch(() => {}); - await unlink(join(intermediateDir, 'complete.m4b')).catch(() => {}); - await unlink(join(intermediateDir, 'complete.mp3.manifest.json')).catch(() => {}); - await unlink(join(intermediateDir, 'complete.m4b.manifest.json')).catch(() => {}); + await unlink(join(intermediateDir, 'complete.mp3')).catch(() => { }); + await unlink(join(intermediateDir, 'complete.m4b')).catch(() => { }); + await unlink(join(intermediateDir, 'complete.mp3.manifest.json')).catch(() => { }); + await unlink(join(intermediateDir, 'complete.m4b.manifest.json')).catch(() => { }); // Ensure meta exists after first successful chapter. if (!existingSettings && incomingSettings) { - await writeFile(metaPath, JSON.stringify(incomingSettings, null, 2)).catch(() => {}); + await writeFile(metaPath, JSON.stringify(incomingSettings, null, 2)).catch(() => { }); } // Clean up input file await unlink(inputPath).catch(console.error); - return NextResponse.json({ + // Insert Chapter Record (Denormalized) + if (isAuthEnabled() && db) { + await db.insert(audiobookChapters).values({ + id: `${bookId}-${chapterIndex}`, + bookId, + userId, + chapterIndex, + title: data.chapterTitle, + duration, + format, + filePath: finalChapterName + }).onConflictDoUpdate({ + target: [audiobookChapters.id], + set: { title: data.chapterTitle, duration, format, filePath: finalChapterName } + }); + } + + return NextResponse.json({ index: chapterIndex, title: data.chapterTitle, duration, @@ -360,6 +403,19 @@ export async function GET(request: NextRequest) { { status: 409 }, ); } + + // Auth Check + const session = await auth?.api.getSession({ headers: request.headers }); + const userId = session?.user?.id || null; + + // Verify ownership + if (isAuthEnabled() && db) { + const [existingBook] = await db.select().from(audiobooks).where(eq(audiobooks.id, bookId)); + if (existingBook && existingBook.userId && existingBook.userId !== userId) { + return NextResponse.json({ error: 'Book not found' }, { status: 404 }); // Hide existence + } + } + const intermediateDir = join(getAudiobooksRootDir(request), `${bookId}-audiobook`); if (!existsSync(intermediateDir)) { @@ -412,8 +468,8 @@ export async function GET(request: NextRequest) { return streamFile(outputPath, format); } - await unlink(outputPath).catch(() => {}); - await unlink(manifestPath).catch(() => {}); + await unlink(outputPath).catch(() => { }); + await unlink(manifestPath).catch(() => { }); } // Ensure we have chapter durations for chapter markers / ordering. @@ -425,7 +481,7 @@ export async function GET(request: NextRequest) { chapter.duration = probe.durationSec; continue; } - } catch {} + } catch { } try { chapter.duration = await getAudioDuration(chapter.filePath, request.signal); @@ -445,7 +501,7 @@ export async function GET(request: NextRequest) { metadata.push('[CHAPTER]', 'TIMEBASE=1/1000', `START=${startMs}`, `END=${endMs}`, `title=${escapeFFMetadata(chapter.title)}`); } - + await writeFile(metadataPath, ';FFMETADATA1\n' + metadata.join('\n')); // Create list file for concat @@ -486,7 +542,7 @@ export async function GET(request: NextRequest) { unlink(listPath).catch(console.error) ]); - await writeFile(manifestPath, JSON.stringify(signature, null, 2)).catch(() => {}); + await writeFile(manifestPath, JSON.stringify(signature, null, 2)).catch(() => { }); // Stream the file back to the client return streamFile(outputPath, format); @@ -553,6 +609,30 @@ export async function DELETE(request: NextRequest) { { status: 409 }, ); } + + // Auth Check + const session = await auth?.api.getSession({ headers: request.headers }); + const userId = session?.user?.id || null; + + if (!userId) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + + // Verify ownership + if (isAuthEnabled() && db) { + const [existingBook] = await db.select().from(audiobooks).where(eq(audiobooks.id, bookId)); + + if (existingBook) { + if (existingBook.userId && existingBook.userId !== userId) { + return NextResponse.json({ error: 'Forbidden' }, { status: 403 }); + } + + await db.delete(audiobooks).where(eq(audiobooks.id, bookId)); + } else { + return NextResponse.json({ error: 'Book not found' }, { status: 404 }); + } + } + const intermediateDir = join(getAudiobooksRootDir(request), `${bookId}-audiobook`); // If directory doesn't exist, consider it already reset diff --git a/src/app/api/audiobook/status/route.ts b/src/app/api/audiobook/status/route.ts index 6761546..ec810d5 100644 --- a/src/app/api/audiobook/status/route.ts +++ b/src/app/api/audiobook/status/route.ts @@ -6,6 +6,7 @@ import { listStoredChapters } from '@/lib/server/audiobook'; import type { AudiobookGenerationSettings } from '@/types/client'; import type { TTSAudiobookFormat, TTSAudiobookChapter } from '@/types/tts'; import { readFile } from 'fs/promises'; +import { requireAuthContext, requireAudiobookOwned } from '@/lib/server/auth'; export const dynamic = 'force-dynamic'; @@ -35,6 +36,23 @@ export async function GET(request: NextRequest) { { status: 409 }, ); } + + const ctxOrRes = await requireAuthContext(request); + if (ctxOrRes instanceof Response) return ctxOrRes; + + if (ctxOrRes.authEnabled) { + const denied = await requireAudiobookOwned(bookId, ctxOrRes.userId!, { onDenied: 'notFound' }); + if (denied) { + return NextResponse.json({ + chapters: [], + exists: false, + hasComplete: false, + bookId: null, + settings: null, + }); + } + } + const intermediateDir = join(getAudiobooksRootDir(request), `${bookId}-audiobook`); if (!existsSync(intermediateDir)) { @@ -66,8 +84,8 @@ export async function GET(request: NextRequest) { const hasComplete = existsSync(join(intermediateDir, 'complete.mp3')) || existsSync(join(intermediateDir, 'complete.m4b')); - return NextResponse.json({ - chapters, + return NextResponse.json({ + chapters, exists: true, hasComplete, bookId, diff --git a/src/app/api/documents/docx-to-pdf/route.ts b/src/app/api/documents/docx-to-pdf/route.ts index de61165..b8ac32a 100644 --- a/src/app/api/documents/docx-to-pdf/route.ts +++ b/src/app/api/documents/docx-to-pdf/route.ts @@ -5,6 +5,7 @@ import path from 'path'; import { existsSync } from 'fs'; import { randomUUID } from 'crypto'; import { pathToFileURL } from 'url'; +import { auth } from '@/lib/server/auth'; const DOCSTORE_DIR = path.join(process.cwd(), 'docstore'); const TEMP_DIR = path.join(DOCSTORE_DIR, 'tmp'); @@ -75,11 +76,17 @@ async function waitForPdfReady(dir: string, timeoutMs = 20000, intervalMs = 100) export async function POST(req: NextRequest) { try { + // Auth check - require session + const session = await auth?.api.getSession({ headers: req.headers }); + if (auth && !session?.user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + await ensureTempDir(); - + const formData = await req.formData(); const file = formData.get('file') as File; - + if (!file) { return NextResponse.json( { error: 'No file provided' }, diff --git a/src/app/api/documents/library/content/route.ts b/src/app/api/documents/library/content/route.ts index 721779e..474fb48 100644 --- a/src/app/api/documents/library/content/route.ts +++ b/src/app/api/documents/library/content/route.ts @@ -2,6 +2,7 @@ import { readFile, stat } from 'fs/promises'; import path from 'path'; import { NextRequest, NextResponse } from 'next/server'; import { contentTypeForName, decodeLibraryId, isPathWithinRoot, parseLibraryRoots } from '@/lib/server/library'; +import { auth } from '@/lib/server/auth'; export const dynamic = 'force-dynamic'; @@ -57,6 +58,12 @@ function contentDispositionAttachment(filename: string): string { } export async function GET(req: NextRequest) { + // Auth check - require session + const session = await auth?.api.getSession({ headers: req.headers }); + if (auth && !session?.user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + const url = new URL(req.url); const id = url.searchParams.get('id'); if (!id) { diff --git a/src/app/api/documents/library/route.ts b/src/app/api/documents/library/route.ts index a4cb0d0..047d9ac 100644 --- a/src/app/api/documents/library/route.ts +++ b/src/app/api/documents/library/route.ts @@ -4,6 +4,7 @@ import path from 'path'; import { NextRequest, NextResponse } from 'next/server'; import { parseLibraryRoots } from '@/lib/server/library'; import type { DocumentType } from '@/types/documents'; +import { auth } from '@/lib/server/auth'; export const dynamic = 'force-dynamic'; @@ -41,10 +42,10 @@ function libraryDocumentTypeFromName(name: string): DocumentType { let cache: | { - cacheKey: string; - cachedAt: number; - documents: LibraryDocument[]; - } + cacheKey: string; + cachedAt: number; + documents: LibraryDocument[]; + } | undefined; async function scanLibraryRoot(root: string, rootIndex: number, limit: number): Promise { @@ -103,6 +104,12 @@ async function scanLibraryRoot(root: string, rootIndex: number, limit: number): } export async function GET(req: NextRequest) { + // Auth check - require session + const session = await auth?.api.getSession({ headers: req.headers }); + if (auth && !session?.user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + const url = new URL(req.url); const refresh = url.searchParams.get('refresh') === '1'; const limit = Math.max(1, Math.min(Number(url.searchParams.get('limit') ?? '5000'), 10000)); diff --git a/src/app/api/documents/route.ts b/src/app/api/documents/route.ts index fdb98a9..ab02515 100644 --- a/src/app/api/documents/route.ts +++ b/src/app/api/documents/route.ts @@ -1,9 +1,14 @@ import { createHash } from 'crypto'; -import { readdir, readFile, stat, unlink, utimes, writeFile } from 'fs/promises'; +import { readFile, stat, unlink, utimes, writeFile } from 'fs/promises'; import { NextRequest, NextResponse } from 'next/server'; import path from 'path'; -import { DOCUMENTS_V1_DIR, isDocumentsV1Ready } from '@/lib/server/docstore'; +import { DOCUMENTS_V1_DIR, isDocumentsV1Ready, scanDocumentsFS } from '@/lib/server/docstore'; import type { BaseDocument, DocumentType, SyncedDocument } from '@/types/documents'; +import { db } from '@/db'; +import { documents } from '@/db/schema'; +import { eq, or, and, inArray, count } from 'drizzle-orm'; +import { isAuthEnabled } from '@/lib/server/auth-config'; +import { requireAuthContext } from '@/lib/server/auth'; export const dynamic = 'force-dynamic'; @@ -29,67 +34,6 @@ function toDocumentTypeFromName(name: string): DocumentType { return 'html'; } -function parseSyncedFileName(fileName: string): { id: string; name: string } | null { - const match = /^([a-f0-9]{64})__(.+)$/i.exec(fileName); - if (!match) return null; - try { - return { id: match[1].toLowerCase(), name: decodeURIComponent(match[2]) }; - } catch { - return null; - } -} - -async function loadSyncedDocuments(includeData: boolean, targetIds?: Set): Promise<(BaseDocument | SyncedDocument)[]> { - const results: (BaseDocument | SyncedDocument)[] = []; - let files: string[] = []; - - try { - files = await readdir(SYNC_DIR); - } catch { - return results; - } - - for (const file of files) { - const parsed = parseSyncedFileName(file); - if (!parsed) continue; - - // Filter by ID if specific IDs are requested - if (targetIds && !targetIds.has(parsed.id)) continue; - - const filePath = path.join(SYNC_DIR, file); - let fileStat: Awaited>; - try { - fileStat = await stat(filePath); - } catch { - continue; - } - - if (!fileStat.isFile()) continue; - - const type = toDocumentTypeFromName(parsed.name); - const metadata: BaseDocument = { - id: parsed.id, - name: parsed.name, - size: fileStat.size, - lastModified: fileStat.mtimeMs, - type, - }; - - if (!includeData) { - results.push(metadata); - continue; - } - - const content = await readFile(filePath); - results.push({ - ...metadata, - data: Array.from(new Uint8Array(content)), - }); - } - - return results; -} - export async function POST(req: NextRequest) { try { if (!(await isDocumentsV1Ready())) { @@ -98,45 +42,56 @@ export async function POST(req: NextRequest) { { status: 409 }, ); } + + const ctxOrRes = await requireAuthContext(req); + if (ctxOrRes instanceof Response) return ctxOrRes; + const userId = ctxOrRes.userId; + const data = await req.json(); - const documents = data.documents as SyncedDocument[]; - - let existingFiles: string[] = []; - try { - existingFiles = await readdir(SYNC_DIR); - } catch { - existingFiles = []; - } - - const existingById = new Map(); - for (const file of existingFiles) { - const parsed = parseSyncedFileName(file); - if (!parsed) continue; - if (!existingById.has(parsed.id)) { - existingById.set(parsed.id, file); - } - } - + const documentsData = data.documents as SyncedDocument[]; const stored: Array<{ oldId: string; id: string; name: string }> = []; - for (const doc of documents) { + // Ensure directory exists (redundant with isDocumentsV1Ready but safe) + // const SYNC_DIR = DOCUMENTS_V1_DIR; + + for (const doc of documentsData) { const content = Buffer.from(new Uint8Array(doc.data)); const id = createHash('sha256').update(content).digest('hex'); const baseName = path.basename(doc.name || `${id}.${doc.type}`); const safeName = baseName.replaceAll('\u0000', '').slice(0, 240) || `${id}.${doc.type}`; - const existingFile = existingById.get(id); - const targetFileName = existingFile ?? `${id}__${encodeURIComponent(safeName)}`; + const targetFileName = `${id}__${encodeURIComponent(safeName)}`; const targetPath = path.join(SYNC_DIR, targetFileName); - if (!existingFile) { + // Write file if not exists + try { + await stat(targetPath); + } catch { await writeFile(targetPath, content); - existingById.set(id, targetFileName); } - await trySetFileMtime(targetPath, doc.lastModified); + // DB Upsert + // With composite PK (id, userId), we check if THIS user already has this document + if (isAuthEnabled() && db) { + const [existing] = await db.select().from(documents).where( + and(eq(documents.id, id), eq(documents.userId, userId)) + ); + + if (!existing) { + await db.insert(documents).values({ + id, + userId, + name: safeName, + type: doc.type, + size: content.length, + lastModified: doc.lastModified, + filePath: targetFileName + }); + } + } + stored.push({ oldId: doc.id, id, name: safeName }); } @@ -156,6 +111,10 @@ export async function GET(req: NextRequest) { ); } + const ctxOrRes = await requireAuthContext(req); + if (ctxOrRes instanceof Response) return ctxOrRes; + const userId = ctxOrRes.userId; + const url = new URL(req.url); const list = url.searchParams.get('list') === 'true'; const format = url.searchParams.get('format'); @@ -165,22 +124,73 @@ export async function GET(req: NextRequest) { // If format=metadata, force metadata only. // Otherwise include data. const includeData = !list && format !== 'metadata'; - - let targetIds: Set | undefined; - if (idsParam) { - targetIds = new Set(idsParam.split(',').filter(Boolean)); + + const targetIds = idsParam ? idsParam.split(',').filter(Boolean) : null; + + // Query database (or filesystem) for documents the user is allowed to access + let allowedDocs: { id: string; name: string; type: string; size: number; lastModified: number; filePath: string }[] = []; + + if (!isAuthEnabled()) { + const fsDocs = await scanDocumentsFS(); + allowedDocs = fsDocs.map(d => ({ ...d })); + if (targetIds) { + allowedDocs = allowedDocs.filter(d => targetIds!.includes(d.id)); + } + } else { + if (!db) throw new Error("DB not initialized"); + const rows = await db.select().from(documents).where( + and( + or(eq(documents.userId, userId), eq(documents.userId, 'unclaimed')), + targetIds ? inArray(documents.id, targetIds) : undefined + ) + ); + allowedDocs = rows as unknown as { id: string; name: string; type: string; size: number; lastModified: number; filePath: string }[]; } - const documents = await loadSyncedDocuments(includeData, targetIds); + const results: (BaseDocument | SyncedDocument)[] = []; - return NextResponse.json({ documents }); + for (const doc of allowedDocs) { + const type: DocumentType = + doc.type === 'pdf' || doc.type === 'epub' || doc.type === 'docx' || doc.type === 'html' + ? (doc.type as DocumentType) + : toDocumentTypeFromName(doc.name); + + const metadata: BaseDocument = { + id: doc.id!, + name: doc.name, + size: doc.size, + lastModified: doc.lastModified, + type, + }; + + if (!includeData) { + results.push(metadata); + continue; + } + + try { + const filePath = path.join(SYNC_DIR, doc.filePath); + const content = await readFile(filePath); + results.push({ + ...metadata, + data: Array.from(new Uint8Array(content)), + }); + } catch (err) { + console.warn(`Failed to read content for document ${doc.id} at ${doc.filePath}`, err); + // Skip adding data if file is missing, or just skip item? + // Best to skip item to avoid client errors on partial data + } + } + + return NextResponse.json({ documents: results }); } catch (error) { console.error('Error loading documents:', error); return NextResponse.json({ error: 'Failed to load documents' }, { status: 500 }); } } -export async function DELETE() { + +export async function DELETE(req: NextRequest) { try { if (!(await isDocumentsV1Ready())) { return NextResponse.json( @@ -189,27 +199,86 @@ export async function DELETE() { ); } - // Delete synced docs (new format) safely without touching unrelated docstore data. - let syncFiles: string[] = []; - try { - syncFiles = await readdir(SYNC_DIR); - } catch { - syncFiles = []; - } + // Auth check - require session + const ctxOrRes = await requireAuthContext(req); + if (ctxOrRes instanceof Response) return ctxOrRes; + const userId = ctxOrRes.userId; - for (const file of syncFiles) { - const filePath = path.join(SYNC_DIR, file); - try { - const st = await stat(filePath); - if (st.isFile()) { - await unlink(filePath); - } - } catch { - continue; + const url = new URL(req.url); + const idsParam = url.searchParams.get('ids'); + + // Determine which IDs to try to delete + let targetIds: string[] = []; + + if (idsParam) { + targetIds = idsParam.split(',').filter(Boolean); + } else { + if (!isAuthEnabled()) { + const fsDocs = await scanDocumentsFS(); + targetIds = fsDocs.map((d) => d.id); + } else { + // Existing behavior was "nuke everything"; keep it scoped to "my" docs. + if (!db) throw new Error("DB not initialized"); + const userDocs = await db.select({ id: documents.id }).from(documents).where(eq(documents.userId, userId as string)); + targetIds = userDocs.map((d: { id: string | null }) => d.id!).filter(Boolean) as string[]; } } - return NextResponse.json({ success: true }); + if (targetIds.length === 0) { + return NextResponse.json({ success: true, deleted: 0 }); + } + + const deletedRows: { id: string; filePath: string }[] = []; + + if (!isAuthEnabled()) { + // FS cleanup only + // Since we don't track ownership, we just delete the files requested. + // This implies in no-auth mode, any user can delete any file if they know the ID. + const fsDocs = await scanDocumentsFS(); + for (const doc of fsDocs) { + if (targetIds.includes(doc.id)) { + deletedRows.push({ id: doc.id, filePath: doc.filePath }); + } + } + } else { + if (!db) throw new Error("DB not initialized"); + const rows = await db.delete(documents) + .where(and( + eq(documents.userId, userId as string), + inArray(documents.id, targetIds) + )) + .returning({ id: documents.id, filePath: documents.filePath }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + rows.forEach((r: any) => deletedRows.push({ id: r.id!, filePath: r.filePath })); + } + + // If driver doesn't support returning (e.g. older SQLite without properly configured returning), we might fallback. + // But Drizzle usually handles this. + + let deletedCount = 0; + + for (const row of deletedRows) { + deletedCount++; + // Chech reference count for this ID + // If 0 remaining, delete file + let refCount = 0; + if (isAuthEnabled() && db) { + const [ref] = await db.select({ count: count() }).from(documents).where(eq(documents.id, row.id!)); + refCount = ref?.count ?? 0; + } + + if (refCount === 0) { + const filePath = path.join(SYNC_DIR, row.filePath); + try { + await unlink(filePath); + } catch { + // Ignore if missing + } + } + } + + return NextResponse.json({ success: true, deleted: deletedCount }); + } catch (error) { console.error('Error deleting documents:', error); return NextResponse.json({ error: 'Failed to delete documents' }, { status: 500 }); diff --git a/src/app/api/migrations/v1/route.ts b/src/app/api/migrations/v1/route.ts index 0cca347..cbee2db 100644 --- a/src/app/api/migrations/v1/route.ts +++ b/src/app/api/migrations/v1/route.ts @@ -9,6 +9,7 @@ import { isAudiobooksV1Ready, isDocumentsV1Ready, } from '@/lib/server/docstore'; +import { auth } from '@/lib/server/auth'; type Mapping = { oldId: string; id: string }; @@ -42,7 +43,7 @@ async function mergeDirectoryContents(sourceDir: string, targetDir: string): Pro if (remaining.length === 0) { await rm(sourcePath); } - } catch {} + } catch { } continue; } @@ -95,7 +96,7 @@ async function rekeyAudiobooksV1(mappings: Mapping[]): Promise<{ renamed: number if (remaining.length === 0) { await rm(sourceDir); } - } catch {} + } catch { } } return { renamed, merged, skipped }; @@ -103,6 +104,12 @@ async function rekeyAudiobooksV1(mappings: Mapping[]): Promise<{ renamed: number export async function POST(request: NextRequest) { try { + // Auth check - require session + const session = await auth?.api.getSession({ headers: request.headers }); + if (auth && !session?.user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + const raw = (await request.json().catch(() => null)) as { mappings?: Mapping[] } | null; const mappings = (raw?.mappings ?? []).filter( (m): m is Mapping => Boolean(m && typeof m.oldId === 'string' && typeof m.id === 'string'), diff --git a/src/app/api/tts/voices/route.ts b/src/app/api/tts/voices/route.ts index 60a7252..bd96e0d 100644 --- a/src/app/api/tts/voices/route.ts +++ b/src/app/api/tts/voices/route.ts @@ -1,5 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; import { isKokoroModel } from '@/utils/voice'; +import { auth } from '@/lib/server/auth'; const OPENAI_VOICES = ['alloy', 'echo', 'fable', 'onyx', 'nova', 'shimmer']; const GPT4O_MINI_VOICES = ['alloy', 'ash', 'coral', 'echo', 'fable', 'onyx', 'nova', 'sage', 'shimmer']; @@ -27,7 +28,7 @@ function getDefaultVoices(provider: string, model: string): string[] { } return OPENAI_VOICES; } - + // For Custom OpenAI-Like provider if (provider === 'custom-openai') { // If using Kokoro-FastAPI (model string contains 'kokoro'), expose full Kokoro voices @@ -36,7 +37,7 @@ function getDefaultVoices(provider: string, model: string): string[] { } return CUSTOM_OPENAI_VOICES; } - + // For Deepinfra provider - model-specific voices if (provider === 'deepinfra') { if (model === 'hexgrad/Kokoro-82M') { @@ -58,7 +59,7 @@ function getDefaultVoices(provider: string, model: string): string[] { // Default Deepinfra voices return CUSTOM_OPENAI_VOICES; } - + // Default fallback return OPENAI_VOICES; } @@ -77,7 +78,7 @@ async function fetchDeepinfraVoices(apiKey: string): Promise { } const data = await response.json(); - + // Extract voice names from the response, excluding preset voices if (data.voices && Array.isArray(data.voices)) { return data.voices @@ -93,6 +94,12 @@ async function fetchDeepinfraVoices(apiKey: string): Promise { export async function GET(req: NextRequest) { try { + // Auth check - require session + const session = await auth?.api.getSession({ headers: req.headers }); + if (auth && !session?.user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + const openApiKey = req.headers.get('x-openai-key') || process.env.API_KEY || 'none'; const openApiBaseUrl = req.headers.get('x-openai-base-url') || process.env.API_BASE; const provider = req.headers.get('x-tts-provider') || 'openai'; @@ -106,9 +113,9 @@ export async function GET(req: NextRequest) { // For Deepinfra provider with specific models that need API fetching if (provider === 'deepinfra') { const needsApiFetch = model === 'ResembleAI/chatterbox' || - model === 'Zyphra/Zonos-v0.1-hybrid' || - model === 'Zyphra/Zonos-v0.1-transformer'; - + model === 'Zyphra/Zonos-v0.1-hybrid' || + model === 'Zyphra/Zonos-v0.1-transformer'; + if (needsApiFetch) { const apiVoices = await fetchDeepinfraVoices(openApiKey); // Combine default voice with fetched voices @@ -117,7 +124,7 @@ export async function GET(req: NextRequest) { return NextResponse.json({ voices: [...defaultVoice, ...apiVoices] }); } } - + // For other Deepinfra models, return static defaults return NextResponse.json({ voices: getDefaultVoices(provider, model) }); } @@ -141,7 +148,7 @@ export async function GET(req: NextRequest) { } catch { console.log('Custom endpoint does not support voices, using defaults'); } - + // Fallback to default voices if API call fails return NextResponse.json({ voices: getDefaultVoices(provider, model) }); } diff --git a/src/app/api/user/claim/route.ts b/src/app/api/user/claim/route.ts new file mode 100644 index 0000000..3be0c30 --- /dev/null +++ b/src/app/api/user/claim/route.ts @@ -0,0 +1,32 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { claimAnonymousData, scanAndPopulateDB } from '@/lib/server/claim-data'; +import { auth } from '@/lib/server/auth'; + +export async function POST(req: NextRequest) { + try { + const session = await auth?.api.getSession({ headers: req.headers }); + if (!session || !session.user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + + const userId = session.user.id; + const { action } = await req.json(); // "claim" or "scan" or "start_fresh" (optional logic) + + if (action === 'scan') { + const counts = await scanAndPopulateDB(); + return NextResponse.json({ success: true, message: 'Scanned file system', ...counts }); + } + + // Default action: Claim + const result = await claimAnonymousData(userId); + + return NextResponse.json({ + success: true, + claimed: result + }); + + } catch (error) { + console.error('Error claiming data:', error); + return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 }); + } +} diff --git a/src/app/api/whisper/route.ts b/src/app/api/whisper/route.ts index a0d0da7..bcec3db 100644 --- a/src/app/api/whisper/route.ts +++ b/src/app/api/whisper/route.ts @@ -6,6 +6,7 @@ import { join } from 'path'; import { spawn } from 'child_process'; import type { TTSSentenceAlignment, TTSAudioBytes, TTSAudioBuffer } from '@/types/tts'; import { preprocessSentenceForAudio } from '@/lib/nlp'; +import { auth } from '@/lib/server/auth'; export const runtime = 'nodejs'; @@ -416,6 +417,12 @@ function makeCacheKey(input: WhisperRequestBody) { export async function POST(req: NextRequest) { try { + // Auth check - require session + const session = await auth?.api.getSession({ headers: req.headers }); + if (auth && !session?.user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + const body = (await req.json()) as WhisperRequestBody; const { text, audio, lang } = body; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 1eed6f7..5df0705 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -6,6 +6,7 @@ import { Footer } from "@/components/Footer"; import { Toaster } from 'react-hot-toast'; import { Analytics } from "@vercel/analytics/next"; import { isAuthEnabled, getAuthBaseUrl } from '@/lib/server/auth-config'; +import ClaimDataPopup from '@/components/ClaimDataModal'; export const metadata: Metadata = { title: "OpenReader WebUI", @@ -64,6 +65,7 @@ export default function RootLayout({ children }: { children: ReactNode }) {
+ {authEnabled && }
{children} diff --git a/src/components/ClaimDataModal.tsx b/src/components/ClaimDataModal.tsx new file mode 100644 index 0000000..fb42cf2 --- /dev/null +++ b/src/components/ClaimDataModal.tsx @@ -0,0 +1,154 @@ +'use client'; + +import { Fragment, useState, useEffect, useCallback } from 'react'; +import { + Dialog, + DialogPanel, + DialogTitle, + Transition, + TransitionChild, + Button, +} from '@headlessui/react'; +import { useAuthSession } from '@/hooks/useAuth'; +import { useRouter } from 'next/navigation'; + +export default function ClaimDataModal() { + const { data: sessionData } = useAuthSession(); + const router = useRouter(); + const [isOpen, setIsOpen] = useState(false); + const [hasChecked, setHasChecked] = useState(false); + const [isClaiming, setIsClaiming] = useState(false); + const user = sessionData?.user; + + const checkClaimableData = useCallback(async () => { + setHasChecked(true); + + try { + const res = await fetch('/api/user/claim', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ action: 'scan' }) + }); + if (res.ok) { + const data = await res.json(); + if (data.documents > 0 || data.audiobooks > 0) { + setIsOpen(true); + } + } + } catch (e) { + console.error(e); + } + }, []); + + useEffect(() => { + // Only check once per session if user is logged in (non-anonymous) + if (user && !user.isAnonymous && !hasChecked) { + checkClaimableData(); + } + }, [user, hasChecked, checkClaimableData]); + + const handleClaim = async () => { + setIsClaiming(true); + try { + const res = await fetch('/api/user/claim', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ action: 'claim' }) + }); + if (res.ok) { + const data = await res.json(); + alert(`Successfully claimed ${data.claimed.documents} documents and ${data.claimed.audiobooks} audiobooks!`); + + setIsOpen(false); + router.refresh(); + } + } catch { + alert('Failed to claim data.'); + } finally { + setIsClaiming(false); + } + }; + + const handleDismiss = () => { + // Close the modal for this session - will reappear on next page load/refresh + setIsOpen(false); + // Keep hasChecked = true so useEffect doesn't re-trigger in this session + }; + + return ( + + + +
+ + +
+
+ + + + Existing Data Found + + +

+ We found documents and audiobooks that were created before auth was enabled. + Would you like to claim them and add them to your account? +

+ +

+ ⚠️ First user to claim these files will own them and revoke access for anyone else. +

+ +
+ + +
+
+
+
+
+
+
+ ); +} diff --git a/src/components/auth/UserMenu.tsx b/src/components/auth/UserMenu.tsx index 77a7ff5..b6607a5 100644 --- a/src/components/auth/UserMenu.tsx +++ b/src/components/auth/UserMenu.tsx @@ -43,19 +43,14 @@ export function UserMenu({ className = '' }: { className?: string }) { } return ( -
-
- - {session.user.name || session.user.email || 'Account'} - - - {session.user.email} - -
+
+ + {session.user.email || 'Account'} +