diff --git a/docs/developer/database-schema.dbml b/docs/developer/database-schema.dbml index 1e2507db0a1d7..a21ffb9d548a7 100644 --- a/docs/developer/database-schema.dbml +++ b/docs/developer/database-schema.dbml @@ -122,6 +122,10 @@ table files { accessed_at "timestamp with time zone" [not null, default: `now()`] created_at "timestamp with time zone" [not null, default: `now()`] updated_at "timestamp with time zone" [not null, default: `now()`] + + indexes { + file_hash [name: 'file_hash_idx'] + } } table global_files { @@ -130,6 +134,7 @@ table global_files { size integer [not null] url text [not null] metadata jsonb + creator text [not null] created_at "timestamp with time zone" [not null, default: `now()`] accessed_at "timestamp with time zone" [not null, default: `now()`] } @@ -137,6 +142,7 @@ table global_files { table knowledge_base_files { knowledge_base_id text [not null] file_id text [not null] + user_id text [not null] created_at "timestamp with time zone" [not null, default: `now()`] indexes { @@ -161,6 +167,7 @@ table knowledge_bases { table message_chunks { message_id text chunk_id uuid + user_id text [not null] indexes { (chunk_id, message_id) [pk] @@ -176,6 +183,7 @@ table message_plugins { identifier text state jsonb error jsonb + user_id text [not null] } table message_queries { @@ -183,6 +191,7 @@ table message_queries { message_id text [not null] rewrite_query text user_query text + user_id text [not null] embeddings_id uuid } @@ -191,6 +200,7 @@ table message_query_chunks { query_id uuid chunk_id uuid similarity "numeric(6, 5)" + user_id text [not null] indexes { (chunk_id, id, query_id) [pk] @@ -202,6 +212,7 @@ table message_tts { content_md5 text file_id text voice text + user_id text [not null] } table message_translates { @@ -209,6 +220,7 @@ table message_translates { content text from text to text + user_id text [not null] } table messages { @@ -249,6 +261,7 @@ table messages { table messages_files { file_id text [not null] message_id text [not null] + user_id text [not null] indexes { (file_id, message_id) [pk] @@ -404,6 +417,7 @@ table rag_eval_evaluation_records { table agents_to_sessions { agent_id text [not null] session_id text [not null] + user_id text [not null] indexes { (agent_id, session_id) [pk] @@ -414,6 +428,7 @@ table file_chunks { file_id varchar chunk_id uuid created_at "timestamp with time zone" [not null, default: `now()`] + user_id text [not null] indexes { (file_id, chunk_id) [pk] @@ -423,6 +438,7 @@ table file_chunks { table files_to_sessions { file_id text [not null] session_id text [not null] + user_id text [not null] indexes { (file_id, session_id) [pk] diff --git a/package.json b/package.json index e05a6bbc07e2f..7bf06a69de929 100644 --- a/package.json +++ b/package.json @@ -65,8 +65,8 @@ "test": "npm run test-app && npm run test-server", "test-app": "vitest run --config vitest.config.ts", "test-app:coverage": "vitest run --config vitest.config.ts --coverage", - "test-server": "vitest run --config vitest.server.config.ts", - "test-server:coverage": "vitest run --config vitest.server.config.ts --coverage", + "test-server": "TEST_SERVER_DB=1 vitest run --config vitest.server.config.ts", + "test-server:coverage": "TEST_SERVER_DB=1 vitest run --config vitest.server.config.ts --coverage", "test:update": "vitest -u", "type-check": "tsc --noEmit", "webhook:ngrok": "ngrok http http://localhost:3011", diff --git a/src/database/client/db.ts b/src/database/client/db.ts index b6f47a8eed06e..d9f926088c024 100644 --- a/src/database/client/db.ts +++ b/src/database/client/db.ts @@ -146,13 +146,15 @@ export class DatabaseManager { private async migrate(skipMultiRun = false): Promise { if (this.isLocalDBSchemaSynced && skipMultiRun) return this.db; - const cacheHash = localStorage.getItem(pgliteSchemaHashCache); - const hash = Md5.hashStr(JSON.stringify(migrations)); - - // if hash is the same, no need to migrate - if (hash === cacheHash) { - this.isLocalDBSchemaSynced = true; - return this.db; + let hash: string | undefined; + if (typeof localStorage !== 'undefined') { + const cacheHash = localStorage.getItem(pgliteSchemaHashCache); + hash = Md5.hashStr(JSON.stringify(migrations)); + // if hash is the same, no need to migrate + if (hash === cacheHash) { + this.isLocalDBSchemaSynced = true; + return this.db; + } } const start = Date.now(); @@ -162,7 +164,11 @@ export class DatabaseManager { // refs: https://github.com/drizzle-team/drizzle-orm/discussions/2532 // @ts-expect-error await this.db.dialect.migrate(migrations, this.db.session, {}); - localStorage.setItem(pgliteSchemaHashCache, hash); + + if (typeof localStorage !== 'undefined' && hash) { + localStorage.setItem(pgliteSchemaHashCache, hash); + } + this.isLocalDBSchemaSynced = true; console.info(`🗂 Migration success, take ${Date.now() - start}ms`); diff --git a/src/database/client/migrations.json b/src/database/client/migrations.json index 5977821bfb457..adf6ec8e111d6 100644 --- a/src/database/client/migrations.json +++ b/src/database/client/migrations.json @@ -323,5 +323,67 @@ "bps": true, "folderMillis": 1741844738677, "hash": "2a7a98be2e49361391444d6fabf3fb5db0bcb6a65e5540e9c3d426ceeb1f7f3a" + }, + { + "sql": [ + "-- Complete User ID Migration Script\n-- Includes adding columns to all tables, populating data, and setting constraints\n\nBEGIN;", + "\n\nCREATE INDEX \"file_hash_idx\" ON \"files\" USING btree (\"file_hash\");", + "\n\n-- Step 1: Add nullable user_id columns to all required tables\nALTER TABLE \"global_files\" ADD COLUMN IF NOT EXISTS \"creator\" text;", + "\nALTER TABLE \"knowledge_base_files\" ADD COLUMN IF NOT EXISTS \"user_id\" text;", + "\nALTER TABLE \"message_chunks\" ADD COLUMN IF NOT EXISTS \"user_id\" text;", + "\nALTER TABLE \"message_plugins\" ADD COLUMN IF NOT EXISTS \"user_id\" text;", + "\nALTER TABLE \"message_queries\" ADD COLUMN IF NOT EXISTS \"user_id\" text;", + "\nALTER TABLE \"message_query_chunks\" ADD COLUMN IF NOT EXISTS \"user_id\" text;", + "\nALTER TABLE \"message_tts\" ADD COLUMN IF NOT EXISTS \"user_id\" text;", + "\nALTER TABLE \"message_translates\" ADD COLUMN IF NOT EXISTS \"user_id\" text;", + "\nALTER TABLE \"messages_files\" ADD COLUMN IF NOT EXISTS \"user_id\" text;", + "\nALTER TABLE \"agents_to_sessions\" ADD COLUMN IF NOT EXISTS \"user_id\" text;", + "\nALTER TABLE \"file_chunks\" ADD COLUMN IF NOT EXISTS \"user_id\" text;", + "\nALTER TABLE \"files_to_sessions\" ADD COLUMN IF NOT EXISTS \"user_id\" text;", + "\n\n-- Step 2: Populate user_id fields\n-- Retrieve user_id from associated tables\n\n-- Populate user_id for knowledge_base_files\nUPDATE \"knowledge_base_files\" AS kbf\nSET \"user_id\" = kb.\"user_id\"\n FROM \"knowledge_bases\" AS kb\nWHERE kbf.\"knowledge_base_id\" = kb.\"id\";", + "\n\n-- Populate user_id for message_chunks\nUPDATE \"message_chunks\" AS mc\nSET \"user_id\" = m.\"user_id\"\n FROM \"messages\" AS m\nWHERE mc.\"message_id\" = m.\"id\";", + "\n\n-- Populate user_id for message_plugins (directly from messages table)\nUPDATE \"message_plugins\" AS mp\nSET \"user_id\" = m.\"user_id\"\n FROM \"messages\" AS m\nWHERE mp.\"id\" = m.\"id\";", + "\n\n-- Populate user_id for message_queries\nUPDATE \"message_queries\" AS mq\nSET \"user_id\" = m.\"user_id\"\n FROM \"messages\" AS m\nWHERE mq.\"message_id\" = m.\"id\";", + "\n\n-- Populate user_id for message_query_chunks\nUPDATE \"message_query_chunks\" AS mqc\nSET \"user_id\" = mq.\"user_id\"\n FROM \"message_queries\" AS mq\nWHERE mqc.\"query_id\" = mq.\"id\";", + "\n\n-- Populate user_id for message_tts\nUPDATE \"message_tts\" AS mt\nSET \"user_id\" = m.\"user_id\"\n FROM \"messages\" AS m\nWHERE mt.\"id\" = m.\"id\";", + "\n\n-- Populate user_id for message_translates\nUPDATE \"message_translates\" AS mt\nSET \"user_id\" = m.\"user_id\"\n FROM \"messages\" AS m\nWHERE mt.\"id\" = m.\"id\";", + "\n\n-- Populate user_id for messages_files\nUPDATE \"messages_files\" AS mf\nSET \"user_id\" = m.\"user_id\"\n FROM \"messages\" AS m\nWHERE mf.\"message_id\" = m.\"id\";", + "\n\n-- Populate creator for global_files (get the first user who created the file from files table)\nUPDATE \"global_files\" AS gf\nSET \"creator\" = (\n SELECT f.\"user_id\"\n FROM \"files\" AS f\n WHERE f.\"file_hash\" = gf.\"hash_id\"\n ORDER BY f.\"created_at\" ASC\n LIMIT 1\n );", + "\n\n-- Delete global_files records where no user has used the file in the files table\nDELETE FROM \"global_files\"\nWHERE \"creator\" IS NULL;", + "\n\n-- Populate user_id for agents_to_sessions\nUPDATE \"agents_to_sessions\" AS ats\nSET \"user_id\" = a.\"user_id\"\n FROM \"agents\" AS a\nWHERE ats.\"agent_id\" = a.\"id\";", + "\n\n-- Populate user_id for file_chunks\nUPDATE \"file_chunks\" AS fc\nSET \"user_id\" = f.\"user_id\"\n FROM \"files\" AS f\nWHERE fc.\"file_id\" = f.\"id\";", + "\n\n-- Populate user_id for files_to_sessions\nUPDATE \"files_to_sessions\" AS fts\nSET \"user_id\" = f.\"user_id\"\n FROM \"files\" AS f\nWHERE fts.\"file_id\" = f.\"id\";", + "\n\n-- Get user_id from sessions table (handle potential NULL values)\nUPDATE \"files_to_sessions\" AS fts\nSET \"user_id\" = s.\"user_id\"\n FROM \"sessions\" AS s\nWHERE fts.\"session_id\" = s.\"id\" AND fts.\"user_id\" IS NULL;", + "\n\nUPDATE \"agents_to_sessions\" AS ats\nSET \"user_id\" = s.\"user_id\"\n FROM \"sessions\" AS s\nWHERE ats.\"session_id\" = s.\"id\" AND ats.\"user_id\" IS NULL;", + "\n\n-- Step 3: Check for any unpopulated records\nDO $$\nDECLARE\nkb_files_count INTEGER;\n msg_chunks_count INTEGER;\n msg_plugins_count INTEGER;\n msg_queries_count INTEGER;\n msg_query_chunks_count INTEGER;\n msg_tts_count INTEGER;\n msg_translates_count INTEGER;\n msgs_files_count INTEGER;\n agents_sessions_count INTEGER;\n file_chunks_count INTEGER;\n files_sessions_count INTEGER;\nBEGIN\nSELECT COUNT(*) INTO kb_files_count FROM \"knowledge_base_files\" WHERE \"user_id\" IS NULL;\nSELECT COUNT(*) INTO msg_chunks_count FROM \"message_chunks\" WHERE \"user_id\" IS NULL;\nSELECT COUNT(*) INTO msg_plugins_count FROM \"message_plugins\" WHERE \"user_id\" IS NULL;\nSELECT COUNT(*) INTO msg_queries_count FROM \"message_queries\" WHERE \"user_id\" IS NULL;\nSELECT COUNT(*) INTO msg_query_chunks_count FROM \"message_query_chunks\" WHERE \"user_id\" IS NULL;\nSELECT COUNT(*) INTO msg_tts_count FROM \"message_tts\" WHERE \"user_id\" IS NULL;\nSELECT COUNT(*) INTO msg_translates_count FROM \"message_translates\" WHERE \"user_id\" IS NULL;\nSELECT COUNT(*) INTO msgs_files_count FROM \"messages_files\" WHERE \"user_id\" IS NULL;\nSELECT COUNT(*) INTO agents_sessions_count FROM \"agents_to_sessions\" WHERE \"user_id\" IS NULL;\nSELECT COUNT(*) INTO file_chunks_count FROM \"file_chunks\" WHERE \"user_id\" IS NULL;\nSELECT COUNT(*) INTO files_sessions_count FROM \"files_to_sessions\" WHERE \"user_id\" IS NULL;\n\nIF kb_files_count > 0 OR msg_chunks_count > 0 OR msg_plugins_count > 0 OR\n msg_queries_count > 0 OR msg_query_chunks_count > 0 OR msg_tts_count > 0 OR\n msg_translates_count > 0 OR msgs_files_count > 0 OR agents_sessions_count > 0 OR\n file_chunks_count > 0 OR files_sessions_count > 0 THEN\n RAISE EXCEPTION 'There are records with NULL user_id values that could not be populated';\nEND IF;\nEND $$;", + "\n\n-- Step 4: Add NOT NULL constraints and foreign keys\nALTER TABLE \"knowledge_base_files\" ALTER COLUMN \"user_id\" SET NOT NULL;", + "\nALTER TABLE \"message_chunks\" ALTER COLUMN \"user_id\" SET NOT NULL;", + "\nALTER TABLE \"message_plugins\" ALTER COLUMN \"user_id\" SET NOT NULL;", + "\nALTER TABLE \"message_queries\" ALTER COLUMN \"user_id\" SET NOT NULL;", + "\nALTER TABLE \"message_query_chunks\" ALTER COLUMN \"user_id\" SET NOT NULL;", + "\nALTER TABLE \"message_tts\" ALTER COLUMN \"user_id\" SET NOT NULL;", + "\nALTER TABLE \"message_translates\" ALTER COLUMN \"user_id\" SET NOT NULL;", + "\nALTER TABLE \"messages_files\" ALTER COLUMN \"user_id\" SET NOT NULL;", + "\nALTER TABLE \"agents_to_sessions\" ALTER COLUMN \"user_id\" SET NOT NULL;", + "\nALTER TABLE \"file_chunks\" ALTER COLUMN \"user_id\" SET NOT NULL;", + "\nALTER TABLE \"files_to_sessions\" ALTER COLUMN \"user_id\" SET NOT NULL;", + "\n\n-- Add foreign key constraints\nALTER TABLE \"global_files\"\n ADD CONSTRAINT \"global_files_creator_users_id_fk\"\n FOREIGN KEY (\"creator\") REFERENCES \"public\".\"users\"(\"id\")\n ON DELETE SET NULL ON UPDATE NO ACTION;", + "\n\nALTER TABLE \"knowledge_base_files\"\n ADD CONSTRAINT \"knowledge_base_files_user_id_users_id_fk\"\n FOREIGN KEY (\"user_id\") REFERENCES \"public\".\"users\"(\"id\")\n ON DELETE CASCADE ON UPDATE NO ACTION;", + "\n\nALTER TABLE \"message_chunks\"\n ADD CONSTRAINT \"message_chunks_user_id_users_id_fk\"\n FOREIGN KEY (\"user_id\") REFERENCES \"public\".\"users\"(\"id\")\n ON DELETE CASCADE ON UPDATE NO ACTION;", + "\n\nALTER TABLE \"message_plugins\"\n ADD CONSTRAINT \"message_plugins_user_id_users_id_fk\"\n FOREIGN KEY (\"user_id\") REFERENCES \"public\".\"users\"(\"id\")\n ON DELETE CASCADE ON UPDATE NO ACTION;", + "\n\nALTER TABLE \"message_queries\"\n ADD CONSTRAINT \"message_queries_user_id_users_id_fk\"\n FOREIGN KEY (\"user_id\") REFERENCES \"public\".\"users\"(\"id\")\n ON DELETE CASCADE ON UPDATE NO ACTION;", + "\n\nALTER TABLE \"message_query_chunks\"\n ADD CONSTRAINT \"message_query_chunks_user_id_users_id_fk\"\n FOREIGN KEY (\"user_id\") REFERENCES \"public\".\"users\"(\"id\")\n ON DELETE CASCADE ON UPDATE NO ACTION;", + "\n\nALTER TABLE \"message_tts\"\n ADD CONSTRAINT \"message_tts_user_id_users_id_fk\"\n FOREIGN KEY (\"user_id\") REFERENCES \"public\".\"users\"(\"id\")\n ON DELETE CASCADE ON UPDATE NO ACTION;", + "\n\nALTER TABLE \"message_translates\"\n ADD CONSTRAINT \"message_translates_user_id_users_id_fk\"\n FOREIGN KEY (\"user_id\") REFERENCES \"public\".\"users\"(\"id\")\n ON DELETE CASCADE ON UPDATE NO ACTION;", + "\n\nALTER TABLE \"messages_files\"\n ADD CONSTRAINT \"messages_files_user_id_users_id_fk\"\n FOREIGN KEY (\"user_id\") REFERENCES \"public\".\"users\"(\"id\")\n ON DELETE CASCADE ON UPDATE NO ACTION;", + "\n\nALTER TABLE \"agents_to_sessions\"\n ADD CONSTRAINT \"agents_to_sessions_user_id_users_id_fk\"\n FOREIGN KEY (\"user_id\") REFERENCES \"public\".\"users\"(\"id\")\n ON DELETE CASCADE ON UPDATE NO ACTION;", + "\n\nALTER TABLE \"file_chunks\"\n ADD CONSTRAINT \"file_chunks_user_id_users_id_fk\"\n FOREIGN KEY (\"user_id\") REFERENCES \"public\".\"users\"(\"id\")\n ON DELETE CASCADE ON UPDATE NO ACTION;", + "\n\nALTER TABLE \"files_to_sessions\"\n ADD CONSTRAINT \"files_to_sessions_user_id_users_id_fk\"\n FOREIGN KEY (\"user_id\") REFERENCES \"public\".\"users\"(\"id\")\n ON DELETE CASCADE ON UPDATE NO ACTION;", + "\n\nCOMMIT;", + "\n" + ], + "bps": true, + "folderMillis": 1742269437903, + "hash": "89e91285be422d5f44511c7405f57b57f8dfda4f0304126ae9b0f266e5fd60f1" } ] \ No newline at end of file diff --git a/src/database/migrations/0017_add_user_id_to_tables.sql b/src/database/migrations/0017_add_user_id_to_tables.sql new file mode 100644 index 0000000000000..4931e62f1ea35 --- /dev/null +++ b/src/database/migrations/0017_add_user_id_to_tables.sql @@ -0,0 +1,225 @@ +-- Complete User ID Migration Script +-- Includes adding columns to all tables, populating data, and setting constraints + +BEGIN;--> statement-breakpoint + +CREATE INDEX "file_hash_idx" ON "files" USING btree ("file_hash");--> statement-breakpoint + +-- Step 1: Add nullable user_id columns to all required tables +ALTER TABLE "global_files" ADD COLUMN IF NOT EXISTS "creator" text;--> statement-breakpoint +ALTER TABLE "knowledge_base_files" ADD COLUMN IF NOT EXISTS "user_id" text;--> statement-breakpoint +ALTER TABLE "message_chunks" ADD COLUMN IF NOT EXISTS "user_id" text;--> statement-breakpoint +ALTER TABLE "message_plugins" ADD COLUMN IF NOT EXISTS "user_id" text;--> statement-breakpoint +ALTER TABLE "message_queries" ADD COLUMN IF NOT EXISTS "user_id" text;--> statement-breakpoint +ALTER TABLE "message_query_chunks" ADD COLUMN IF NOT EXISTS "user_id" text;--> statement-breakpoint +ALTER TABLE "message_tts" ADD COLUMN IF NOT EXISTS "user_id" text;--> statement-breakpoint +ALTER TABLE "message_translates" ADD COLUMN IF NOT EXISTS "user_id" text;--> statement-breakpoint +ALTER TABLE "messages_files" ADD COLUMN IF NOT EXISTS "user_id" text;--> statement-breakpoint +ALTER TABLE "agents_to_sessions" ADD COLUMN IF NOT EXISTS "user_id" text;--> statement-breakpoint +ALTER TABLE "file_chunks" ADD COLUMN IF NOT EXISTS "user_id" text;--> statement-breakpoint +ALTER TABLE "files_to_sessions" ADD COLUMN IF NOT EXISTS "user_id" text;--> statement-breakpoint + +-- Step 2: Populate user_id fields +-- Retrieve user_id from associated tables + +-- Populate user_id for knowledge_base_files +UPDATE "knowledge_base_files" AS kbf +SET "user_id" = kb."user_id" + FROM "knowledge_bases" AS kb +WHERE kbf."knowledge_base_id" = kb."id";--> statement-breakpoint + +-- Populate user_id for message_chunks +UPDATE "message_chunks" AS mc +SET "user_id" = m."user_id" + FROM "messages" AS m +WHERE mc."message_id" = m."id";--> statement-breakpoint + +-- Populate user_id for message_plugins (directly from messages table) +UPDATE "message_plugins" AS mp +SET "user_id" = m."user_id" + FROM "messages" AS m +WHERE mp."id" = m."id";--> statement-breakpoint + +-- Populate user_id for message_queries +UPDATE "message_queries" AS mq +SET "user_id" = m."user_id" + FROM "messages" AS m +WHERE mq."message_id" = m."id";--> statement-breakpoint + +-- Populate user_id for message_query_chunks +UPDATE "message_query_chunks" AS mqc +SET "user_id" = mq."user_id" + FROM "message_queries" AS mq +WHERE mqc."query_id" = mq."id";--> statement-breakpoint + +-- Populate user_id for message_tts +UPDATE "message_tts" AS mt +SET "user_id" = m."user_id" + FROM "messages" AS m +WHERE mt."id" = m."id";--> statement-breakpoint + +-- Populate user_id for message_translates +UPDATE "message_translates" AS mt +SET "user_id" = m."user_id" + FROM "messages" AS m +WHERE mt."id" = m."id";--> statement-breakpoint + +-- Populate user_id for messages_files +UPDATE "messages_files" AS mf +SET "user_id" = m."user_id" + FROM "messages" AS m +WHERE mf."message_id" = m."id";--> statement-breakpoint + +-- Populate creator for global_files (get the first user who created the file from files table) +UPDATE "global_files" AS gf +SET "creator" = ( + SELECT f."user_id" + FROM "files" AS f + WHERE f."file_hash" = gf."hash_id" + ORDER BY f."created_at" ASC + LIMIT 1 + );--> statement-breakpoint + +-- Delete global_files records where no user has used the file in the files table +DELETE FROM "global_files" +WHERE "creator" IS NULL;--> statement-breakpoint + +-- Populate user_id for agents_to_sessions +UPDATE "agents_to_sessions" AS ats +SET "user_id" = a."user_id" + FROM "agents" AS a +WHERE ats."agent_id" = a."id";--> statement-breakpoint + +-- Populate user_id for file_chunks +UPDATE "file_chunks" AS fc +SET "user_id" = f."user_id" + FROM "files" AS f +WHERE fc."file_id" = f."id";--> statement-breakpoint + +-- Populate user_id for files_to_sessions +UPDATE "files_to_sessions" AS fts +SET "user_id" = f."user_id" + FROM "files" AS f +WHERE fts."file_id" = f."id";--> statement-breakpoint + +-- Get user_id from sessions table (handle potential NULL values) +UPDATE "files_to_sessions" AS fts +SET "user_id" = s."user_id" + FROM "sessions" AS s +WHERE fts."session_id" = s."id" AND fts."user_id" IS NULL;--> statement-breakpoint + +UPDATE "agents_to_sessions" AS ats +SET "user_id" = s."user_id" + FROM "sessions" AS s +WHERE ats."session_id" = s."id" AND ats."user_id" IS NULL;--> statement-breakpoint + +-- Step 3: Check for any unpopulated records +DO $$ +DECLARE +kb_files_count INTEGER; + msg_chunks_count INTEGER; + msg_plugins_count INTEGER; + msg_queries_count INTEGER; + msg_query_chunks_count INTEGER; + msg_tts_count INTEGER; + msg_translates_count INTEGER; + msgs_files_count INTEGER; + agents_sessions_count INTEGER; + file_chunks_count INTEGER; + files_sessions_count INTEGER; +BEGIN +SELECT COUNT(*) INTO kb_files_count FROM "knowledge_base_files" WHERE "user_id" IS NULL; +SELECT COUNT(*) INTO msg_chunks_count FROM "message_chunks" WHERE "user_id" IS NULL; +SELECT COUNT(*) INTO msg_plugins_count FROM "message_plugins" WHERE "user_id" IS NULL; +SELECT COUNT(*) INTO msg_queries_count FROM "message_queries" WHERE "user_id" IS NULL; +SELECT COUNT(*) INTO msg_query_chunks_count FROM "message_query_chunks" WHERE "user_id" IS NULL; +SELECT COUNT(*) INTO msg_tts_count FROM "message_tts" WHERE "user_id" IS NULL; +SELECT COUNT(*) INTO msg_translates_count FROM "message_translates" WHERE "user_id" IS NULL; +SELECT COUNT(*) INTO msgs_files_count FROM "messages_files" WHERE "user_id" IS NULL; +SELECT COUNT(*) INTO agents_sessions_count FROM "agents_to_sessions" WHERE "user_id" IS NULL; +SELECT COUNT(*) INTO file_chunks_count FROM "file_chunks" WHERE "user_id" IS NULL; +SELECT COUNT(*) INTO files_sessions_count FROM "files_to_sessions" WHERE "user_id" IS NULL; + +IF kb_files_count > 0 OR msg_chunks_count > 0 OR msg_plugins_count > 0 OR + msg_queries_count > 0 OR msg_query_chunks_count > 0 OR msg_tts_count > 0 OR + msg_translates_count > 0 OR msgs_files_count > 0 OR agents_sessions_count > 0 OR + file_chunks_count > 0 OR files_sessions_count > 0 THEN + RAISE EXCEPTION 'There are records with NULL user_id values that could not be populated'; +END IF; +END $$;--> statement-breakpoint + +-- Step 4: Add NOT NULL constraints and foreign keys +ALTER TABLE "knowledge_base_files" ALTER COLUMN "user_id" SET NOT NULL;--> statement-breakpoint +ALTER TABLE "message_chunks" ALTER COLUMN "user_id" SET NOT NULL;--> statement-breakpoint +ALTER TABLE "message_plugins" ALTER COLUMN "user_id" SET NOT NULL;--> statement-breakpoint +ALTER TABLE "message_queries" ALTER COLUMN "user_id" SET NOT NULL;--> statement-breakpoint +ALTER TABLE "message_query_chunks" ALTER COLUMN "user_id" SET NOT NULL;--> statement-breakpoint +ALTER TABLE "message_tts" ALTER COLUMN "user_id" SET NOT NULL;--> statement-breakpoint +ALTER TABLE "message_translates" ALTER COLUMN "user_id" SET NOT NULL;--> statement-breakpoint +ALTER TABLE "messages_files" ALTER COLUMN "user_id" SET NOT NULL;--> statement-breakpoint +ALTER TABLE "agents_to_sessions" ALTER COLUMN "user_id" SET NOT NULL;--> statement-breakpoint +ALTER TABLE "file_chunks" ALTER COLUMN "user_id" SET NOT NULL;--> statement-breakpoint +ALTER TABLE "files_to_sessions" ALTER COLUMN "user_id" SET NOT NULL;--> statement-breakpoint + +-- Add foreign key constraints +ALTER TABLE "global_files" + ADD CONSTRAINT "global_files_creator_users_id_fk" + FOREIGN KEY ("creator") REFERENCES "public"."users"("id") + ON DELETE SET NULL ON UPDATE NO ACTION;--> statement-breakpoint + +ALTER TABLE "knowledge_base_files" + ADD CONSTRAINT "knowledge_base_files_user_id_users_id_fk" + FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") + ON DELETE CASCADE ON UPDATE NO ACTION;--> statement-breakpoint + +ALTER TABLE "message_chunks" + ADD CONSTRAINT "message_chunks_user_id_users_id_fk" + FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") + ON DELETE CASCADE ON UPDATE NO ACTION;--> statement-breakpoint + +ALTER TABLE "message_plugins" + ADD CONSTRAINT "message_plugins_user_id_users_id_fk" + FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") + ON DELETE CASCADE ON UPDATE NO ACTION;--> statement-breakpoint + +ALTER TABLE "message_queries" + ADD CONSTRAINT "message_queries_user_id_users_id_fk" + FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") + ON DELETE CASCADE ON UPDATE NO ACTION;--> statement-breakpoint + +ALTER TABLE "message_query_chunks" + ADD CONSTRAINT "message_query_chunks_user_id_users_id_fk" + FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") + ON DELETE CASCADE ON UPDATE NO ACTION;--> statement-breakpoint + +ALTER TABLE "message_tts" + ADD CONSTRAINT "message_tts_user_id_users_id_fk" + FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") + ON DELETE CASCADE ON UPDATE NO ACTION;--> statement-breakpoint + +ALTER TABLE "message_translates" + ADD CONSTRAINT "message_translates_user_id_users_id_fk" + FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") + ON DELETE CASCADE ON UPDATE NO ACTION;--> statement-breakpoint + +ALTER TABLE "messages_files" + ADD CONSTRAINT "messages_files_user_id_users_id_fk" + FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") + ON DELETE CASCADE ON UPDATE NO ACTION;--> statement-breakpoint + +ALTER TABLE "agents_to_sessions" + ADD CONSTRAINT "agents_to_sessions_user_id_users_id_fk" + FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") + ON DELETE CASCADE ON UPDATE NO ACTION;--> statement-breakpoint + +ALTER TABLE "file_chunks" + ADD CONSTRAINT "file_chunks_user_id_users_id_fk" + FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") + ON DELETE CASCADE ON UPDATE NO ACTION;--> statement-breakpoint + +ALTER TABLE "files_to_sessions" + ADD CONSTRAINT "files_to_sessions_user_id_users_id_fk" + FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") + ON DELETE CASCADE ON UPDATE NO ACTION;--> statement-breakpoint + +COMMIT;--> statement-breakpoint diff --git a/src/database/migrations/meta/0017_snapshot.json b/src/database/migrations/meta/0017_snapshot.json new file mode 100644 index 0000000000000..1ccada6208802 --- /dev/null +++ b/src/database/migrations/meta/0017_snapshot.json @@ -0,0 +1,3858 @@ +{ + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + }, + "dialect": "postgresql", + "enums": {}, + "id": "b694d492-9c9c-4628-8f0e-e730ebbb7d09", + "policies": {}, + "prevId": "f0154cbe-26e2-462d-bac7-d88f54e49c6f", + "roles": {}, + "schemas": {}, + "sequences": {}, + "tables": { + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "background_color": { + "name": "background_color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "plugins": { + "name": "plugins", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_config": { + "name": "chat_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "few_shots": { + "name": "few_shots", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "params": { + "name": "params", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "system_role": { + "name": "system_role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tts": { + "name": "tts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "agents_user_id_users_id_fk": { + "name": "agents_user_id_users_id_fk", + "tableFrom": "agents", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "agents_slug_unique": { + "name": "agents_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents_files": { + "name": "agents_files", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "agents_files_file_id_files_id_fk": { + "name": "agents_files_file_id_files_id_fk", + "tableFrom": "agents_files", + "tableTo": "files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agents_files_agent_id_agents_id_fk": { + "name": "agents_files_agent_id_agents_id_fk", + "tableFrom": "agents_files", + "tableTo": "agents", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agents_files_user_id_users_id_fk": { + "name": "agents_files_user_id_users_id_fk", + "tableFrom": "agents_files", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "agents_files_file_id_agent_id_user_id_pk": { + "name": "agents_files_file_id_agent_id_user_id_pk", + "columns": ["file_id", "agent_id", "user_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents_knowledge_bases": { + "name": "agents_knowledge_bases", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "agents_knowledge_bases_agent_id_agents_id_fk": { + "name": "agents_knowledge_bases_agent_id_agents_id_fk", + "tableFrom": "agents_knowledge_bases", + "tableTo": "agents", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agents_knowledge_bases_knowledge_base_id_knowledge_bases_id_fk": { + "name": "agents_knowledge_bases_knowledge_base_id_knowledge_bases_id_fk", + "tableFrom": "agents_knowledge_bases", + "tableTo": "knowledge_bases", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agents_knowledge_bases_user_id_users_id_fk": { + "name": "agents_knowledge_bases_user_id_users_id_fk", + "tableFrom": "agents_knowledge_bases", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "agents_knowledge_bases_agent_id_knowledge_base_id_pk": { + "name": "agents_knowledge_bases_agent_id_knowledge_base_id_pk", + "columns": ["agent_id", "knowledge_base_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_models": { + "name": "ai_models", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(150)", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization": { + "name": "organization", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'chat'" + }, + "sort": { + "name": "sort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pricing": { + "name": "pricing", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "parameters": { + "name": "parameters", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "abilities": { + "name": "abilities", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "context_window_tokens": { + "name": "context_window_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "released_at": { + "name": "released_at", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "ai_models_user_id_users_id_fk": { + "name": "ai_models_user_id_users_id_fk", + "tableFrom": "ai_models", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ai_models_id_provider_id_user_id_pk": { + "name": "ai_models_id_provider_id_user_id_pk", + "columns": ["id", "provider_id", "user_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_providers": { + "name": "ai_providers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sort": { + "name": "sort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "fetch_on_client": { + "name": "fetch_on_client", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "check_model": { + "name": "check_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key_vaults": { + "name": "key_vaults", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "ai_providers_user_id_users_id_fk": { + "name": "ai_providers_user_id_users_id_fk", + "tableFrom": "ai_providers", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ai_providers_id_user_id_pk": { + "name": "ai_providers_id_user_id_pk", + "columns": ["id", "user_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.async_tasks": { + "name": "async_tasks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "duration": { + "name": "duration", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "async_tasks_user_id_users_id_fk": { + "name": "async_tasks_user_id_users_id_fk", + "tableFrom": "async_tasks", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.files": { + "name": "files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_type": { + "name": "file_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "file_hash": { + "name": "file_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "chunk_task_id": { + "name": "chunk_task_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "embedding_task_id": { + "name": "embedding_task_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "file_hash_idx": { + "name": "file_hash_idx", + "columns": [ + { + "expression": "file_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "files_user_id_users_id_fk": { + "name": "files_user_id_users_id_fk", + "tableFrom": "files", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "files_file_hash_global_files_hash_id_fk": { + "name": "files_file_hash_global_files_hash_id_fk", + "tableFrom": "files", + "tableTo": "global_files", + "columnsFrom": ["file_hash"], + "columnsTo": ["hash_id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "files_chunk_task_id_async_tasks_id_fk": { + "name": "files_chunk_task_id_async_tasks_id_fk", + "tableFrom": "files", + "tableTo": "async_tasks", + "columnsFrom": ["chunk_task_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "files_embedding_task_id_async_tasks_id_fk": { + "name": "files_embedding_task_id_async_tasks_id_fk", + "tableFrom": "files", + "tableTo": "async_tasks", + "columnsFrom": ["embedding_task_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.global_files": { + "name": "global_files", + "schema": "", + "columns": { + "hash_id": { + "name": "hash_id", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "file_type": { + "name": "file_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "creator": { + "name": "creator", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "global_files_creator_users_id_fk": { + "name": "global_files_creator_users_id_fk", + "tableFrom": "global_files", + "tableTo": "users", + "columnsFrom": ["creator"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_base_files": { + "name": "knowledge_base_files", + "schema": "", + "columns": { + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "knowledge_base_files_knowledge_base_id_knowledge_bases_id_fk": { + "name": "knowledge_base_files_knowledge_base_id_knowledge_bases_id_fk", + "tableFrom": "knowledge_base_files", + "tableTo": "knowledge_bases", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_base_files_file_id_files_id_fk": { + "name": "knowledge_base_files_file_id_files_id_fk", + "tableFrom": "knowledge_base_files", + "tableTo": "files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_base_files_user_id_users_id_fk": { + "name": "knowledge_base_files_user_id_users_id_fk", + "tableFrom": "knowledge_base_files", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "knowledge_base_files_knowledge_base_id_file_id_pk": { + "name": "knowledge_base_files_knowledge_base_id_file_id_pk", + "columns": ["knowledge_base_id", "file_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_bases": { + "name": "knowledge_bases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "knowledge_bases_user_id_users_id_fk": { + "name": "knowledge_bases_user_id_users_id_fk", + "tableFrom": "knowledge_bases", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_chunks": { + "name": "message_chunks", + "schema": "", + "columns": { + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "chunk_id": { + "name": "chunk_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "message_chunks_message_id_messages_id_fk": { + "name": "message_chunks_message_id_messages_id_fk", + "tableFrom": "message_chunks", + "tableTo": "messages", + "columnsFrom": ["message_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_chunks_chunk_id_chunks_id_fk": { + "name": "message_chunks_chunk_id_chunks_id_fk", + "tableFrom": "message_chunks", + "tableTo": "chunks", + "columnsFrom": ["chunk_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_chunks_user_id_users_id_fk": { + "name": "message_chunks_user_id_users_id_fk", + "tableFrom": "message_chunks", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "message_chunks_chunk_id_message_id_pk": { + "name": "message_chunks_chunk_id_message_id_pk", + "columns": ["chunk_id", "message_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_plugins": { + "name": "message_plugins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tool_call_id": { + "name": "tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'default'" + }, + "api_name": { + "name": "api_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "arguments": { + "name": "arguments", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "message_plugins_id_messages_id_fk": { + "name": "message_plugins_id_messages_id_fk", + "tableFrom": "message_plugins", + "tableTo": "messages", + "columnsFrom": ["id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_plugins_user_id_users_id_fk": { + "name": "message_plugins_user_id_users_id_fk", + "tableFrom": "message_plugins", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_queries": { + "name": "message_queries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rewrite_query": { + "name": "rewrite_query", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_query": { + "name": "user_query", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "embeddings_id": { + "name": "embeddings_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "message_queries_message_id_messages_id_fk": { + "name": "message_queries_message_id_messages_id_fk", + "tableFrom": "message_queries", + "tableTo": "messages", + "columnsFrom": ["message_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_queries_user_id_users_id_fk": { + "name": "message_queries_user_id_users_id_fk", + "tableFrom": "message_queries", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_queries_embeddings_id_embeddings_id_fk": { + "name": "message_queries_embeddings_id_embeddings_id_fk", + "tableFrom": "message_queries", + "tableTo": "embeddings", + "columnsFrom": ["embeddings_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_query_chunks": { + "name": "message_query_chunks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "query_id": { + "name": "query_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "chunk_id": { + "name": "chunk_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "similarity": { + "name": "similarity", + "type": "numeric(6, 5)", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "message_query_chunks_id_messages_id_fk": { + "name": "message_query_chunks_id_messages_id_fk", + "tableFrom": "message_query_chunks", + "tableTo": "messages", + "columnsFrom": ["id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_query_chunks_query_id_message_queries_id_fk": { + "name": "message_query_chunks_query_id_message_queries_id_fk", + "tableFrom": "message_query_chunks", + "tableTo": "message_queries", + "columnsFrom": ["query_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_query_chunks_chunk_id_chunks_id_fk": { + "name": "message_query_chunks_chunk_id_chunks_id_fk", + "tableFrom": "message_query_chunks", + "tableTo": "chunks", + "columnsFrom": ["chunk_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_query_chunks_user_id_users_id_fk": { + "name": "message_query_chunks_user_id_users_id_fk", + "tableFrom": "message_query_chunks", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "message_query_chunks_chunk_id_id_query_id_pk": { + "name": "message_query_chunks_chunk_id_id_query_id_pk", + "columns": ["chunk_id", "id", "query_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_tts": { + "name": "message_tts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "content_md5": { + "name": "content_md5", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "voice": { + "name": "voice", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "message_tts_id_messages_id_fk": { + "name": "message_tts_id_messages_id_fk", + "tableFrom": "message_tts", + "tableTo": "messages", + "columnsFrom": ["id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_tts_file_id_files_id_fk": { + "name": "message_tts_file_id_files_id_fk", + "tableFrom": "message_tts", + "tableTo": "files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_tts_user_id_users_id_fk": { + "name": "message_tts_user_id_users_id_fk", + "tableFrom": "message_tts", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_translates": { + "name": "message_translates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "from": { + "name": "from", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "to": { + "name": "to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "message_translates_id_messages_id_fk": { + "name": "message_translates_id_messages_id_fk", + "tableFrom": "message_translates", + "tableTo": "messages", + "columnsFrom": ["id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_translates_user_id_users_id_fk": { + "name": "message_translates_user_id_users_id_fk", + "tableFrom": "message_translates", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.messages": { + "name": "messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reasoning": { + "name": "reasoning", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "search": { + "name": "search", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favorite": { + "name": "favorite", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "error": { + "name": "error", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "tools": { + "name": "tools", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "trace_id": { + "name": "trace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "observation_id": { + "name": "observation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "topic_id": { + "name": "topic_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "quota_id": { + "name": "quota_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "messages_created_at_idx": { + "name": "messages_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "message_client_id_user_unique": { + "name": "message_client_id_user_unique", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "messages_topic_id_idx": { + "name": "messages_topic_id_idx", + "columns": [ + { + "expression": "topic_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "messages_parent_id_idx": { + "name": "messages_parent_id_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "messages_quota_id_idx": { + "name": "messages_quota_id_idx", + "columns": [ + { + "expression": "quota_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "messages_user_id_users_id_fk": { + "name": "messages_user_id_users_id_fk", + "tableFrom": "messages", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "messages_session_id_sessions_id_fk": { + "name": "messages_session_id_sessions_id_fk", + "tableFrom": "messages", + "tableTo": "sessions", + "columnsFrom": ["session_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "messages_topic_id_topics_id_fk": { + "name": "messages_topic_id_topics_id_fk", + "tableFrom": "messages", + "tableTo": "topics", + "columnsFrom": ["topic_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "messages_thread_id_threads_id_fk": { + "name": "messages_thread_id_threads_id_fk", + "tableFrom": "messages", + "tableTo": "threads", + "columnsFrom": ["thread_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "messages_parent_id_messages_id_fk": { + "name": "messages_parent_id_messages_id_fk", + "tableFrom": "messages", + "tableTo": "messages", + "columnsFrom": ["parent_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "messages_quota_id_messages_id_fk": { + "name": "messages_quota_id_messages_id_fk", + "tableFrom": "messages", + "tableTo": "messages", + "columnsFrom": ["quota_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "messages_agent_id_agents_id_fk": { + "name": "messages_agent_id_agents_id_fk", + "tableFrom": "messages", + "tableTo": "agents", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.messages_files": { + "name": "messages_files", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "messages_files_file_id_files_id_fk": { + "name": "messages_files_file_id_files_id_fk", + "tableFrom": "messages_files", + "tableTo": "files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "messages_files_message_id_messages_id_fk": { + "name": "messages_files_message_id_messages_id_fk", + "tableFrom": "messages_files", + "tableTo": "messages", + "columnsFrom": ["message_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "messages_files_user_id_users_id_fk": { + "name": "messages_files_user_id_users_id_fk", + "tableFrom": "messages_files", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "messages_files_file_id_message_id_pk": { + "name": "messages_files_file_id_message_id_pk", + "columns": ["file_id", "message_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.nextauth_accounts": { + "name": "nextauth_accounts", + "schema": "", + "columns": { + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "nextauth_accounts_userId_users_id_fk": { + "name": "nextauth_accounts_userId_users_id_fk", + "tableFrom": "nextauth_accounts", + "tableTo": "users", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "nextauth_accounts_provider_providerAccountId_pk": { + "name": "nextauth_accounts_provider_providerAccountId_pk", + "columns": ["provider", "providerAccountId"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.nextauth_authenticators": { + "name": "nextauth_authenticators", + "schema": "", + "columns": { + "counter": { + "name": "counter", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "credentialBackedUp": { + "name": "credentialBackedUp", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "credentialDeviceType": { + "name": "credentialDeviceType", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credentialID": { + "name": "credentialID", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credentialPublicKey": { + "name": "credentialPublicKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "transports": { + "name": "transports", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "nextauth_authenticators_userId_users_id_fk": { + "name": "nextauth_authenticators_userId_users_id_fk", + "tableFrom": "nextauth_authenticators", + "tableTo": "users", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "nextauth_authenticators_userId_credentialID_pk": { + "name": "nextauth_authenticators_userId_credentialID_pk", + "columns": ["userId", "credentialID"] + } + }, + "uniqueConstraints": { + "nextauth_authenticators_credentialID_unique": { + "name": "nextauth_authenticators_credentialID_unique", + "nullsNotDistinct": false, + "columns": ["credentialID"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.nextauth_sessions": { + "name": "nextauth_sessions", + "schema": "", + "columns": { + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "sessionToken": { + "name": "sessionToken", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "nextauth_sessions_userId_users_id_fk": { + "name": "nextauth_sessions_userId_users_id_fk", + "tableFrom": "nextauth_sessions", + "tableTo": "users", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.nextauth_verificationtokens": { + "name": "nextauth_verificationtokens", + "schema": "", + "columns": { + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "nextauth_verificationtokens_identifier_token_pk": { + "name": "nextauth_verificationtokens_identifier_token_pk", + "columns": ["identifier", "token"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chunks": { + "name": "chunks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "abstract": { + "name": "abstract", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "index": { + "name": "index", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "chunks_user_id_users_id_fk": { + "name": "chunks_user_id_users_id_fk", + "tableFrom": "chunks", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.embeddings": { + "name": "embeddings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chunk_id": { + "name": "chunk_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "embeddings": { + "name": "embeddings", + "type": "vector(1024)", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "embeddings_chunk_id_chunks_id_fk": { + "name": "embeddings_chunk_id_chunks_id_fk", + "tableFrom": "embeddings", + "tableTo": "chunks", + "columnsFrom": ["chunk_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "embeddings_user_id_users_id_fk": { + "name": "embeddings_user_id_users_id_fk", + "tableFrom": "embeddings", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "embeddings_chunk_id_unique": { + "name": "embeddings_chunk_id_unique", + "nullsNotDistinct": false, + "columns": ["chunk_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.unstructured_chunks": { + "name": "unstructured_chunks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "index": { + "name": "index", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "parent_id": { + "name": "parent_id", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "composite_id": { + "name": "composite_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_id": { + "name": "file_id", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "unstructured_chunks_composite_id_chunks_id_fk": { + "name": "unstructured_chunks_composite_id_chunks_id_fk", + "tableFrom": "unstructured_chunks", + "tableTo": "chunks", + "columnsFrom": ["composite_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "unstructured_chunks_user_id_users_id_fk": { + "name": "unstructured_chunks_user_id_users_id_fk", + "tableFrom": "unstructured_chunks", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "unstructured_chunks_file_id_files_id_fk": { + "name": "unstructured_chunks_file_id_files_id_fk", + "tableFrom": "unstructured_chunks", + "tableTo": "files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rag_eval_dataset_records": { + "name": "rag_eval_dataset_records", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "rag_eval_dataset_records_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "dataset_id": { + "name": "dataset_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "ideal": { + "name": "ideal", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "question": { + "name": "question", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_files": { + "name": "reference_files", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "rag_eval_dataset_records_dataset_id_rag_eval_datasets_id_fk": { + "name": "rag_eval_dataset_records_dataset_id_rag_eval_datasets_id_fk", + "tableFrom": "rag_eval_dataset_records", + "tableTo": "rag_eval_datasets", + "columnsFrom": ["dataset_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "rag_eval_dataset_records_user_id_users_id_fk": { + "name": "rag_eval_dataset_records_user_id_users_id_fk", + "tableFrom": "rag_eval_dataset_records", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rag_eval_datasets": { + "name": "rag_eval_datasets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "rag_eval_datasets_id_seq", + "schema": "public", + "increment": "1", + "startWith": "30000", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "rag_eval_datasets_knowledge_base_id_knowledge_bases_id_fk": { + "name": "rag_eval_datasets_knowledge_base_id_knowledge_bases_id_fk", + "tableFrom": "rag_eval_datasets", + "tableTo": "knowledge_bases", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "rag_eval_datasets_user_id_users_id_fk": { + "name": "rag_eval_datasets_user_id_users_id_fk", + "tableFrom": "rag_eval_datasets", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rag_eval_evaluations": { + "name": "rag_eval_evaluations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "rag_eval_evaluations_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "eval_records_url": { + "name": "eval_records_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "dataset_id": { + "name": "dataset_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "language_model": { + "name": "language_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "rag_eval_evaluations_dataset_id_rag_eval_datasets_id_fk": { + "name": "rag_eval_evaluations_dataset_id_rag_eval_datasets_id_fk", + "tableFrom": "rag_eval_evaluations", + "tableTo": "rag_eval_datasets", + "columnsFrom": ["dataset_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "rag_eval_evaluations_knowledge_base_id_knowledge_bases_id_fk": { + "name": "rag_eval_evaluations_knowledge_base_id_knowledge_bases_id_fk", + "tableFrom": "rag_eval_evaluations", + "tableTo": "knowledge_bases", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "rag_eval_evaluations_user_id_users_id_fk": { + "name": "rag_eval_evaluations_user_id_users_id_fk", + "tableFrom": "rag_eval_evaluations", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rag_eval_evaluation_records": { + "name": "rag_eval_evaluation_records", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "rag_eval_evaluation_records_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "question": { + "name": "question", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "answer": { + "name": "answer", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "context": { + "name": "context", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "ideal": { + "name": "ideal", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "language_model": { + "name": "language_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "question_embedding_id": { + "name": "question_embedding_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "duration": { + "name": "duration", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "dataset_record_id": { + "name": "dataset_record_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "evaluation_id": { + "name": "evaluation_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "rag_eval_evaluation_records_question_embedding_id_embeddings_id_fk": { + "name": "rag_eval_evaluation_records_question_embedding_id_embeddings_id_fk", + "tableFrom": "rag_eval_evaluation_records", + "tableTo": "embeddings", + "columnsFrom": ["question_embedding_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "rag_eval_evaluation_records_dataset_record_id_rag_eval_dataset_records_id_fk": { + "name": "rag_eval_evaluation_records_dataset_record_id_rag_eval_dataset_records_id_fk", + "tableFrom": "rag_eval_evaluation_records", + "tableTo": "rag_eval_dataset_records", + "columnsFrom": ["dataset_record_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "rag_eval_evaluation_records_evaluation_id_rag_eval_evaluations_id_fk": { + "name": "rag_eval_evaluation_records_evaluation_id_rag_eval_evaluations_id_fk", + "tableFrom": "rag_eval_evaluation_records", + "tableTo": "rag_eval_evaluations", + "columnsFrom": ["evaluation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "rag_eval_evaluation_records_user_id_users_id_fk": { + "name": "rag_eval_evaluation_records_user_id_users_id_fk", + "tableFrom": "rag_eval_evaluation_records", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents_to_sessions": { + "name": "agents_to_sessions", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "agents_to_sessions_agent_id_agents_id_fk": { + "name": "agents_to_sessions_agent_id_agents_id_fk", + "tableFrom": "agents_to_sessions", + "tableTo": "agents", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agents_to_sessions_session_id_sessions_id_fk": { + "name": "agents_to_sessions_session_id_sessions_id_fk", + "tableFrom": "agents_to_sessions", + "tableTo": "sessions", + "columnsFrom": ["session_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agents_to_sessions_user_id_users_id_fk": { + "name": "agents_to_sessions_user_id_users_id_fk", + "tableFrom": "agents_to_sessions", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "agents_to_sessions_agent_id_session_id_pk": { + "name": "agents_to_sessions_agent_id_session_id_pk", + "columns": ["agent_id", "session_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.file_chunks": { + "name": "file_chunks", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "chunk_id": { + "name": "chunk_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "file_chunks_file_id_files_id_fk": { + "name": "file_chunks_file_id_files_id_fk", + "tableFrom": "file_chunks", + "tableTo": "files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "file_chunks_chunk_id_chunks_id_fk": { + "name": "file_chunks_chunk_id_chunks_id_fk", + "tableFrom": "file_chunks", + "tableTo": "chunks", + "columnsFrom": ["chunk_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "file_chunks_user_id_users_id_fk": { + "name": "file_chunks_user_id_users_id_fk", + "tableFrom": "file_chunks", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "file_chunks_file_id_chunk_id_pk": { + "name": "file_chunks_file_id_chunk_id_pk", + "columns": ["file_id", "chunk_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.files_to_sessions": { + "name": "files_to_sessions", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "files_to_sessions_file_id_files_id_fk": { + "name": "files_to_sessions_file_id_files_id_fk", + "tableFrom": "files_to_sessions", + "tableTo": "files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "files_to_sessions_session_id_sessions_id_fk": { + "name": "files_to_sessions_session_id_sessions_id_fk", + "tableFrom": "files_to_sessions", + "tableTo": "sessions", + "columnsFrom": ["session_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "files_to_sessions_user_id_users_id_fk": { + "name": "files_to_sessions_user_id_users_id_fk", + "tableFrom": "files_to_sessions", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "files_to_sessions_file_id_session_id_pk": { + "name": "files_to_sessions_file_id_session_id_pk", + "columns": ["file_id", "session_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session_groups": { + "name": "session_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sort": { + "name": "sort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "session_groups_user_id_users_id_fk": { + "name": "session_groups_user_id_users_id_fk", + "tableFrom": "session_groups", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_group_client_id_user_unique": { + "name": "session_group_client_id_user_unique", + "nullsNotDistinct": false, + "columns": ["client_id", "user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "background_color": { + "name": "background_color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'agent'" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pinned": { + "name": "pinned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "slug_user_id_unique": { + "name": "slug_user_id_unique", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "sessions_group_id_session_groups_id_fk": { + "name": "sessions_group_id_session_groups_id_fk", + "tableFrom": "sessions", + "tableTo": "session_groups", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sessions_client_id_user_id_unique": { + "name": "sessions_client_id_user_id_unique", + "nullsNotDistinct": false, + "columns": ["client_id", "user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.threads": { + "name": "threads", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'active'" + }, + "topic_id": { + "name": "topic_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_message_id": { + "name": "source_message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_thread_id": { + "name": "parent_thread_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_active_at": { + "name": "last_active_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "threads_topic_id_topics_id_fk": { + "name": "threads_topic_id_topics_id_fk", + "tableFrom": "threads", + "tableTo": "topics", + "columnsFrom": ["topic_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "threads_parent_thread_id_threads_id_fk": { + "name": "threads_parent_thread_id_threads_id_fk", + "tableFrom": "threads", + "tableTo": "threads", + "columnsFrom": ["parent_thread_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "threads_user_id_users_id_fk": { + "name": "threads_user_id_users_id_fk", + "tableFrom": "threads", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.topics": { + "name": "topics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favorite": { + "name": "favorite", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "history_summary": { + "name": "history_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "topics_session_id_sessions_id_fk": { + "name": "topics_session_id_sessions_id_fk", + "tableFrom": "topics", + "tableTo": "sessions", + "columnsFrom": ["session_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "topics_user_id_users_id_fk": { + "name": "topics_user_id_users_id_fk", + "tableFrom": "topics", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "topic_client_id_user_id_unique": { + "name": "topic_client_id_user_id_unique", + "nullsNotDistinct": false, + "columns": ["client_id", "user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_installed_plugins": { + "name": "user_installed_plugins", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "manifest": { + "name": "manifest", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "custom_params": { + "name": "custom_params", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_installed_plugins_user_id_users_id_fk": { + "name": "user_installed_plugins_user_id_users_id_fk", + "tableFrom": "user_installed_plugins", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_installed_plugins_user_id_identifier_pk": { + "name": "user_installed_plugins_user_id_identifier_pk", + "columns": ["user_id", "identifier"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_settings": { + "name": "user_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tts": { + "name": "tts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "key_vaults": { + "name": "key_vaults", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "general": { + "name": "general", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "language_model": { + "name": "language_model", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "system_agent": { + "name": "system_agent", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "default_agent": { + "name": "default_agent", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "tool": { + "name": "tool", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_settings_id_users_id_fk": { + "name": "user_settings_id_users_id_fk", + "tableFrom": "user_settings", + "tableTo": "users", + "columnsFrom": ["id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "full_name": { + "name": "full_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_onboarded": { + "name": "is_onboarded", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "clerk_created_at": { + "name": "clerk_created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "email_verified_at": { + "name": "email_verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "preference": { + "name": "preference", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_username_unique": { + "name": "users_username_unique", + "nullsNotDistinct": false, + "columns": ["username"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "version": "7", + "views": {} +} diff --git a/src/database/migrations/meta/_journal.json b/src/database/migrations/meta/_journal.json index 0c9ae1b0708ab..149db178ea0b7 100644 --- a/src/database/migrations/meta/_journal.json +++ b/src/database/migrations/meta/_journal.json @@ -119,6 +119,13 @@ "when": 1741844738677, "tag": "0016_add_message_index", "breakpoints": true + }, + { + "idx": 17, + "version": "7", + "when": 1742269437903, + "tag": "0017_add_user_id_to_tables", + "breakpoints": true } ], "version": "6" diff --git a/src/database/server/models/__tests__/_test_template.ts b/src/database/models/__tests__/_test_template.ts similarity index 97% rename from src/database/server/models/__tests__/_test_template.ts rename to src/database/models/__tests__/_test_template.ts index 3a2d2ecbbc1b4..d45257e83894b 100644 --- a/src/database/server/models/__tests__/_test_template.ts +++ b/src/database/models/__tests__/_test_template.ts @@ -4,8 +4,8 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { getTestDBInstance } from '@/database/server/core/dbForTest'; -import { sessionGroups, users } from '../../../schemas'; -import { SessionGroupModel } from '../sessionGroup'; +import { sessionGroups, users } from '../../schemas'; +import { SessionGroupModel } from '../../server/models/sessionGroup'; let serverDB = await getTestDBInstance(); diff --git a/src/database/models/__tests__/_util.ts b/src/database/models/__tests__/_util.ts new file mode 100644 index 0000000000000..70b743868d134 --- /dev/null +++ b/src/database/models/__tests__/_util.ts @@ -0,0 +1,12 @@ +import { clientDB, initializeDB } from '@/database/client/db'; +import { getTestDBInstance } from '@/database/server/core/dbForTest'; +import { LobeChatDatabase } from '@/database/type'; + +export const isServerDBMode = process.env.TEST_SERVER_DB === '1'; + +export const getTestDB = async () => { + if (isServerDBMode) return await getTestDBInstance(); + + await initializeDB(); + return clientDB as LobeChatDatabase; +}; diff --git a/src/database/server/models/__tests__/agent.test.ts b/src/database/models/__tests__/agent.test.ts similarity index 96% rename from src/database/server/models/__tests__/agent.test.ts rename to src/database/models/__tests__/agent.test.ts index 60d0ea39d4523..5a3ee28844319 100644 --- a/src/database/server/models/__tests__/agent.test.ts +++ b/src/database/models/__tests__/agent.test.ts @@ -2,7 +2,7 @@ import { eq } from 'drizzle-orm/expressions'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import { getTestDBInstance } from '@/database/server/core/dbForTest'; +import { LobeChatDatabase } from '@/database/type'; import { agents, @@ -13,10 +13,11 @@ import { knowledgeBases, sessions, users, -} from '../../../schemas'; -import { AgentModel } from '../agent'; +} from '../../schemas'; +import { AgentModel } from '../../server/models/agent'; +import { getTestDB } from './_util'; -let serverDB = await getTestDBInstance(); +const serverDB: LobeChatDatabase = await getTestDB(); const userId = 'agent-model-test-user-id'; const agentModel = new AgentModel(serverDB, userId); @@ -77,7 +78,7 @@ describe('AgentModel', () => { const sessionId = 'test-session-id'; await serverDB.insert(agents).values({ id: agentId, userId }); await serverDB.insert(sessions).values({ id: sessionId, userId }); - await serverDB.insert(agentsToSessions).values({ agentId, sessionId }); + await serverDB.insert(agentsToSessions).values({ agentId, sessionId, userId }); const result = await agentModel.findBySessionId(sessionId); diff --git a/src/database/server/models/__tests__/aiModel.test.ts b/src/database/models/__tests__/aiModel.test.ts similarity index 97% rename from src/database/server/models/__tests__/aiModel.test.ts rename to src/database/models/__tests__/aiModel.test.ts index 7a8dea883e363..7c66942315c98 100644 --- a/src/database/server/models/__tests__/aiModel.test.ts +++ b/src/database/models/__tests__/aiModel.test.ts @@ -2,13 +2,14 @@ import { eq } from 'drizzle-orm/expressions'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import { getTestDBInstance } from '@/database/server/core/dbForTest'; +import { LobeChatDatabase } from '@/database/type'; import { AiProviderModelListItem } from '@/types/aiModel'; -import { AiModelSelectItem, NewAiModelItem, aiModels, users } from '../../../schemas'; -import { AiModelModel } from '../aiModel'; +import { AiModelSelectItem, NewAiModelItem, aiModels, users } from '../../schemas'; +import { AiModelModel } from '../../server/models/aiModel'; +import { getTestDB } from './_util'; -let serverDB = await getTestDBInstance(); +const serverDB: LobeChatDatabase = await getTestDB(); const userId = 'ai-model-test-user-id'; const aiProviderModel = new AiModelModel(serverDB, userId); diff --git a/src/database/server/models/__tests__/aiProvider.test.ts b/src/database/models/__tests__/aiProvider.test.ts similarity index 97% rename from src/database/server/models/__tests__/aiProvider.test.ts rename to src/database/models/__tests__/aiProvider.test.ts index 6a4e88b6ddda8..581b343af0b75 100644 --- a/src/database/server/models/__tests__/aiProvider.test.ts +++ b/src/database/models/__tests__/aiProvider.test.ts @@ -2,13 +2,14 @@ import { eq } from 'drizzle-orm/expressions'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { getTestDBInstance } from '@/database/server/core/dbForTest'; +import { LobeChatDatabase } from '@/database/type'; import { ModelProvider } from '@/libs/agent-runtime'; -import { aiProviders, users } from '../../../schemas'; -import { AiProviderModel } from '../aiProvider'; +import { aiProviders, users } from '../../schemas'; +import { AiProviderModel } from '../../server/models/aiProvider'; +import { getTestDB } from './_util'; -let serverDB = await getTestDBInstance(); +const serverDB: LobeChatDatabase = await getTestDB(); const userId = 'session-group-model-test-user-id'; const aiProviderModel = new AiProviderModel(serverDB, userId); diff --git a/src/database/server/models/__tests__/asyncTask.test.ts b/src/database/models/__tests__/asyncTask.test.ts similarity index 94% rename from src/database/server/models/__tests__/asyncTask.test.ts rename to src/database/models/__tests__/asyncTask.test.ts index 30f9e5937f9ac..ceb17089950b3 100644 --- a/src/database/server/models/__tests__/asyncTask.test.ts +++ b/src/database/models/__tests__/asyncTask.test.ts @@ -2,13 +2,14 @@ import { eq } from 'drizzle-orm/expressions'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { getTestDBInstance } from '@/database/server/core/dbForTest'; +import { LobeChatDatabase } from '@/database/type'; import { AsyncTaskStatus, AsyncTaskType } from '@/types/asyncTask'; -import { asyncTasks, users } from '../../../schemas'; -import { ASYNC_TASK_TIMEOUT, AsyncTaskModel } from '../asyncTask'; +import { asyncTasks, users } from '../../schemas'; +import { ASYNC_TASK_TIMEOUT, AsyncTaskModel } from '../../server/models/asyncTask'; +import { getTestDB } from './_util'; -let serverDB = await getTestDBInstance(); +const serverDB: LobeChatDatabase = await getTestDB(); const userId = 'async-task-model-test-user-id'; const asyncTaskModel = new AsyncTaskModel(serverDB, userId); diff --git a/src/database/server/models/__tests__/chunk.test.ts b/src/database/models/__tests__/chunk.test.ts similarity index 92% rename from src/database/server/models/__tests__/chunk.test.ts rename to src/database/models/__tests__/chunk.test.ts index 584c3e249970a..ec9c8c8105169 100644 --- a/src/database/server/models/__tests__/chunk.test.ts +++ b/src/database/models/__tests__/chunk.test.ts @@ -2,14 +2,15 @@ import { eq } from 'drizzle-orm/expressions'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import { getTestDBInstance } from '@/database/server/core/dbForTest'; +import { LobeChatDatabase } from '@/database/type'; import { uuid } from '@/utils/uuid'; -import { chunks, embeddings, fileChunks, files, unstructuredChunks, users } from '../../../schemas'; -import { ChunkModel } from '../chunk'; +import { chunks, embeddings, fileChunks, files, unstructuredChunks, users } from '../../schemas'; +import { ChunkModel } from '../../server/models/chunk'; +import { getTestDB } from './_util'; import { codeEmbedding, designThinkingQuery, designThinkingQuery2 } from './fixtures/embedding'; -let serverDB = await getTestDBInstance(); +const serverDB: LobeChatDatabase = await getTestDB(); const userId = 'chunk-model-test-user-id'; const chunkModel = new ChunkModel(serverDB, userId); @@ -124,7 +125,9 @@ describe('ChunkModel', () => { .values([{ text: 'Non-Orphan Chunk', userId }]) .returning(); - await serverDB.insert(fileChunks).values([{ fileId: '1', chunkId: nonOrphanChunk.id }]); + await serverDB + .insert(fileChunks) + .values([{ fileId: '1', chunkId: nonOrphanChunk.id, userId }]); // Execute the method await chunkModel.deleteOrphanChunks(); @@ -146,8 +149,8 @@ describe('ChunkModel', () => { .returning(); await serverDB.insert(fileChunks).values([ - { fileId: '1', chunkId: chunk1.id }, - { fileId: '2', chunkId: chunk2.id }, + { fileId: '1', chunkId: chunk1.id, userId }, + { fileId: '2', chunkId: chunk2.id, userId }, ]); // Execute the method @@ -180,8 +183,8 @@ describe('ChunkModel', () => { .returning(); await serverDB.insert(fileChunks).values([ - { fileId, chunkId: chunk1.id }, - { fileId, chunkId: chunk2.id }, + { fileId, chunkId: chunk1.id, userId }, + { fileId, chunkId: chunk2.id, userId }, ]); await serverDB.insert(embeddings).values([ @@ -273,9 +276,9 @@ describe('ChunkModel', () => { .returning(); await serverDB.insert(fileChunks).values([ - { fileId, chunkId: chunk1.id }, - { fileId, chunkId: chunk2.id }, - { fileId, chunkId: chunk3.id }, + { fileId, chunkId: chunk1.id, userId }, + { fileId, chunkId: chunk2.id, userId }, + { fileId, chunkId: chunk3.id, userId }, ]); const result = await chunkModel.findByFileId(fileId, 0); @@ -299,8 +302,8 @@ describe('ChunkModel', () => { .returning(); await serverDB.insert(fileChunks).values([ - { fileId, chunkId: chunk1.id }, - { fileId, chunkId: chunk2.id }, + { fileId, chunkId: chunk1.id, userId }, + { fileId, chunkId: chunk2.id, userId }, ]); const result = await chunkModel.getChunksTextByFileId(fileId); @@ -324,9 +327,9 @@ describe('ChunkModel', () => { .returning(); await serverDB.insert(fileChunks).values([ - { fileId: '1', chunkId: chunk1.id }, - { fileId: '1', chunkId: chunk2.id }, - { fileId: '2', chunkId: chunk3.id }, + { fileId: '1', chunkId: chunk1.id, userId }, + { fileId: '1', chunkId: chunk2.id, userId }, + { fileId: '2', chunkId: chunk3.id, userId }, ]); const result = await chunkModel.countByFileIds(fileIds); @@ -355,8 +358,8 @@ describe('ChunkModel', () => { .returning(); await serverDB.insert(fileChunks).values([ - { fileId, chunkId: chunk1.id }, - { fileId, chunkId: chunk2.id }, + { fileId, chunkId: chunk1.id, userId }, + { fileId, chunkId: chunk2.id, userId }, ]); const result = await chunkModel.countByFileId(fileId); @@ -383,8 +386,8 @@ describe('ChunkModel', () => { .returning(); await serverDB.insert(fileChunks).values([ - { fileId, chunkId: chunk1.id }, - { fileId, chunkId: chunk2.id }, + { fileId, chunkId: chunk1.id, userId }, + { fileId, chunkId: chunk2.id, userId }, ]); await serverDB.insert(embeddings).values([ @@ -511,6 +514,7 @@ content in Table html is below: chunkResult.map((chunk) => ({ fileId, chunkId: chunk.id, + userId, })), ); diff --git a/src/database/server/models/__tests__/file.test.ts b/src/database/models/__tests__/file.test.ts similarity index 97% rename from src/database/server/models/__tests__/file.test.ts rename to src/database/models/__tests__/file.test.ts index 53dabfe59e637..2bf597acf74d7 100644 --- a/src/database/server/models/__tests__/file.test.ts +++ b/src/database/models/__tests__/file.test.ts @@ -2,13 +2,14 @@ import { eq, inArray } from 'drizzle-orm/expressions'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { getTestDBInstance } from '@/database/server/core/dbForTest'; +import { LobeChatDatabase } from '@/database/type'; import { FilesTabs, SortType } from '@/types/files'; -import { files, globalFiles, knowledgeBaseFiles, knowledgeBases, users } from '../../../schemas'; -import { FileModel } from '../file'; +import { files, globalFiles, knowledgeBaseFiles, knowledgeBases, users } from '../../schemas'; +import { FileModel } from '../../server/models/file'; +import { getTestDB } from './_util'; -const serverDB = await getTestDBInstance(); +const serverDB: LobeChatDatabase = await getTestDB(); const userId = 'file-model-test-user-id'; const fileModel = new FileModel(serverDB, userId); @@ -95,6 +96,7 @@ describe('FileModel', () => { size: 100, url: 'https://example.com/global-file.txt', metadata: { key: 'value' }, + creator: userId, }; const result = await fileModel.createGlobalFile(globalFile); @@ -115,6 +117,7 @@ describe('FileModel', () => { size: 100, url: 'https://example.com/existing-file.txt', metadata: { key: 'value' }, + creator: userId, }; await serverDB.insert(globalFiles).values(globalFile); @@ -137,6 +140,7 @@ describe('FileModel', () => { url: 'https://example.com/file1.txt', size: 100, fileType: 'text/plain', + creator: userId, }); const { id } = await fileModel.create({ @@ -163,6 +167,7 @@ describe('FileModel', () => { url: 'https://example.com/file1.txt', size: 100, fileType: 'text/plain', + creator: userId, }); const { id } = await fileModel.create({ @@ -192,12 +197,14 @@ describe('FileModel', () => { url: 'https://example.com/file1.txt', size: 100, fileType: 'text/plain', + creator: userId, }); await fileModel.createGlobalFile({ hashId: '2', url: 'https://example.com/file2.txt', size: 200, fileType: 'text/plain', + creator: userId, }); const file1 = await fileModel.create({ @@ -240,12 +247,14 @@ describe('FileModel', () => { url: 'https://example.com/file1.txt', size: 100, fileType: 'text/plain', + creator: userId, }); await fileModel.createGlobalFile({ hashId: '2', url: 'https://example.com/file2.txt', size: 200, fileType: 'text/plain', + creator: userId, }); const file1 = await fileModel.create({ @@ -450,7 +459,7 @@ describe('FileModel', () => { ]); await serverDB .insert(knowledgeBaseFiles) - .values([{ fileId: 'file1', knowledgeBaseId: 'kb1' }]); + .values([{ fileId: 'file1', knowledgeBaseId: 'kb1', userId }]); }); it('should query files in a specific knowledge base', async () => { @@ -551,12 +560,14 @@ describe('FileModel', () => { url: 'https://example.com/document.pdf', size: 1000, fileType: 'application/pdf', + creator: userId, }, { hashId: 'hash2', url: 'https://example.com/image.jpg', size: 500, fileType: 'image/jpeg', + creator: userId, }, ]); @@ -674,6 +685,7 @@ describe('FileModel', () => { size: 100, url: 'https://example.com/global-file.txt', metadata: { key: 'value' }, + creator: userId, }; await serverDB.insert(globalFiles).values(globalFile); @@ -700,12 +712,14 @@ describe('FileModel', () => { fileType: 'text/plain', size: 100, url: 'https://example.com/file1.txt', + creator: userId, }; const globalFiles2 = { hashId: 'hash2', fileType: 'text/plain', size: 200, url: 'https://example.com/file2.txt', + creator: userId, }; await serverDB.insert(globalFiles).values([globalFiles1, globalFiles2]); diff --git a/src/database/server/models/__tests__/fixtures/embedding.ts b/src/database/models/__tests__/fixtures/embedding.ts similarity index 100% rename from src/database/server/models/__tests__/fixtures/embedding.ts rename to src/database/models/__tests__/fixtures/embedding.ts diff --git a/src/database/server/models/__tests__/knowledgeBase.test.ts b/src/database/models/__tests__/knowledgeBase.test.ts similarity index 95% rename from src/database/server/models/__tests__/knowledgeBase.test.ts rename to src/database/models/__tests__/knowledgeBase.test.ts index e9bc96e630697..1ca96c1710e67 100644 --- a/src/database/server/models/__tests__/knowledgeBase.test.ts +++ b/src/database/models/__tests__/knowledgeBase.test.ts @@ -2,7 +2,7 @@ import { and, eq } from 'drizzle-orm/expressions'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import { getTestDBInstance } from '@/database/server/core/dbForTest'; +import { LobeChatDatabase } from '@/database/type'; import { NewKnowledgeBase, @@ -11,10 +11,11 @@ import { knowledgeBaseFiles, knowledgeBases, users, -} from '../../../schemas'; -import { KnowledgeBaseModel } from '../knowledgeBase'; +} from '../../schemas'; +import { KnowledgeBaseModel } from '../../server/models/knowledgeBase'; +import { getTestDB } from './_util'; -let serverDB = await getTestDBInstance(); +const serverDB: LobeChatDatabase = await getTestDB(); const userId = 'session-group-model-test-user-id'; const knowledgeBaseModel = new KnowledgeBaseModel(serverDB, userId); @@ -160,12 +161,14 @@ describe('KnowledgeBaseModel', () => { url: 'https://example.com/document.pdf', size: 1000, fileType: 'application/pdf', + creator: userId, }, { hashId: 'hash2', url: 'https://example.com/image.jpg', size: 500, fileType: 'image/jpeg', + creator: userId, }, ]); @@ -198,12 +201,14 @@ describe('KnowledgeBaseModel', () => { url: 'https://example.com/document.pdf', size: 1000, fileType: 'application/pdf', + creator: userId, }, { hashId: 'hash2', url: 'https://example.com/image.jpg', size: 500, fileType: 'image/jpeg', + creator: userId, }, ]); diff --git a/src/database/server/models/__tests__/message.test.ts b/src/database/models/__tests__/message.test.ts similarity index 71% rename from src/database/server/models/__tests__/message.test.ts rename to src/database/models/__tests__/message.test.ts index fc63fbf6dcea1..2bb5bcc20aacc 100644 --- a/src/database/server/models/__tests__/message.test.ts +++ b/src/database/models/__tests__/message.test.ts @@ -2,7 +2,8 @@ import dayjs from 'dayjs'; import { eq } from 'drizzle-orm/expressions'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { getTestDBInstance } from '@/database/server/core/dbForTest'; +import { getTestDB } from '@/database/models/__tests__/_util'; +import { LobeChatDatabase } from '@/database/type'; import { MessageItem } from '@/types/message'; import { uuid } from '@/utils/uuid'; @@ -21,15 +22,15 @@ import { sessions, topics, users, -} from '../../../schemas'; -import { MessageModel } from '../message'; +} from '../../schemas'; +import { MessageModel } from '../../server/models/message'; import { codeEmbedding } from './fixtures/embedding'; -let serverDB = await getTestDBInstance(); +const serverDB: LobeChatDatabase = await getTestDB(); const userId = 'message-db'; const messageModel = new MessageModel(serverDB, userId); - +const embeddingsId = uuid(); beforeEach(async () => { // 在每个测试用例之前,清空表 await serverDB.transaction(async (trx) => { @@ -49,6 +50,12 @@ beforeEach(async () => { fileType: 'image/png', size: 1000, }); + + await trx.insert(embeddings).values({ + id: embeddingsId, + embeddings: codeEmbedding, + userId, + }); }); }); @@ -201,13 +208,14 @@ describe('MessageModel', () => { { id: 'f-1', url: 'abc', name: 'file-1', userId, fileType: 'image/png', size: 100 }, { id: 'f-3', url: 'abc', name: 'file-3', userId, fileType: 'image/png', size: 400 }, ]); - await trx - .insert(messageTTS) - .values([{ id: '1' }, { id: '2', voice: 'a', fileId: 'f-1', contentMd5: 'abc' }]); + await trx.insert(messageTTS).values([ + { id: '1', userId }, + { id: '2', voice: 'a', fileId: 'f-1', contentMd5: 'abc', userId }, + ]); await trx.insert(messagesFiles).values([ - { fileId: 'f-0', messageId: '1' }, - { fileId: 'f-3', messageId: '1' }, + { fileId: 'f-0', messageId: '1', userId }, + { fileId: 'f-3', messageId: '1', userId }, ]); }); @@ -244,10 +252,10 @@ describe('MessageModel', () => { ]); await trx .insert(messageTranslates) - .values([{ id: '1', content: 'translated', from: 'en', to: 'zh' }]); + .values([{ id: '1', content: 'translated', from: 'en', to: 'zh', userId }]); await trx .insert(messageTTS) - .values([{ id: '1', voice: 'voice1', fileId: 'f1', contentMd5: 'md5' }]); + .values([{ id: '1', voice: 'voice1', fileId: 'f1', contentMd5: 'md5', userId }]); }); // 调用 query 方法 @@ -281,7 +289,81 @@ describe('MessageModel', () => { expect(result3).toHaveLength(0); }); - // 补充测试复杂查询场景 + describe('query with messageQueries', () => { + it('should include ragQuery, ragQueryId and ragRawQuery in query results', async () => { + // 创建测试数据 + const messageId = 'msg-with-query'; + const queryId = uuid(); + + await serverDB.insert(messages).values({ + id: messageId, + userId, + role: 'user', + content: 'test message', + }); + + await serverDB.insert(messageQueries).values({ + id: queryId, + messageId, + userQuery: 'original query', + rewriteQuery: 'rewritten query', + userId, + }); + + // 调用 query 方法 + const result = await messageModel.query(); + + // 断言结果 + expect(result).toHaveLength(1); + expect(result[0].id).toBe(messageId); + expect(result[0].ragQueryId).toBe(queryId); + expect(result[0].ragQuery).toBe('rewritten query'); + expect(result[0].ragRawQuery).toBe('original query'); + }); + + it.skip('should handle multiple message queries for the same message', async () => { + // 创建测试数据 + const messageId = 'msg-multi-query'; + const queryId1 = uuid(); + const queryId2 = uuid(); + + await serverDB.insert(messages).values({ + id: messageId, + userId, + role: 'user', + content: 'test message', + }); + + // 创建两个查询,但查询结果应该只包含一个(最新的) + await serverDB.insert(messageQueries).values([ + { + id: queryId1, + messageId, + userQuery: 'original query 1', + rewriteQuery: 'rewritten query 1', + userId, + }, + { + id: queryId2, + messageId, + userQuery: 'original query 2', + rewriteQuery: 'rewritten query 2', + userId, + }, + ]); + + // 调用 query 方法 + const result = await messageModel.query(); + + // 断言结果 - 应该只包含最新的查询 + expect(result).toHaveLength(1); + expect(result[0].id).toBe(messageId); + expect(result[0].ragQueryId).toBe(queryId2); + expect(result[0].ragQuery).toBe('rewritten query 2'); + expect(result[0].ragRawQuery).toBe('original query 2'); + }); + }); + it('should handle complex query with multiple joins and file chunks', async () => { await serverDB.transaction(async (trx) => { const chunk1Id = uuid(); @@ -316,12 +398,14 @@ describe('MessageModel', () => { // 关联消息和文件 await trx.insert(messagesFiles).values({ messageId: 'msg1', + userId, fileId: 'file1', }); // 创建文件块关联 await trx.insert(fileChunks).values({ fileId: 'file1', + userId, chunkId: chunk1Id, }); @@ -329,6 +413,7 @@ describe('MessageModel', () => { await trx.insert(messageQueries).values({ id: query1Id, messageId: 'msg1', + userId, userQuery: 'original query', rewriteQuery: 'rewritten query', }); @@ -339,6 +424,7 @@ describe('MessageModel', () => { queryId: query1Id, chunkId: chunk1Id, similarity: '0.95', + userId, }); }); @@ -648,6 +734,135 @@ describe('MessageModel', () => { expect(pluginResult[0].identifier).toBe('lobe-web-browsing'); expect(pluginResult[0].state!).toMatchObject(state); }); + + describe('create with advanced parameters', () => { + it('should create a message with custom ID', async () => { + const customId = 'custom-msg-id'; + + const result = await messageModel.create( + { + role: 'user', + content: 'message with custom ID', + sessionId: '1', + }, + customId, + ); + + expect(result.id).toBe(customId); + + // 验证数据库中的记录 + const dbResult = await serverDB.select().from(messages).where(eq(messages.id, customId)); + expect(dbResult).toHaveLength(1); + expect(dbResult[0].id).toBe(customId); + }); + + it.skip('should create a message with file chunks and RAG query ID', async () => { + // 创建测试数据 + const chunkId1 = uuid(); + const chunkId2 = uuid(); + const ragQueryId = uuid(); + + await serverDB.insert(chunks).values([ + { id: chunkId1, text: 'chunk text 1' }, + { id: chunkId2, text: 'chunk text 2' }, + ]); + + // 调用 create 方法 + const result = await messageModel.create({ + role: 'assistant', + content: 'message with file chunks', + fileChunks: [ + { id: chunkId1, similarity: 0.95 }, + { id: chunkId2, similarity: 0.85 }, + ], + ragQueryId, + sessionId: '1', + }); + + // 验证消息创建成功 + expect(result.id).toBeDefined(); + + // 验证消息查询块关联创建成功 + const queryChunks = await serverDB + .select() + .from(messageQueryChunks) + .where(eq(messageQueryChunks.messageId, result.id)); + + expect(queryChunks).toHaveLength(2); + expect(queryChunks[0].chunkId).toBe(chunkId1); + expect(queryChunks[0].queryId).toBe(ragQueryId); + expect(queryChunks[0].similarity).toBe('0.95'); + expect(queryChunks[1].chunkId).toBe(chunkId2); + expect(queryChunks[1].similarity).toBe('0.85'); + }); + + it('should create a message with files', async () => { + // 创建测试数据 + await serverDB.insert(files).values([ + { + id: 'file1', + name: 'file1.txt', + fileType: 'text/plain', + size: 100, + url: 'url1', + userId, + }, + { + id: 'file2', + name: 'file2.jpg', + fileType: 'image/jpeg', + size: 200, + url: 'url2', + userId, + }, + ]); + + // 调用 create 方法 + const result = await messageModel.create({ + role: 'user', + content: 'message with files', + files: ['file1', 'file2'], + sessionId: '1', + }); + + // 验证消息创建成功 + expect(result.id).toBeDefined(); + + // 验证消息文件关联创建成功 + const messageFiles = await serverDB + .select() + .from(messagesFiles) + .where(eq(messagesFiles.messageId, result.id)); + + expect(messageFiles).toHaveLength(2); + expect(messageFiles[0].fileId).toBe('file1'); + expect(messageFiles[1].fileId).toBe('file2'); + }); + + it('should create a message with custom timestamps', async () => { + const customCreatedAt = '2022-05-15T10:30:00Z'; + const customUpdatedAt = '2022-05-16T11:45:00Z'; + + const result = await messageModel.create({ + role: 'user', + content: 'message with custom timestamps', + createdAt: customCreatedAt as any, + updatedAt: customUpdatedAt as any, + sessionId: '1', + }); + + // 验证数据库中的记录 + const dbResult = await serverDB.select().from(messages).where(eq(messages.id, result.id)); + + // 日期比较需要考虑时区和格式化问题,所以使用 toISOString 进行比较 + expect(new Date(dbResult[0].createdAt!).toISOString()).toBe( + new Date(customCreatedAt).toISOString(), + ); + expect(new Date(dbResult[0].updatedAt!).toISOString()).toBe( + new Date(customUpdatedAt).toISOString(), + ); + }); + }); }); describe('batchCreateMessages', () => { @@ -734,10 +949,122 @@ describe('MessageModel', () => { // 断言结果 const result = await serverDB.select().from(messages).where(eq(messages.id, '1')); - expect(result[0].tools[0].arguments).toBe( + expect((result[0].tools as any)[0].arguments).toBe( '{"query":"2024 杭州暴雨","searchEngines":["duckduckgo","google","brave"]}', ); }); + + describe('update with imageList', () => { + it('should update a message and add image files', async () => { + // 创建测试数据 + await serverDB.insert(messages).values({ + id: 'msg-to-update', + userId, + role: 'user', + content: 'original content', + }); + + await serverDB.insert(files).values([ + { + id: 'img1', + name: 'image1.jpg', + fileType: 'image/jpeg', + size: 100, + url: 'url1', + userId, + }, + { id: 'img2', name: 'image2.png', fileType: 'image/png', size: 200, url: 'url2', userId }, + ]); + + // 调用 update 方法 + await messageModel.update('msg-to-update', { + content: 'updated content', + imageList: [ + { id: 'img1', alt: 'image 1', url: 'url1' }, + { id: 'img2', alt: 'image 2', url: 'url2' }, + ], + }); + + // 验证消息更新成功 + const updatedMessage = await serverDB + .select() + .from(messages) + .where(eq(messages.id, 'msg-to-update')); + + expect(updatedMessage[0].content).toBe('updated content'); + + // 验证消息文件关联创建成功 + const messageFiles = await serverDB + .select() + .from(messagesFiles) + .where(eq(messagesFiles.messageId, 'msg-to-update')); + + expect(messageFiles).toHaveLength(2); + expect(messageFiles[0].fileId).toBe('img1'); + expect(messageFiles[1].fileId).toBe('img2'); + }); + + it('should handle empty imageList', async () => { + // 创建测试数据 + await serverDB.insert(messages).values({ + id: 'msg-no-images', + userId, + role: 'user', + content: 'original content', + }); + + // 调用 update 方法,不提供 imageList + await messageModel.update('msg-no-images', { + content: 'updated content', + }); + + // 验证消息更新成功 + const updatedMessage = await serverDB + .select() + .from(messages) + .where(eq(messages.id, 'msg-no-images')); + + expect(updatedMessage[0].content).toBe('updated content'); + + // 验证没有创建消息文件关联 + const messageFiles = await serverDB + .select() + .from(messagesFiles) + .where(eq(messagesFiles.messageId, 'msg-no-images')); + + expect(messageFiles).toHaveLength(0); + }); + + it('should update multiple fields at once', async () => { + // 创建测试数据 + await serverDB.insert(messages).values({ + id: 'msg-multi-update', + userId, + role: 'user', + content: 'original content', + model: 'gpt-3.5', + }); + + // 调用 update 方法,更新多个字段 + await messageModel.update('msg-multi-update', { + content: 'updated content', + role: 'assistant', + model: 'gpt-4', + metadata: { tps: 1 }, + }); + + // 验证消息更新成功 + const updatedMessage = await serverDB + .select() + .from(messages) + .where(eq(messages.id, 'msg-multi-update')); + + expect(updatedMessage[0].content).toBe('updated content'); + expect(updatedMessage[0].role).toBe('assistant'); + expect(updatedMessage[0].model).toBe('gpt-4'); + expect(updatedMessage[0].metadata).toEqual({ tps: 1 }); + }); + }); }); describe('deleteMessage', () => { @@ -764,7 +1091,7 @@ describe('MessageModel', () => { ]); await trx .insert(messagePlugins) - .values([{ id: '2', toolCallId: 'tool1', identifier: 'plugin-1' }]); + .values([{ id: '2', toolCallId: 'tool1', identifier: 'plugin-1', userId }]); }); // 调用 deleteMessage 方法 @@ -858,11 +1185,15 @@ describe('MessageModel', () => { it('should update the state field in messagePlugins table', async () => { // 创建测试数据 await serverDB.insert(messages).values({ id: '1', content: 'abc', role: 'user', userId }); - await serverDB - .insert(messagePlugins) - .values([ - { id: '1', toolCallId: 'tool1', identifier: 'plugin1', state: { key1: 'value1' } }, - ]); + await serverDB.insert(messagePlugins).values([ + { + id: '1', + toolCallId: 'tool1', + identifier: 'plugin1', + state: { key1: 'value1' }, + userId, + }, + ]); // 调用 updatePluginState 方法 await messageModel.updatePluginState('1', { key2: 'value2' }); @@ -884,11 +1215,15 @@ describe('MessageModel', () => { it('should update the state field in messagePlugins table', async () => { // 创建测试数据 await serverDB.insert(messages).values({ id: '1', content: 'abc', role: 'user', userId }); - await serverDB - .insert(messagePlugins) - .values([ - { id: '1', toolCallId: 'tool1', identifier: 'plugin1', state: { key1: 'value1' } }, - ]); + await serverDB.insert(messagePlugins).values([ + { + id: '1', + toolCallId: 'tool1', + identifier: 'plugin1', + state: { key1: 'value1' }, + userId, + }, + ]); // 调用 updatePluginState 方法 await messageModel.updateMessagePlugin('1', { identifier: 'plugin2' }); @@ -939,7 +1274,7 @@ describe('MessageModel', () => { .values([{ id: '1', userId, role: 'user', content: 'message 1' }]); await trx .insert(messageTranslates) - .values([{ id: '1', content: 'translated message 1', from: 'en', to: 'zh' }]); + .values([{ id: '1', content: 'translated message 1', from: 'en', to: 'zh', userId }]); }); // 调用 updateTranslate 方法 @@ -980,7 +1315,7 @@ describe('MessageModel', () => { .values([{ id: '1', userId, role: 'user', content: 'message 1' }]); await trx .insert(messageTTS) - .values([{ id: '1', contentMd5: 'md5', fileId: 'f1', voice: 'voice1' }]); + .values([{ id: '1', contentMd5: 'md5', fileId: 'f1', voice: 'voice1', userId }]); }); // 调用 updateTTS 方法 @@ -997,7 +1332,7 @@ describe('MessageModel', () => { it('should delete the message translate record', async () => { // 创建测试数据 await serverDB.insert(messages).values([{ id: '1', role: 'abc', userId }]); - await serverDB.insert(messageTranslates).values([{ id: '1' }]); + await serverDB.insert(messageTranslates).values([{ id: '1', userId }]); // 调用 deleteMessageTranslate 方法 await messageModel.deleteMessageTranslate('1'); @@ -1016,7 +1351,7 @@ describe('MessageModel', () => { it('should delete the message TTS record', async () => { // 创建测试数据 await serverDB.insert(messages).values([{ id: '1', role: 'abc', userId }]); - await serverDB.insert(messageTTS).values([{ id: '1' }]); + await serverDB.insert(messageTTS).values([{ userId, id: '1' }]); // 调用 deleteMessageTTS 方法 await messageModel.deleteMessageTTS('1'); @@ -1042,6 +1377,90 @@ describe('MessageModel', () => { // 断言结果 expect(result).toBe(2); }); + + describe('count with date filters', () => { + beforeEach(async () => { + // 创建测试数据,包含不同日期的消息 + await serverDB.insert(messages).values([ + { + id: 'date1', + userId, + role: 'user', + content: 'message 1', + createdAt: new Date('2023-01-15'), + }, + { + id: 'date2', + userId, + role: 'user', + content: 'message 2', + createdAt: new Date('2023-02-15'), + }, + { + id: 'date3', + userId, + role: 'user', + content: 'message 3', + createdAt: new Date('2023-03-15'), + }, + { + id: 'date4', + userId, + role: 'user', + content: 'message 4', + createdAt: new Date('2023-04-15'), + }, + ]); + }); + + it('should count messages with startDate filter', async () => { + const result = await messageModel.count({ startDate: '2023-02-01' }); + expect(result).toBe(3); // 2月15日, 3月15日, 4月15日的消息 + }); + + it('should count messages with endDate filter', async () => { + const result = await messageModel.count({ endDate: '2023-03-01' }); + expect(result).toBe(2); // 1月15日, 2月15日的消息 + }); + + it('should count messages with both startDate and endDate filters', async () => { + const result = await messageModel.count({ + startDate: '2023-02-01', + endDate: '2023-03-31', + }); + expect(result).toBe(2); // 2月15日, 3月15日的消息 + }); + + it('should count messages with range filter', async () => { + const result = await messageModel.count({ + range: ['2023-02-01', '2023-04-01'], + }); + expect(result).toBe(2); // 2月15日, 3月15日的消息 + }); + + it('should handle edge cases in date filters', async () => { + // 边界日期 + const result1 = await messageModel.count({ + startDate: '2023-01-15', + endDate: '2023-04-15', + }); + expect(result1).toBe(4); // 包含所有消息 + + // 没有消息的日期范围 + const result2 = await messageModel.count({ + startDate: '2023-05-01', + endDate: '2023-06-01', + }); + expect(result2).toBe(0); + + // 精确到一天 + const result3 = await messageModel.count({ + startDate: '2023-01-15', + endDate: '2023-01-15', + }); + expect(result3).toBe(1); + }); + }); }); describe('findMessageQueriesById', () => { @@ -1068,6 +1487,7 @@ describe('MessageModel', () => { userQuery: 'test query', rewriteQuery: 'rewritten query', embeddingsId: embeddings1Id, + userId, }); }); @@ -1523,4 +1943,180 @@ describe('MessageModel', () => { expect(result3).toBe(true); }); }); + + describe('createMessageQuery', () => { + it('should create a new message query', async () => { + // 创建测试数据 + await serverDB.insert(messages).values({ + id: 'msg1', + userId, + role: 'user', + content: 'test message', + }); + + // 调用 createMessageQuery 方法 + const result = await messageModel.createMessageQuery({ + messageId: 'msg1', + userQuery: 'original query', + rewriteQuery: 'rewritten query', + embeddingsId, + }); + + // 断言结果 + expect(result).toBeDefined(); + expect(result.id).toBeDefined(); + expect(result.messageId).toBe('msg1'); + expect(result.userQuery).toBe('original query'); + expect(result.rewriteQuery).toBe('rewritten query'); + expect(result.userId).toBe(userId); + + // 验证数据库中的记录 + const dbResult = await serverDB + .select() + .from(messageQueries) + .where(eq(messageQueries.id, result.id)); + + expect(dbResult).toHaveLength(1); + expect(dbResult[0].messageId).toBe('msg1'); + expect(dbResult[0].userQuery).toBe('original query'); + expect(dbResult[0].rewriteQuery).toBe('rewritten query'); + }); + + it('should create a message query with embeddings ID', async () => { + // 创建测试数据 + await serverDB.insert(messages).values({ + id: 'msg2', + userId, + role: 'user', + content: 'test message', + }); + + // 调用 createMessageQuery 方法 + const result = await messageModel.createMessageQuery({ + messageId: 'msg2', + userQuery: 'test query', + rewriteQuery: 'test rewritten query', + embeddingsId, + }); + + // 断言结果 + expect(result).toBeDefined(); + expect(result.embeddingsId).toBe(embeddingsId); + + // 验证数据库中的记录 + const dbResult = await serverDB + .select() + .from(messageQueries) + .where(eq(messageQueries.id, result.id)); + + expect(dbResult[0].embeddingsId).toBe(embeddingsId); + }); + + it('should generate a unique ID for each message query', async () => { + // 创建测试数据 + await serverDB.insert(messages).values({ + id: 'msg3', + userId, + role: 'user', + content: 'test message', + }); + + // 连续创建两个消息查询 + const result1 = await messageModel.createMessageQuery({ + messageId: 'msg3', + userQuery: 'query 1', + rewriteQuery: 'rewritten query 1', + embeddingsId, + }); + + const result2 = await messageModel.createMessageQuery({ + messageId: 'msg3', + userQuery: 'query 2', + rewriteQuery: 'rewritten query 2', + embeddingsId, + }); + + // 断言结果 + expect(result1.id).not.toBe(result2.id); + }); + }); + + describe('deleteMessageQuery', () => { + it('should delete a message query by ID', async () => { + // 创建测试数据 + const queryId = uuid(); + await serverDB.insert(messages).values({ + id: 'msg4', + userId, + role: 'user', + content: 'test message', + }); + + await serverDB.insert(messageQueries).values({ + id: queryId, + messageId: 'msg4', + userQuery: 'test query', + rewriteQuery: 'rewritten query', + userId, + }); + + // 验证查询已创建 + const beforeDelete = await serverDB + .select() + .from(messageQueries) + .where(eq(messageQueries.id, queryId)); + + expect(beforeDelete).toHaveLength(1); + + // 调用 deleteMessageQuery 方法 + await messageModel.deleteMessageQuery(queryId); + + // 验证查询已删除 + const afterDelete = await serverDB + .select() + .from(messageQueries) + .where(eq(messageQueries.id, queryId)); + + expect(afterDelete).toHaveLength(0); + }); + + it('should only delete message queries belonging to the user', async () => { + // 创建测试数据 - 其他用户的查询 + const queryId = uuid(); + await serverDB.insert(messages).values({ + id: 'msg5', + userId: '456', + role: 'user', + content: 'test message', + }); + + await serverDB.insert(messageQueries).values({ + id: queryId, + messageId: 'msg5', + userQuery: 'test query', + rewriteQuery: 'rewritten query', + userId: '456', // 其他用户 + }); + + // 调用 deleteMessageQuery 方法 + await messageModel.deleteMessageQuery(queryId); + + // 验证查询未被删除 + const afterDelete = await serverDB + .select() + .from(messageQueries) + .where(eq(messageQueries.id, queryId)); + + expect(afterDelete).toHaveLength(1); + }); + + it('should throw error when deleting non-existent message query', async () => { + // 调用 deleteMessageQuery 方法删除不存在的查询 + try { + await messageModel.deleteMessageQuery('non-existent-id'); + } catch (e) { + expect(e).toBeInstanceOf(Error); + } + }); + }); }); diff --git a/src/database/server/models/__tests__/plugin.test.ts b/src/database/models/__tests__/plugin.test.ts similarity index 95% rename from src/database/server/models/__tests__/plugin.test.ts rename to src/database/models/__tests__/plugin.test.ts index e2edb982388bf..dd3de402a84e9 100644 --- a/src/database/server/models/__tests__/plugin.test.ts +++ b/src/database/models/__tests__/plugin.test.ts @@ -1,12 +1,13 @@ // @vitest-environment node import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { getTestDBInstance } from '@/database/server/core/dbForTest'; +import { LobeChatDatabase } from '@/database/type'; -import { NewInstalledPlugin, userInstalledPlugins, users } from '../../../schemas'; -import { PluginModel } from '../plugin'; +import { NewInstalledPlugin, userInstalledPlugins, users } from '../../schemas'; +import { PluginModel } from '../../server/models/plugin'; +import { getTestDB } from './_util'; -let serverDB = await getTestDBInstance(); +const serverDB: LobeChatDatabase = await getTestDB(); const userId = 'plugin-db'; const pluginModel = new PluginModel(serverDB, userId); diff --git a/src/database/server/models/__tests__/session.test.ts b/src/database/models/__tests__/session.test.ts similarity index 96% rename from src/database/server/models/__tests__/session.test.ts rename to src/database/models/__tests__/session.test.ts index ffa4574dcc78f..4151f4a22bc21 100644 --- a/src/database/server/models/__tests__/session.test.ts +++ b/src/database/models/__tests__/session.test.ts @@ -3,6 +3,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { DEFAULT_AGENT_CONFIG } from '@/const/settings'; import { getTestDBInstance } from '@/database/server/core/dbForTest'; +import { LobeChatDatabase } from '@/database/type'; import { idGenerator } from '@/database/utils/idGenerator'; import { @@ -15,10 +16,11 @@ import { sessions, topics, users, -} from '../../../schemas'; -import { SessionModel } from '../session'; +} from '../../schemas'; +import { SessionModel } from '../../server/models/session'; +import { getTestDB } from './_util'; -let serverDB = await getTestDBInstance(); +const serverDB: LobeChatDatabase = await getTestDB(); const userId = 'session-user'; const sessionModel = new SessionModel(serverDB, userId); @@ -236,8 +238,8 @@ describe('SessionModel', () => { ]); await serverDB.insert(agentsToSessions).values([ - { agentId: 'agent-1', sessionId: '1' }, - { agentId: 'agent-2', sessionId: '2' }, + { agentId: 'agent-1', sessionId: '1', userId }, + { agentId: 'agent-2', sessionId: '2', userId }, ]); const result = await sessionModel.queryByKeyword('hello'); @@ -265,8 +267,8 @@ describe('SessionModel', () => { ]); await serverDB.insert(agentsToSessions).values([ - { agentId: 'agent-1', sessionId: '1' }, - { agentId: 'agent-2', sessionId: '2' }, + { agentId: 'agent-1', sessionId: '1', userId }, + { agentId: 'agent-2', sessionId: '2', userId }, ]); const result = await sessionModel.queryByKeyword('keyword'); @@ -288,9 +290,9 @@ describe('SessionModel', () => { ]); await serverDB.insert(agentsToSessions).values([ - { agentId: '1', sessionId: '1' }, - { agentId: '2', sessionId: '2' }, - { agentId: '3', sessionId: '3' }, + { agentId: '1', sessionId: '1', userId }, + { agentId: '2', sessionId: '2', userId }, + { agentId: '3', sessionId: '3', userId }, ]); const result = await sessionModel.queryByKeyword('keyword'); @@ -361,7 +363,8 @@ describe('SessionModel', () => { const result = await sessionModel.batchCreate(sessions); // 断言结果 - expect(result.rowCount).toEqual(2); + // pglite return affectedRows while postgres return rowCount + expect((result as any).affectedRows || result.rowCount).toEqual(2); }); it.skip('should set group to default if group does not exist', async () => { @@ -391,7 +394,7 @@ describe('SessionModel', () => { .insert(sessions) .values({ id: '1', userId, type: 'agent', title: 'Original Session', pinned: true }); await trx.insert(agents).values({ id: 'agent-1', userId, model: 'gpt-3.5-turbo' }); - await trx.insert(agentsToSessions).values({ agentId: 'agent-1', sessionId: '1' }); + await trx.insert(agentsToSessions).values({ agentId: 'agent-1', sessionId: '1', userId }); }); // 调用 duplicate 方法 @@ -801,9 +804,9 @@ describe('SessionModel', () => { // Link agents to sessions await trx.insert(agentsToSessions).values([ - { sessionId: '1', agentId: 'a1' }, - { sessionId: '2', agentId: 'a2' }, - { sessionId: '3', agentId: 'a3' }, + { sessionId: '1', agentId: 'a1', userId }, + { sessionId: '2', agentId: 'a2', userId }, + { sessionId: '3', agentId: 'a3', userId }, ]); // Create topics (different counts for ranking) @@ -863,9 +866,9 @@ describe('SessionModel', () => { ]); await trx.insert(agentsToSessions).values([ - { sessionId: '1', agentId: 'a1' }, - { sessionId: '2', agentId: 'a2' }, - { sessionId: '3', agentId: 'a3' }, + { sessionId: '1', agentId: 'a1', userId }, + { sessionId: '2', agentId: 'a2', userId }, + { sessionId: '3', agentId: 'a3', userId }, ]); await trx.insert(topics).values([ @@ -899,8 +902,8 @@ describe('SessionModel', () => { ]); await trx.insert(agentsToSessions).values([ - { sessionId: '1', agentId: 'a1' }, - { sessionId: '2', agentId: 'a2' }, + { sessionId: '1', agentId: 'a1', userId }, + { sessionId: '2', agentId: 'a2', userId }, ]); // No topics created diff --git a/src/database/server/models/__tests__/sessionGroup.test.ts b/src/database/models/__tests__/sessionGroup.test.ts similarity index 94% rename from src/database/server/models/__tests__/sessionGroup.test.ts rename to src/database/models/__tests__/sessionGroup.test.ts index 33285f76c2978..736fe7beb188f 100644 --- a/src/database/server/models/__tests__/sessionGroup.test.ts +++ b/src/database/models/__tests__/sessionGroup.test.ts @@ -2,12 +2,13 @@ import { eq } from 'drizzle-orm/expressions'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { getTestDBInstance } from '@/database/server/core/dbForTest'; +import { LobeChatDatabase } from '@/database/type'; -import { sessionGroups, users } from '../../../schemas'; -import { SessionGroupModel } from '../sessionGroup'; +import { sessionGroups, users } from '../../schemas'; +import { SessionGroupModel } from '../../server/models/sessionGroup'; +import { getTestDB } from './_util'; -let serverDB = await getTestDBInstance(); +const serverDB: LobeChatDatabase = await getTestDB(); const userId = 'session-group-model-test-user-id'; const sessionGroupModel = new SessionGroupModel(serverDB, userId); diff --git a/src/database/server/models/__tests__/topic.test.ts b/src/database/models/__tests__/topic.test.ts similarity index 98% rename from src/database/server/models/__tests__/topic.test.ts rename to src/database/models/__tests__/topic.test.ts index 6e1bac63b6abe..e83683a3cc073 100644 --- a/src/database/server/models/__tests__/topic.test.ts +++ b/src/database/models/__tests__/topic.test.ts @@ -1,12 +1,13 @@ import { eq, inArray } from 'drizzle-orm/expressions'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { getTestDBInstance } from '@/database/server/core/dbForTest'; +import { LobeChatDatabase } from '@/database/type'; -import { messages, sessions, topics, users } from '../../../schemas'; -import { CreateTopicParams, TopicModel } from '../topic'; +import { messages, sessions, topics, users } from '../../schemas'; +import { CreateTopicParams, TopicModel } from '../../server/models/topic'; +import { getTestDB } from './_util'; -let serverDB = await getTestDBInstance(); +const serverDB: LobeChatDatabase = await getTestDB(); const userId = 'topic-user-test'; const sessionId = 'topic-session'; diff --git a/src/database/repositories/dataImporter/index.ts b/src/database/repositories/dataImporter/index.ts index d41a0e0045315..189fe98d1a7bc 100644 --- a/src/database/repositories/dataImporter/index.ts +++ b/src/database/repositories/dataImporter/index.ts @@ -138,6 +138,7 @@ export class DataImporterRepos { shouldInsertSessionAgents.map(({ id }, index) => ({ agentId: agentMapArray[index].id, sessionId: sessionIdMap[id], + userId: this.userId, })), ); } @@ -291,6 +292,7 @@ export class DataImporterRepos { state: msg.pluginState, toolCallId: msg.tool_call_id, type: msg.plugin?.type, + userId: this.userId, })), ); } @@ -302,6 +304,7 @@ export class DataImporterRepos { translateInserts.map((msg) => ({ id: messageIdMap[msg.id], ...msg.extra?.translate, + userId: this.userId, })), ); } diff --git a/src/database/schemas/file.ts b/src/database/schemas/file.ts index ded4b734aacef..e545be8095b52 100644 --- a/src/database/schemas/file.ts +++ b/src/database/schemas/file.ts @@ -1,6 +1,7 @@ /* eslint-disable sort-keys-fix/sort-keys-fix */ import { boolean, + index, integer, jsonb, pgTable, @@ -23,7 +24,9 @@ export const globalFiles = pgTable('global_files', { size: integer('size').notNull(), url: text('url').notNull(), metadata: jsonb('metadata'), - + creator: text('creator') + .references(() => users.id, { onDelete: 'set null' }) + .notNull(), createdAt: createdAt(), accessedAt: accessedAt(), }); @@ -31,31 +34,38 @@ export const globalFiles = pgTable('global_files', { export type NewGlobalFile = typeof globalFiles.$inferInsert; export type GlobalFileItem = typeof globalFiles.$inferSelect; -export const files = pgTable('files', { - id: text('id') - .$defaultFn(() => idGenerator('files')) - .primaryKey(), - - userId: text('user_id') - .references(() => users.id, { onDelete: 'cascade' }) - .notNull(), - fileType: varchar('file_type', { length: 255 }).notNull(), - fileHash: varchar('file_hash', { length: 64 }).references(() => globalFiles.hashId, { - onDelete: 'no action', - }), - name: text('name').notNull(), - size: integer('size').notNull(), - url: text('url').notNull(), - - metadata: jsonb('metadata'), - chunkTaskId: uuid('chunk_task_id').references(() => asyncTasks.id, { onDelete: 'set null' }), - embeddingTaskId: uuid('embedding_task_id').references(() => asyncTasks.id, { - onDelete: 'set null', - }), +export const files = pgTable( + 'files', + { + id: text('id') + .$defaultFn(() => idGenerator('files')) + .primaryKey(), - ...timestamps, -}); + userId: text('user_id') + .references(() => users.id, { onDelete: 'cascade' }) + .notNull(), + fileType: varchar('file_type', { length: 255 }).notNull(), + fileHash: varchar('file_hash', { length: 64 }).references(() => globalFiles.hashId, { + onDelete: 'no action', + }), + name: text('name').notNull(), + size: integer('size').notNull(), + url: text('url').notNull(), + + metadata: jsonb('metadata'), + chunkTaskId: uuid('chunk_task_id').references(() => asyncTasks.id, { onDelete: 'set null' }), + embeddingTaskId: uuid('embedding_task_id').references(() => asyncTasks.id, { + onDelete: 'set null', + }), + ...timestamps, + }, + (table) => { + return { + fileHashIdx: index('file_hash_idx').on(table.fileHash), + }; + }, +); export type NewFile = typeof files.$inferInsert; export type FileItem = typeof files.$inferSelect; @@ -97,19 +107,15 @@ export const knowledgeBaseFiles = pgTable( .references(() => files.id, { onDelete: 'cascade' }) .notNull(), - // userId: text('user_id') - // .references(() => users.id, { onDelete: 'cascade' }) - // .notNull(), + userId: text('user_id') + .references(() => users.id, { onDelete: 'cascade' }) + .notNull(), createdAt: createdAt(), }, (t) => ({ pk: primaryKey({ - columns: [ - t.knowledgeBaseId, - t.fileId, - // t.userId - ], + columns: [t.knowledgeBaseId, t.fileId], }), }), ); diff --git a/src/database/schemas/message.ts b/src/database/schemas/message.ts index 9b9fc62171355..1ebe59d650a04 100644 --- a/src/database/schemas/message.ts +++ b/src/database/schemas/message.ts @@ -95,6 +95,9 @@ export const messagePlugins = pgTable('message_plugins', { identifier: text('identifier'), state: jsonb('state'), error: jsonb('error'), + userId: text('user_id') + .references(() => users.id, { onDelete: 'cascade' }) + .notNull(), }); export type MessagePluginItem = typeof messagePlugins.$inferSelect; @@ -107,6 +110,9 @@ export const messageTTS = pgTable('message_tts', { contentMd5: text('content_md5'), fileId: text('file_id').references(() => files.id, { onDelete: 'cascade' }), voice: text('voice'), + userId: text('user_id') + .references(() => users.id, { onDelete: 'cascade' }) + .notNull(), }); export const messageTranslates = pgTable('message_translates', { @@ -116,6 +122,9 @@ export const messageTranslates = pgTable('message_translates', { content: text('content'), from: text('from'), to: text('to'), + userId: text('user_id') + .references(() => users.id, { onDelete: 'cascade' }) + .notNull(), }); // if the message contains a file @@ -129,6 +138,9 @@ export const messagesFiles = pgTable( messageId: text('message_id') .notNull() .references(() => messages.id, { onDelete: 'cascade' }), + userId: text('user_id') + .references(() => users.id, { onDelete: 'cascade' }) + .notNull(), }, (t) => ({ pk: primaryKey({ columns: [t.fileId, t.messageId] }), @@ -142,6 +154,9 @@ export const messageQueries = pgTable('message_queries', { .notNull(), rewriteQuery: text('rewrite_query'), userQuery: text('user_query'), + userId: text('user_id') + .references(() => users.id, { onDelete: 'cascade' }) + .notNull(), embeddingsId: uuid('embeddings_id').references(() => embeddings.id, { onDelete: 'set null' }), }); @@ -154,6 +169,9 @@ export const messageQueryChunks = pgTable( queryId: uuid('query_id').references(() => messageQueries.id, { onDelete: 'cascade' }), chunkId: uuid('chunk_id').references(() => chunks.id, { onDelete: 'cascade' }), similarity: numeric('similarity', { precision: 6, scale: 5 }), + userId: text('user_id') + .references(() => users.id, { onDelete: 'cascade' }) + .notNull(), }, (t) => ({ pk: primaryKey({ columns: [t.chunkId, t.messageId, t.queryId] }), @@ -168,6 +186,9 @@ export const messageChunks = pgTable( { messageId: text('message_id').references(() => messages.id, { onDelete: 'cascade' }), chunkId: uuid('chunk_id').references(() => chunks.id, { onDelete: 'cascade' }), + userId: text('user_id') + .references(() => users.id, { onDelete: 'cascade' }) + .notNull(), }, (t) => ({ pk: primaryKey({ columns: [t.chunkId, t.messageId] }), diff --git a/src/database/schemas/relations.ts b/src/database/schemas/relations.ts index 9b7361bcdccc4..4ccc88d5d0a70 100644 --- a/src/database/schemas/relations.ts +++ b/src/database/schemas/relations.ts @@ -11,6 +11,7 @@ import { messages, messagesFiles } from './message'; import { chunks, unstructuredChunks } from './rag'; import { sessionGroups, sessions } from './session'; import { threads, topics } from './topic'; +import { users } from './user'; export const agentsToSessions = pgTable( 'agents_to_sessions', @@ -21,6 +22,9 @@ export const agentsToSessions = pgTable( sessionId: text('session_id') .notNull() .references(() => sessions.id, { onDelete: 'cascade' }), + userId: text('user_id') + .references(() => users.id, { onDelete: 'cascade' }) + .notNull(), }, (t) => ({ pk: primaryKey({ columns: [t.agentId, t.sessionId] }), @@ -36,6 +40,9 @@ export const filesToSessions = pgTable( sessionId: text('session_id') .notNull() .references(() => sessions.id, { onDelete: 'cascade' }), + userId: text('user_id') + .references(() => users.id, { onDelete: 'cascade' }) + .notNull(), }, (t) => ({ pk: primaryKey({ columns: [t.fileId, t.sessionId] }), @@ -48,6 +55,9 @@ export const fileChunks = pgTable( fileId: varchar('file_id').references(() => files.id, { onDelete: 'cascade' }), chunkId: uuid('chunk_id').references(() => chunks.id, { onDelete: 'cascade' }), createdAt: createdAt(), + userId: text('user_id') + .references(() => users.id, { onDelete: 'cascade' }) + .notNull(), }, (t) => ({ pk: primaryKey({ columns: [t.fileId, t.chunkId] }), diff --git a/src/database/server/models/__tests__/nextauth.test.ts b/src/database/server/models/__tests__/nextauth.test.ts index ecd4d034bcdaf..c79217cb21eab 100644 --- a/src/database/server/models/__tests__/nextauth.test.ts +++ b/src/database/server/models/__tests__/nextauth.test.ts @@ -16,9 +16,11 @@ import { users, } from '@/database/schemas'; import { getTestDBInstance } from '@/database/server/core/dbForTest'; +import { LobeChatDatabase } from '@/database/type'; import { LobeNextAuthDbAdapter } from '@/libs/next-auth/adapter'; let serverDB = await getTestDBInstance(); + let nextAuthAdapter = LobeNextAuthDbAdapter(serverDB); const userId = 'user-db'; diff --git a/src/database/server/models/__tests__/user.test.ts b/src/database/server/models/__tests__/user.test.ts index bc5fed479c510..35f107f6ed722 100644 --- a/src/database/server/models/__tests__/user.test.ts +++ b/src/database/server/models/__tests__/user.test.ts @@ -1,15 +1,17 @@ +import { TRPCError } from '@trpc/server'; import dayjs from 'dayjs'; import { eq } from 'drizzle-orm/expressions'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { INBOX_SESSION_ID } from '@/const/session'; import { getTestDBInstance } from '@/database/server/core/dbForTest'; +import { LobeChatDatabase } from '@/database/type'; import { KeyVaultsGateKeeper } from '@/server/modules/KeyVaultsEncrypt'; import { UserGuide, UserPreference } from '@/types/user'; import { UserSettingsItem, userSettings, users } from '../../../schemas'; import { SessionModel } from '../session'; -import { UserModel } from '../user'; +import { UserModel, UserNotFoundError } from '../user'; let serverDB = await getTestDBInstance(); @@ -408,3 +410,13 @@ describe('UserModel', () => { }); }); }); + +describe('UserNotFoundError', () => { + it('should extend TRPCError with correct code and message', () => { + const error = new UserNotFoundError(); + + expect(error).toBeInstanceOf(TRPCError); + expect(error.code).toBe('UNAUTHORIZED'); + expect(error.message).toBe('user not found'); + }); +}); diff --git a/src/database/server/models/chunk.ts b/src/database/server/models/chunk.ts index 50cf1fd9452d8..20e6954439dcf 100644 --- a/src/database/server/models/chunk.ts +++ b/src/database/server/models/chunk.ts @@ -31,7 +31,11 @@ export class ChunkModel { const result = await trx.insert(chunks).values(params).returning(); - const fileChunksData = result.map((chunk) => ({ chunkId: chunk.id, fileId })); + const fileChunksData = result.map((chunk) => ({ + chunkId: chunk.id, + fileId, + userId: this.userId, + })); if (fileChunksData.length > 0) { await trx.insert(fileChunks).values(fileChunksData); diff --git a/src/database/server/models/file.ts b/src/database/server/models/file.ts index 0d2b02b82b384..3a772a9f4b593 100644 --- a/src/database/server/models/file.ts +++ b/src/database/server/models/file.ts @@ -33,6 +33,7 @@ export class FileModel { const result = await this.db.transaction(async (trx) => { if (insertToGlobalFiles) { await trx.insert(globalFiles).values({ + creator: this.userId, fileType: params.fileType, hashId: params.fileHash!, metadata: params.metadata, @@ -49,9 +50,11 @@ export class FileModel { const item = result[0]; if (params.knowledgeBaseId) { - await trx - .insert(knowledgeBaseFiles) - .values({ fileId: item.id, knowledgeBaseId: params.knowledgeBaseId }); + await trx.insert(knowledgeBaseFiles).values({ + fileId: item.id, + knowledgeBaseId: params.knowledgeBaseId, + userId: this.userId, + }); } return item; diff --git a/src/database/server/models/message.ts b/src/database/server/models/message.ts index 8d8a0a0b208e4..9895f8691c501 100644 --- a/src/database/server/models/message.ts +++ b/src/database/server/models/message.ts @@ -21,6 +21,7 @@ import { CreateMessageParams, MessageItem, ModelRankItem, + NewMessageQueryParams, UpdateMessageParams, } from '@/types/message'; import { merge } from '@/utils/merge'; @@ -28,7 +29,6 @@ import { today } from '@/utils/time'; import { MessagePluginItem, - NewMessageQuery, chunks, embeddings, fileChunks, @@ -458,13 +458,14 @@ export class MessageModel { state: pluginState, toolCallId: message.tool_call_id, type: plugin?.type, + userId: this.userId, }); } if (files && files.length > 0) { await trx .insert(messagesFiles) - .values(files.map((file) => ({ fileId: file, messageId: id }))); + .values(files.map((file) => ({ fileId: file, messageId: id, userId: this.userId }))); } if (fileChunks && fileChunks.length > 0 && ragQueryId) { @@ -474,6 +475,7 @@ export class MessageModel { messageId: id, queryId: ragQueryId, similarity: chunk.similarity?.toString(), + userId: this.userId, })), ); } @@ -491,8 +493,11 @@ export class MessageModel { return this.db.insert(messages).values(messagesToInsert); }; - createMessageQuery = async (params: NewMessageQuery) => { - const result = await this.db.insert(messageQueries).values(params).returning(); + createMessageQuery = async (params: NewMessageQueryParams) => { + const result = await this.db + .insert(messageQueries) + .values({ ...params, userId: this.userId }) + .returning(); return result[0]; }; @@ -504,7 +509,9 @@ export class MessageModel { if (imageList && imageList.length > 0) { await trx .insert(messagesFiles) - .values(imageList.map((file) => ({ fileId: file.id, messageId: id }))); + .values( + imageList.map((file) => ({ fileId: file.id, messageId: id, userId: this.userId })), + ); } return trx @@ -547,7 +554,7 @@ export class MessageModel { // If the message does not exist in the translate table, insert it if (!result) { - return this.db.insert(messageTranslates).values({ ...translate, id }); + return this.db.insert(messageTranslates).values({ ...translate, id, userId: this.userId }); } // or just update the existing one @@ -561,9 +568,13 @@ export class MessageModel { // If the message does not exist in the translate table, insert it if (!result) { - return this.db - .insert(messageTTS) - .values({ contentMd5: tts.contentMd5, fileId: tts.file, id, voice: tts.voice }); + return this.db.insert(messageTTS).values({ + contentMd5: tts.contentMd5, + fileId: tts.file, + id, + userId: this.userId, + voice: tts.voice, + }); } // or just update the existing one @@ -618,13 +629,19 @@ export class MessageModel { .where(and(eq(messages.userId, this.userId), inArray(messages.id, ids))); deleteMessageTranslate = async (id: string) => - this.db.delete(messageTranslates).where(and(eq(messageTranslates.id, id))); + this.db + .delete(messageTranslates) + .where(and(eq(messageTranslates.id, id), eq(messageTranslates.userId, this.userId))); deleteMessageTTS = async (id: string) => - this.db.delete(messageTTS).where(and(eq(messageTTS.id, id))); + this.db + .delete(messageTTS) + .where(and(eq(messageTTS.id, id), eq(messageTTS.userId, this.userId))); deleteMessageQuery = async (id: string) => - this.db.delete(messageQueries).where(and(eq(messageQueries.id, id))); + this.db + .delete(messageQueries) + .where(and(eq(messageQueries.id, id), eq(messageQueries.userId, this.userId))); deleteMessagesBySession = async (sessionId?: string | null, topicId?: string | null) => this.db diff --git a/src/database/server/models/session.ts b/src/database/server/models/session.ts index 1d75351d2d526..c76fbe222cca9 100644 --- a/src/database/server/models/session.ts +++ b/src/database/server/models/session.ts @@ -220,6 +220,7 @@ export class SessionModel { await trx.insert(agentsToSessions).values({ agentId: newAgents[0].id, sessionId: id, + userId: this.userId, }); return result[0]; diff --git a/src/database/server/models/user.test.ts b/src/database/server/models/user.test.ts deleted file mode 100644 index 4cc62ca3f0654..0000000000000 --- a/src/database/server/models/user.test.ts +++ /dev/null @@ -1,58 +0,0 @@ -// @vitest-environment node -import { TRPCError } from '@trpc/server'; -import { describe, expect, it, vi } from 'vitest'; - -import { UserModel, UserNotFoundError } from '@/database/server/models/user'; - -describe('UserNotFoundError', () => { - it('should extend TRPCError with correct code and message', () => { - const error = new UserNotFoundError(); - - expect(error).toBeInstanceOf(TRPCError); - expect(error.code).toBe('UNAUTHORIZED'); - expect(error.message).toBe('user not found'); - }); -}); - -describe('UserModel', () => { - const mockDb = { - query: { - users: { - findFirst: vi.fn(), - }, - }, - }; - - const mockUserId = 'test-user-id'; - const userModel = new UserModel(mockDb as any, mockUserId); - - describe('getUserRegistrationDuration', () => { - it('should return default values when user not found', async () => { - mockDb.query.users.findFirst.mockResolvedValue(null); - - const result = await userModel.getUserRegistrationDuration(); - - expect(result).toEqual({ - createdAt: expect.any(String), - duration: 1, - updatedAt: expect.any(String), - }); - }); - - it('should calculate duration correctly for existing user', async () => { - const createdAt = new Date('2024-01-01'); - mockDb.query.users.findFirst.mockResolvedValue({ - createdAt, - }); - - const result = await userModel.getUserRegistrationDuration(); - - expect(result).toEqual({ - createdAt: '2024-01-01', - duration: expect.any(Number), - updatedAt: expect.any(String), - }); - expect(result.duration).toBeGreaterThan(0); - }); - }); -}); diff --git a/src/services/file/client.test.ts b/src/services/file/client.test.ts index 32c3c57f3b1cd..94b46921d7b21 100644 --- a/src/services/file/client.test.ts +++ b/src/services/file/client.test.ts @@ -92,7 +92,7 @@ describe('FileService', () => { hashId: '123tttt', }; - await clientDB.insert(globalFiles).values(file); + await clientDB.insert(globalFiles).values({ ...file, creator: userId }); await clientDB.insert(files).values({ id: fileId, @@ -174,6 +174,7 @@ describe('FileService', () => { await clientDB.insert(globalFiles).values({ ...mockFile, hashId: hash, + creator: userId, }); await clientDB.insert(files).values({ id: '1', diff --git a/src/services/message/client.test.ts b/src/services/message/client.test.ts index 773b3df7dd41c..102993742d24e 100644 --- a/src/services/message/client.test.ts +++ b/src/services/message/client.test.ts @@ -284,7 +284,7 @@ describe('MessageClientService', () => { it('should update the plugin state of a message', async () => { // Setup await clientDB.insert(messages).values({ id: mockMessageId, role: 'user', userId }); - await clientDB.insert(messagePlugins).values({ id: mockMessageId }); + await clientDB.insert(messagePlugins).values({ id: mockMessageId, userId }); const key = 'stateKey'; const value = 'stateValue'; const newPluginState = { [key]: value }; @@ -304,7 +304,7 @@ describe('MessageClientService', () => { it('should update the plugin arguments object of a message', async () => { // Setup await clientDB.insert(messages).values({ id: mockMessageId, role: 'user', userId }); - await clientDB.insert(messagePlugins).values({ id: mockMessageId }); + await clientDB.insert(messagePlugins).values({ id: mockMessageId, userId }); const value = 'stateValue'; // Execute @@ -319,7 +319,7 @@ describe('MessageClientService', () => { it('should update the plugin arguments string of a message', async () => { // Setup await clientDB.insert(messages).values({ id: mockMessageId, role: 'user', userId }); - await clientDB.insert(messagePlugins).values({ id: mockMessageId }); + await clientDB.insert(messagePlugins).values({ id: mockMessageId, userId }); const value = 'stateValue'; // Execute await messageService.updateMessagePluginArguments( diff --git a/src/services/session/client.test.ts b/src/services/session/client.test.ts index e4eaa7a75019c..c780d538b6c50 100644 --- a/src/services/session/client.test.ts +++ b/src/services/session/client.test.ts @@ -32,7 +32,9 @@ beforeEach(async () => { await trx.insert(users).values([{ id: userId }, { id: '456' }]); await trx.insert(sessions).values([{ id: mockSessionId, userId }]); await trx.insert(agents).values([{ id: mockAgentId, userId }]); - await trx.insert(agentsToSessions).values([{ agentId: mockAgentId, sessionId: mockSessionId }]); + await trx + .insert(agentsToSessions) + .values([{ agentId: mockAgentId, sessionId: mockSessionId, userId }]); await trx.insert(sessionGroups).values([ { id: 'group-1', name: 'group-A', sort: 2, userId }, { id: 'group-2', name: 'group-B', sort: 1, userId }, @@ -176,7 +178,7 @@ describe('SessionService', () => { await clientDB.insert(agents).values({ userId, id: 'agent-1', title: 'Session Name' }); await clientDB .insert(agentsToSessions) - .values({ agentId: 'agent-1', sessionId: mockSessionId }); + .values({ agentId: 'agent-1', sessionId: mockSessionId, userId }); // Execute const keyword = 'Name'; @@ -201,7 +203,7 @@ describe('SessionService', () => { await clientDB.insert(agents).values({ userId, id: 'agent-1' }); await clientDB .insert(agentsToSessions) - .values({ agentId: 'agent-1', sessionId: 'duplicated-session-id' }); + .values({ agentId: 'agent-1', sessionId: 'duplicated-session-id', userId }); // Execute const duplicatedSessionId = await sessionService.cloneSession(mockSessionId, newTitle); diff --git a/src/types/message/base.ts b/src/types/message/base.ts index 8f923aab70d6c..9b4d5877c9416 100644 --- a/src/types/message/base.ts +++ b/src/types/message/base.ts @@ -117,3 +117,10 @@ export interface UpdateMessageParams { toolCalls?: MessageToolCall[]; tools?: ChatToolPayload[] | null; } + +export interface NewMessageQueryParams { + embeddingsId: string; + messageId: string; + rewriteQuery: string; + userQuery: string; +} diff --git a/vitest.server.config.ts b/vitest.server.config.ts index 84906f5986fc1..e901eb3e44e16 100644 --- a/vitest.server.config.ts +++ b/vitest.server.config.ts @@ -15,7 +15,7 @@ export default defineConfig({ reportsDirectory: './coverage/server', }, environment: 'node', - include: ['src/database/server/**/**/*.test.ts'], + include: ['src/database/models/**/**/*.test.ts', 'src/database/server/**/**/*.test.ts'], poolOptions: { threads: { singleThread: true }, },