From e1262506c4168d9a9f90213cdf2e97e8581a6ae4 Mon Sep 17 00:00:00 2001 From: static Date: Mon, 13 Jan 2025 09:10:56 +0900 Subject: [PATCH 01/53] =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20DB=20=ED=85=8C=EC=9D=B4=EB=B8=94=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/server/db/category.ts | 111 +++++++++++++++++++++++++++ src/lib/server/db/error.ts | 4 + src/lib/server/db/file.ts | 54 ++++++++++++- src/lib/server/db/schema/category.ts | 52 +++++++++++++ src/lib/server/db/schema/file.ts | 25 +++++- src/lib/server/db/schema/index.ts | 1 + 6 files changed, 244 insertions(+), 3 deletions(-) create mode 100644 src/lib/server/db/category.ts create mode 100644 src/lib/server/db/schema/category.ts diff --git a/src/lib/server/db/category.ts b/src/lib/server/db/category.ts new file mode 100644 index 0000000..db543ab --- /dev/null +++ b/src/lib/server/db/category.ts @@ -0,0 +1,111 @@ +import { and, eq, isNull } from "drizzle-orm"; +import db from "./drizzle"; +import { IntegrityError } from "./error"; +import { category, categoryLog, mek } from "./schema"; + +type CategoryId = "root" | number; + +export interface NewCategoryParams { + parentId: "root" | number; + userId: number; + mekVersion: number; + encDek: string; + dekVersion: Date; + encName: string; + encNameIv: string; +} + +export const registerCategory = async (params: NewCategoryParams) => { + await db.transaction( + async (tx) => { + const meks = await tx + .select({ version: mek.version }) + .from(mek) + .where(and(eq(mek.userId, params.userId), eq(mek.state, "active"))) + .limit(1); + if (meks[0]?.version !== params.mekVersion) { + throw new IntegrityError("Inactive MEK version"); + } + + const newCategories = await tx + .insert(category) + .values({ + parentId: params.parentId === "root" ? null : params.parentId, + userId: params.userId, + mekVersion: params.mekVersion, + encDek: params.encDek, + dekVersion: params.dekVersion, + encName: { ciphertext: params.encName, iv: params.encNameIv }, + }) + .returning({ id: category.id }); + const { id: categoryId } = newCategories[0]!; + await tx.insert(categoryLog).values({ + categoryId, + timestamp: new Date(), + action: "create", + newName: { ciphertext: params.encName, iv: params.encNameIv }, + }); + }, + { behavior: "exclusive" }, + ); +}; + +export const getAllCategoriesByParent = async (userId: number, parentId: CategoryId) => { + return await db + .select() + .from(category) + .where( + and( + eq(category.userId, userId), + parentId === "root" ? isNull(category.parentId) : eq(category.parentId, parentId), + ), + ); +}; + +export const getCategory = async (userId: number, categoryId: number) => { + const res = await db + .select() + .from(category) + .where(and(eq(category.userId, userId), eq(category.id, categoryId))) + .limit(1); + return res[0] ?? null; +}; + +export const setCategoryEncName = async ( + userId: number, + categoryId: number, + dekVersion: Date, + encName: string, + encNameIv: string, +) => { + await db.transaction( + async (tx) => { + const categories = await tx + .select({ version: category.dekVersion }) + .from(category) + .where(and(eq(category.userId, userId), eq(category.id, categoryId))) + .limit(1); + if (!categories[0]) { + throw new IntegrityError("Category not found"); + } else if (categories[0].version.getTime() !== dekVersion.getTime()) { + throw new IntegrityError("Invalid DEK version"); + } + + await tx + .update(category) + .set({ encName: { ciphertext: encName, iv: encNameIv } }) + .where(and(eq(category.userId, userId), eq(category.id, categoryId))); + await tx.insert(categoryLog).values({ + categoryId, + timestamp: new Date(), + action: "rename", + newName: { ciphertext: encName, iv: encNameIv }, + }); + }, + { behavior: "exclusive" }, + ); +}; + +export const unregisterCategory = async (userId: number, categoryId: number) => { + await db.delete(category).where(and(eq(category.userId, userId), eq(category.id, categoryId))); +}; diff --git a/src/lib/server/db/error.ts b/src/lib/server/db/error.ts index 547cc6c..a145f14 100644 --- a/src/lib/server/db/error.ts +++ b/src/lib/server/db/error.ts @@ -1,4 +1,6 @@ type IntegrityErrorMessages = + // Category + | "Category not found" // Challenge | "Challenge already registered" // Client @@ -7,6 +9,8 @@ type IntegrityErrorMessages = // File | "Directory not found" | "File not found" + | "File not found in category" + | "File already added to category" | "Invalid DEK version" // HSK | "HSK already registered" diff --git a/src/lib/server/db/file.ts b/src/lib/server/db/file.ts index b99bd38..5d89306 100644 --- a/src/lib/server/db/file.ts +++ b/src/lib/server/db/file.ts @@ -1,7 +1,8 @@ +import { SqliteError } from "better-sqlite3"; import { and, eq, isNull } from "drizzle-orm"; import db from "./drizzle"; import { IntegrityError } from "./error"; -import { directory, directoryLog, file, fileLog, hsk, mek } from "./schema"; +import { directory, directoryLog, file, fileLog, fileCategory, hsk, mek } from "./schema"; type DirectoryId = "root" | number; @@ -237,6 +238,14 @@ export const getAllFilesByParent = async (userId: number, parentId: DirectoryId) ); }; +export const getAllFilesByCategory = async (userId: number, categoryId: number) => { + return await db + .select() + .from(file) + .innerJoin(fileCategory, eq(file.id, fileCategory.fileId)) + .where(and(eq(file.userId, userId), eq(fileCategory.categoryId, categoryId))); +}; + export const getAllFileIdsByContentHmac = async ( userId: number, hskVersion: number, @@ -308,3 +317,46 @@ export const unregisterFile = async (userId: number, fileId: number) => { } return files[0].path; }; + +export const addFileToCategory = async (fileId: number, categoryId: number) => { + await db.transaction( + async (tx) => { + try { + await tx.insert(fileCategory).values({ fileId, categoryId }); + await tx.insert(fileLog).values({ + fileId, + timestamp: new Date(), + action: "addToCategory", + categoryId, + }); + } catch (e) { + if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_PRIMARYKEY") { + throw new IntegrityError("File already added to category"); + } + throw e; + } + }, + { behavior: "exclusive" }, + ); +}; + +export const removeFileFromCategory = async (fileId: number, categoryId: number) => { + await db.transaction( + async (tx) => { + const res = await tx + .delete(fileCategory) + .where(and(eq(fileCategory.fileId, fileId), eq(fileCategory.categoryId, categoryId))); + if (res.changes === 0) { + throw new IntegrityError("File not found in category"); + } + + await tx.insert(fileLog).values({ + fileId, + timestamp: new Date(), + action: "removeFromCategory", + categoryId, + }); + }, + { behavior: "exclusive" }, + ); +}; diff --git a/src/lib/server/db/schema/category.ts b/src/lib/server/db/schema/category.ts new file mode 100644 index 0000000..f8e1621 --- /dev/null +++ b/src/lib/server/db/schema/category.ts @@ -0,0 +1,52 @@ +import { + sqliteTable, + text, + integer, + foreignKey, + type AnySQLiteColumn, +} from "drizzle-orm/sqlite-core"; +import { mek } from "./mek"; +import { user } from "./user"; + +const ciphertext = (name: string) => + text(name, { mode: "json" }).$type<{ + ciphertext: string; // Base64 + iv: string; // Base64 + }>(); + +export const category = sqliteTable( + "category", + { + id: integer("id").primaryKey({ autoIncrement: true }), + parentId: integer("parent_id").references((): AnySQLiteColumn => category.id, { + onDelete: "cascade", + }), + userId: integer("user_id") + .notNull() + .references(() => user.id), + mekVersion: integer("master_encryption_key_version").notNull(), + encDek: text("encrypted_data_encryption_key").notNull().unique(), // Base64 + dekVersion: integer("data_encryption_key_version", { mode: "timestamp_ms" }).notNull(), + encName: ciphertext("encrypted_name").notNull(), + }, + (t) => ({ + ref1: foreignKey({ + columns: [t.parentId], + foreignColumns: [t.id], + }), + ref2: foreignKey({ + columns: [t.userId, t.mekVersion], + foreignColumns: [mek.userId, mek.version], + }), + }), +); + +export const categoryLog = sqliteTable("category_log", { + id: integer("id").primaryKey({ autoIncrement: true }), + categoryId: integer("category_id") + .notNull() + .references(() => category.id, { onDelete: "cascade" }), + timestamp: integer("timestamp", { mode: "timestamp_ms" }).notNull(), + action: text("action", { enum: ["create", "rename"] }).notNull(), + newName: ciphertext("new_name"), +}); diff --git a/src/lib/server/db/schema/file.ts b/src/lib/server/db/schema/file.ts index 65c5471..9400ae9 100644 --- a/src/lib/server/db/schema/file.ts +++ b/src/lib/server/db/schema/file.ts @@ -1,4 +1,5 @@ -import { sqliteTable, text, integer, foreignKey } from "drizzle-orm/sqlite-core"; +import { sqliteTable, text, integer, primaryKey, foreignKey } from "drizzle-orm/sqlite-core"; +import { category } from "./category"; import { hsk } from "./hsk"; import { mek } from "./mek"; import { user } from "./user"; @@ -82,6 +83,26 @@ export const fileLog = sqliteTable("file_log", { .notNull() .references(() => file.id, { onDelete: "cascade" }), timestamp: integer("timestamp", { mode: "timestamp_ms" }).notNull(), - action: text("action", { enum: ["create", "rename"] }).notNull(), + action: text("action", { + enum: ["create", "rename", "addToCategory", "removeFromCategory"], + }).notNull(), newName: ciphertext("new_name"), + categoryId: integer("category_id").references(() => category.id, { onDelete: "set null" }), }); + +export const fileCategory = sqliteTable( + "file_category", + { + fileId: integer("file_id") + .notNull() + .references(() => file.id, { onDelete: "cascade" }), + categoryId: integer("category_id") + .notNull() + .references(() => category.id, { onDelete: "cascade" }), + }, + (t) => ({ + pk: primaryKey({ + columns: [t.fileId, t.categoryId], + }), + }), +); diff --git a/src/lib/server/db/schema/index.ts b/src/lib/server/db/schema/index.ts index 40cb9be..13aff6b 100644 --- a/src/lib/server/db/schema/index.ts +++ b/src/lib/server/db/schema/index.ts @@ -1,3 +1,4 @@ +export * from "./category"; export * from "./client"; export * from "./file"; export * from "./hsk"; From 2b303f91978a220877f7f4cf005ca82218a37f57 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 18 Jan 2025 16:29:39 +0900 Subject: [PATCH 02/53] =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EB=AA=A9=EB=A1=9D?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=97=85=EB=A1=9C=EB=93=9C=20=EC=A4=91?= =?UTF-8?q?=EC=9D=B8=20=ED=8C=8C=EC=9D=BC=EC=9D=B4=20=ED=91=9C=EC=8B=9C?= =?UTF-8?q?=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8D=98=20=EB=B2=84=EA=B7=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../directory/[[id]]/DirectoryEntries/DirectoryEntries.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/DirectoryEntries.svelte b/src/routes/(main)/directory/[[id]]/DirectoryEntries/DirectoryEntries.svelte index e2a187c..e482e38 100644 --- a/src/routes/(main)/directory/[[id]]/DirectoryEntries/DirectoryEntries.svelte +++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/DirectoryEntries.svelte @@ -68,7 +68,7 @@ $fileUploadStatusStore .filter((statusStore) => { const { parentId, status } = get(statusStore); - return parentId === info.id && !isFileUploading(status); + return parentId === info.id && isFileUploading(status); }) .map((status) => ({ type: "uploading-file", From 2af3caf3b9bdf0ba2a22446de13b77a892fb4946 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 18 Jan 2025 16:45:07 +0900 Subject: [PATCH 03/53] =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EB=B0=8F=20?= =?UTF-8?q?=EB=94=94=EB=A0=89=ED=84=B0=EB=A6=AC=20=EB=AA=A9=EB=A1=9D?= =?UTF-8?q?=EC=9D=84=20=EC=A0=95=EB=A0=AC=ED=95=A0=20=EB=95=8C=20=EC=9E=90?= =?UTF-8?q?=EC=97=B0=20=EC=A0=95=EB=A0=AC=EC=9D=84=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.html | 2 +- .../(main)/directory/[[id]]/DirectoryEntries/service.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app.html b/src/app.html index 4471298..2e7fd3e 100644 --- a/src/app.html +++ b/src/app.html @@ -1,5 +1,5 @@ - + diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts b/src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts index e1fc716..b797727 100644 --- a/src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts +++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts @@ -5,8 +5,10 @@ export enum SortBy { type SortFunc = (a?: string, b?: string) => number; +const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" }); + const sortByNameAsc: SortFunc = (a, b) => { - if (a && b) return a.localeCompare(b); + if (a && b) return collator.compare(a, b); if (a) return -1; if (b) return 1; return 0; From 10eba7844471e4703965d1a81d32bb44ede4dff0 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 18 Jan 2025 18:12:40 +0900 Subject: [PATCH 04/53] =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=97=85=EB=A1=9C?= =?UTF-8?q?=EB=93=9C=EC=8B=9C=EC=9D=98=20=EC=B2=B4=ED=81=AC=EC=84=AC=20?= =?UTF-8?q?=EA=B2=80=EC=82=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- drizzle/0002_good_talisman.sql | 1 + drizzle/meta/0002_snapshot.json | 1308 +++++++++++++++++++++++++ drizzle/meta/_journal.json | 7 + src/lib/modules/file/upload.ts | 10 +- src/lib/server/db/file.ts | 6 +- src/lib/server/db/schema/file.ts | 1 + src/lib/server/services/file.ts | 20 +- src/routes/api/file/upload/+server.ts | 52 +- 8 files changed, 1379 insertions(+), 26 deletions(-) create mode 100644 drizzle/0002_good_talisman.sql create mode 100644 drizzle/meta/0002_snapshot.json diff --git a/drizzle/0002_good_talisman.sql b/drizzle/0002_good_talisman.sql new file mode 100644 index 0000000..55387df --- /dev/null +++ b/drizzle/0002_good_talisman.sql @@ -0,0 +1 @@ +ALTER TABLE `file` ADD `encrypted_content_hash` text NOT NULL; \ No newline at end of file diff --git a/drizzle/meta/0002_snapshot.json b/drizzle/meta/0002_snapshot.json new file mode 100644 index 0000000..d9da594 --- /dev/null +++ b/drizzle/meta/0002_snapshot.json @@ -0,0 +1,1308 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "93a0c9c7-dde3-4025-bd59-0fe3a6b70fa0", + "prevId": "5e999e6f-1ec4-40b0-bb10-741ffc6da4af", + "tables": { + "client": { + "name": "client", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "encryption_public_key": { + "name": "encryption_public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "signature_public_key": { + "name": "signature_public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "client_encryption_public_key_unique": { + "name": "client_encryption_public_key_unique", + "columns": [ + "encryption_public_key" + ], + "isUnique": true + }, + "client_signature_public_key_unique": { + "name": "client_signature_public_key_unique", + "columns": [ + "signature_public_key" + ], + "isUnique": true + }, + "client_encryption_public_key_signature_public_key_unique": { + "name": "client_encryption_public_key_signature_public_key_unique", + "columns": [ + "encryption_public_key", + "signature_public_key" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "user_client": { + "name": "user_client", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'challenging'" + } + }, + "indexes": {}, + "foreignKeys": { + "user_client_user_id_user_id_fk": { + "name": "user_client_user_id_user_id_fk", + "tableFrom": "user_client", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "user_client_client_id_client_id_fk": { + "name": "user_client_client_id_client_id_fk", + "tableFrom": "user_client", + "tableTo": "client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_client_user_id_client_id_pk": { + "columns": [ + "client_id", + "user_id" + ], + "name": "user_client_user_id_client_id_pk" + } + }, + "uniqueConstraints": {} + }, + "user_client_challenge": { + "name": "user_client_challenge", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "answer": { + "name": "answer", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "allowed_ip": { + "name": "allowed_ip", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "user_client_challenge_answer_unique": { + "name": "user_client_challenge_answer_unique", + "columns": [ + "answer" + ], + "isUnique": true + } + }, + "foreignKeys": { + "user_client_challenge_user_id_user_id_fk": { + "name": "user_client_challenge_user_id_user_id_fk", + "tableFrom": "user_client_challenge", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "user_client_challenge_client_id_client_id_fk": { + "name": "user_client_challenge_client_id_client_id_fk", + "tableFrom": "user_client_challenge", + "tableTo": "client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "user_client_challenge_user_id_client_id_user_client_user_id_client_id_fk": { + "name": "user_client_challenge_user_id_client_id_user_client_user_id_client_id_fk", + "tableFrom": "user_client_challenge", + "tableTo": "user_client", + "columnsFrom": [ + "user_id", + "client_id" + ], + "columnsTo": [ + "user_id", + "client_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "directory": { + "name": "directory", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "parent_id": { + "name": "parent_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "master_encryption_key_version": { + "name": "master_encryption_key_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_data_encryption_key": { + "name": "encrypted_data_encryption_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "data_encryption_key_version": { + "name": "data_encryption_key_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_name": { + "name": "encrypted_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "directory_encrypted_data_encryption_key_unique": { + "name": "directory_encrypted_data_encryption_key_unique", + "columns": [ + "encrypted_data_encryption_key" + ], + "isUnique": true + } + }, + "foreignKeys": { + "directory_user_id_user_id_fk": { + "name": "directory_user_id_user_id_fk", + "tableFrom": "directory", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "directory_parent_id_directory_id_fk": { + "name": "directory_parent_id_directory_id_fk", + "tableFrom": "directory", + "tableTo": "directory", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "directory_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { + "name": "directory_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", + "tableFrom": "directory", + "tableTo": "master_encryption_key", + "columnsFrom": [ + "user_id", + "master_encryption_key_version" + ], + "columnsTo": [ + "user_id", + "version" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "directory_log": { + "name": "directory_log", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "directory_id": { + "name": "directory_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "new_name": { + "name": "new_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "directory_log_directory_id_directory_id_fk": { + "name": "directory_log_directory_id_directory_id_fk", + "tableFrom": "directory_log", + "tableTo": "directory", + "columnsFrom": [ + "directory_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "file": { + "name": "file", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "parent_id": { + "name": "parent_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "master_encryption_key_version": { + "name": "master_encryption_key_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_data_encryption_key": { + "name": "encrypted_data_encryption_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "data_encryption_key_version": { + "name": "data_encryption_key_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "hmac_secret_key_version": { + "name": "hmac_secret_key_version", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content_hmac": { + "name": "content_hmac", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_content_iv": { + "name": "encrypted_content_iv", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_content_hash": { + "name": "encrypted_content_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_name": { + "name": "encrypted_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_created_at": { + "name": "encrypted_created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_last_modified_at": { + "name": "encrypted_last_modified_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "file_path_unique": { + "name": "file_path_unique", + "columns": [ + "path" + ], + "isUnique": true + }, + "file_encrypted_data_encryption_key_unique": { + "name": "file_encrypted_data_encryption_key_unique", + "columns": [ + "encrypted_data_encryption_key" + ], + "isUnique": true + } + }, + "foreignKeys": { + "file_parent_id_directory_id_fk": { + "name": "file_parent_id_directory_id_fk", + "tableFrom": "file", + "tableTo": "directory", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "file_user_id_user_id_fk": { + "name": "file_user_id_user_id_fk", + "tableFrom": "file", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "file_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { + "name": "file_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", + "tableFrom": "file", + "tableTo": "master_encryption_key", + "columnsFrom": [ + "user_id", + "master_encryption_key_version" + ], + "columnsTo": [ + "user_id", + "version" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "file_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk": { + "name": "file_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk", + "tableFrom": "file", + "tableTo": "hmac_secret_key", + "columnsFrom": [ + "user_id", + "hmac_secret_key_version" + ], + "columnsTo": [ + "user_id", + "version" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "file_log": { + "name": "file_log", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "file_id": { + "name": "file_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "new_name": { + "name": "new_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "file_log_file_id_file_id_fk": { + "name": "file_log_file_id_file_id_fk", + "tableFrom": "file_log", + "tableTo": "file", + "columnsFrom": [ + "file_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "hmac_secret_key": { + "name": "hmac_secret_key", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "master_encryption_key_version": { + "name": "master_encryption_key_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "hmac_secret_key_encrypted_key_unique": { + "name": "hmac_secret_key_encrypted_key_unique", + "columns": [ + "encrypted_key" + ], + "isUnique": true + } + }, + "foreignKeys": { + "hmac_secret_key_user_id_user_id_fk": { + "name": "hmac_secret_key_user_id_user_id_fk", + "tableFrom": "hmac_secret_key", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "hmac_secret_key_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { + "name": "hmac_secret_key_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", + "tableFrom": "hmac_secret_key", + "tableTo": "master_encryption_key", + "columnsFrom": [ + "user_id", + "master_encryption_key_version" + ], + "columnsTo": [ + "user_id", + "version" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "hmac_secret_key_user_id_version_pk": { + "columns": [ + "user_id", + "version" + ], + "name": "hmac_secret_key_user_id_version_pk" + } + }, + "uniqueConstraints": {} + }, + "hmac_secret_key_log": { + "name": "hmac_secret_key_log", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "hmac_secret_key_version": { + "name": "hmac_secret_key_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action_by": { + "name": "action_by", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "hmac_secret_key_log_user_id_user_id_fk": { + "name": "hmac_secret_key_log_user_id_user_id_fk", + "tableFrom": "hmac_secret_key_log", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "hmac_secret_key_log_action_by_user_id_fk": { + "name": "hmac_secret_key_log_action_by_user_id_fk", + "tableFrom": "hmac_secret_key_log", + "tableTo": "user", + "columnsFrom": [ + "action_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "hmac_secret_key_log_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk": { + "name": "hmac_secret_key_log_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk", + "tableFrom": "hmac_secret_key_log", + "tableTo": "hmac_secret_key", + "columnsFrom": [ + "user_id", + "hmac_secret_key_version" + ], + "columnsTo": [ + "user_id", + "version" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "client_master_encryption_key": { + "name": "client_master_encryption_key", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_key_signature": { + "name": "encrypted_key_signature", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "client_master_encryption_key_user_id_user_id_fk": { + "name": "client_master_encryption_key_user_id_user_id_fk", + "tableFrom": "client_master_encryption_key", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "client_master_encryption_key_client_id_client_id_fk": { + "name": "client_master_encryption_key_client_id_client_id_fk", + "tableFrom": "client_master_encryption_key", + "tableTo": "client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "client_master_encryption_key_user_id_version_master_encryption_key_user_id_version_fk": { + "name": "client_master_encryption_key_user_id_version_master_encryption_key_user_id_version_fk", + "tableFrom": "client_master_encryption_key", + "tableTo": "master_encryption_key", + "columnsFrom": [ + "user_id", + "version" + ], + "columnsTo": [ + "user_id", + "version" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "client_master_encryption_key_user_id_client_id_version_pk": { + "columns": [ + "client_id", + "user_id", + "version" + ], + "name": "client_master_encryption_key_user_id_client_id_version_pk" + } + }, + "uniqueConstraints": {} + }, + "master_encryption_key": { + "name": "master_encryption_key", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "retired_at": { + "name": "retired_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "master_encryption_key_user_id_user_id_fk": { + "name": "master_encryption_key_user_id_user_id_fk", + "tableFrom": "master_encryption_key", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "master_encryption_key_user_id_version_pk": { + "columns": [ + "user_id", + "version" + ], + "name": "master_encryption_key_user_id_version_pk" + } + }, + "uniqueConstraints": {} + }, + "master_encryption_key_log": { + "name": "master_encryption_key_log", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "master_encryption_key_version": { + "name": "master_encryption_key_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action_by": { + "name": "action_by", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "master_encryption_key_log_user_id_user_id_fk": { + "name": "master_encryption_key_log_user_id_user_id_fk", + "tableFrom": "master_encryption_key_log", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "master_encryption_key_log_action_by_client_id_fk": { + "name": "master_encryption_key_log_action_by_client_id_fk", + "tableFrom": "master_encryption_key_log", + "tableTo": "client", + "columnsFrom": [ + "action_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "master_encryption_key_log_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { + "name": "master_encryption_key_log_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", + "tableFrom": "master_encryption_key_log", + "tableTo": "master_encryption_key", + "columnsFrom": [ + "user_id", + "master_encryption_key_version" + ], + "columnsTo": [ + "user_id", + "version" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used_by_ip": { + "name": "last_used_by_ip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_used_by_user_agent": { + "name": "last_used_by_user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "session_user_id_client_id_unique": { + "name": "session_user_id_client_id_unique", + "columns": [ + "user_id", + "client_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "session_client_id_client_id_fk": { + "name": "session_client_id_client_id_fk", + "tableFrom": "session", + "tableTo": "client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "session_upgrade_challenge": { + "name": "session_upgrade_challenge", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "answer": { + "name": "answer", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "allowed_ip": { + "name": "allowed_ip", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "session_upgrade_challenge_session_id_unique": { + "name": "session_upgrade_challenge_session_id_unique", + "columns": [ + "session_id" + ], + "isUnique": true + }, + "session_upgrade_challenge_answer_unique": { + "name": "session_upgrade_challenge_answer_unique", + "columns": [ + "answer" + ], + "isUnique": true + } + }, + "foreignKeys": { + "session_upgrade_challenge_session_id_session_id_fk": { + "name": "session_upgrade_challenge_session_id_session_id_fk", + "tableFrom": "session_upgrade_challenge", + "tableTo": "session", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "session_upgrade_challenge_client_id_client_id_fk": { + "name": "session_upgrade_challenge_client_id_client_id_fk", + "tableFrom": "session_upgrade_challenge", + "tableTo": "client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "nickname": { + "name": "nickname", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 65be42a..f5d931a 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -15,6 +15,13 @@ "when": 1736720831242, "tag": "0001_blushing_alice", "breakpoints": true + }, + { + "idx": 2, + "version": "6", + "when": 1737191517463, + "tag": "0002_good_talisman", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/lib/modules/file/upload.ts b/src/lib/modules/file/upload.ts index 2518c7f..a2a9707 100644 --- a/src/lib/modules/file/upload.ts +++ b/src/lib/modules/file/upload.ts @@ -8,6 +8,7 @@ import { wrapDataKey, encryptData, encryptString, + digestMessage, signMessageHmac, } from "$lib/modules/crypto"; import type { @@ -97,6 +98,8 @@ const encryptFile = limitFunction( const dataKeyWrapped = await wrapDataKey(dataKey, masterKey.key); const fileEncrypted = await encryptData(fileBuffer, dataKey); + const fileEncryptedHash = encodeToBase64(await digestMessage(fileEncrypted.ciphertext)); + const nameEncrypted = await encryptString(file.name, dataKey); const createdAtEncrypted = createdAt && (await encryptString(createdAt.getTime().toString(), dataKey)); @@ -110,8 +113,9 @@ const encryptFile = limitFunction( return { dataKeyWrapped, dataKeyVersion, - fileEncrypted, fileType, + fileEncrypted, + fileEncryptedHash, nameEncrypted, createdAtEncrypted, lastModifiedAtEncrypted, @@ -184,8 +188,9 @@ export const uploadFile = async ( const { dataKeyWrapped, dataKeyVersion, - fileEncrypted, fileType, + fileEncrypted, + fileEncryptedHash, nameEncrypted, createdAtEncrypted, lastModifiedAtEncrypted, @@ -212,6 +217,7 @@ export const uploadFile = async ( } as FileUploadRequest), ); form.set("content", new Blob([fileEncrypted.ciphertext])); + form.set("checksum", fileEncryptedHash); await requestFileUpload(status, form); return true; diff --git a/src/lib/server/db/file.ts b/src/lib/server/db/file.ts index a42235b..6bd0452 100644 --- a/src/lib/server/db/file.ts +++ b/src/lib/server/db/file.ts @@ -26,6 +26,7 @@ export interface NewFileParams { contentHmac: string | null; contentType: string; encContentIv: string; + encContentHash: string; encName: string; encNameIv: string; encCreatedAt: string | null; @@ -198,11 +199,12 @@ export const registerFile = async (params: NewFileParams) => { userId: params.userId, mekVersion: params.mekVersion, hskVersion: params.hskVersion, - contentHmac: params.contentHmac, - contentType: params.contentType, encDek: params.encDek, dekVersion: params.dekVersion, + contentHmac: params.contentHmac, + contentType: params.contentType, encContentIv: params.encContentIv, + encContentHash: params.encContentHash, encName: { ciphertext: params.encName, iv: params.encNameIv }, encCreatedAt: params.encCreatedAt && params.encCreatedAtIv diff --git a/src/lib/server/db/schema/file.ts b/src/lib/server/db/schema/file.ts index 65c5471..ffe303b 100644 --- a/src/lib/server/db/schema/file.ts +++ b/src/lib/server/db/schema/file.ts @@ -60,6 +60,7 @@ export const file = sqliteTable( contentHmac: text("content_hmac"), // Base64 contentType: text("content_type").notNull(), encContentIv: text("encrypted_content_iv").notNull(), // Base64 + encContentHash: text("encrypted_content_hash").notNull(), // Base64 encName: ciphertext("encrypted_name").notNull(), encCreatedAt: ciphertext("encrypted_created_at"), encLastModifiedAt: ciphertext("encrypted_last_modified_at").notNull(), diff --git a/src/lib/server/services/file.ts b/src/lib/server/services/file.ts index 3589bed..cabcc1d 100644 --- a/src/lib/server/services/file.ts +++ b/src/lib/server/services/file.ts @@ -1,4 +1,5 @@ import { error } from "@sveltejs/kit"; +import { createHash } from "crypto"; import { createReadStream, createWriteStream } from "fs"; import { mkdir, stat, unlink } from "fs/promises"; import { dirname } from "path"; @@ -95,8 +96,9 @@ const safeUnlink = async (path: string) => { }; export const uploadFile = async ( - params: Omit, + params: Omit, encContentStream: Readable, + encContentHash: Promise, ) => { const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000); const oneMinuteLater = new Date(Date.now() + 60 * 1000); @@ -108,16 +110,30 @@ export const uploadFile = async ( await mkdir(dirname(path), { recursive: true }); try { - await pipeline(encContentStream, createWriteStream(path, { flags: "wx", mode: 0o600 })); + const hashStream = createHash("sha256"); + const [_, hash] = await Promise.all([ + pipeline(encContentStream, hashStream, createWriteStream(path, { flags: "wx", mode: 0o600 })), + encContentHash, + ]); + if (hashStream.digest("base64") != hash) { + throw new Error("Invalid checksum"); + } + await registerFile({ ...params, path, + encContentHash: hash, }); } catch (e) { await safeUnlink(path); if (e instanceof IntegrityError && e.message === "Inactive MEK version") { error(400, "Invalid MEK version"); + } else if ( + e instanceof Error && + (e.message === "Invalid request body" || e.message === "Invalid checksum") + ) { + error(400, "Invalid request body"); } throw e; } diff --git a/src/routes/api/file/upload/+server.ts b/src/routes/api/file/upload/+server.ts index 0e8c082..a69df0c 100644 --- a/src/routes/api/file/upload/+server.ts +++ b/src/routes/api/file/upload/+server.ts @@ -67,27 +67,39 @@ export const POST: RequestHandler = async ({ locals, request }) => { let metadata: FileMetadata | null = null; let content: Readable | null = null; + const checksum = new Promise((resolveChecksum, rejectChecksum) => { + bb.on( + "field", + handler(async (fieldname, val) => { + if (fieldname === "metadata") { + if (!metadata) { + // Ignore subsequent metadata fields + metadata = parseFileMetadata(userId, val); + } + } else if (fieldname === "checksum") { + resolveChecksum(val); // Ignore subsequent checksum fields + } else { + error(400, "Invalid request body"); + } + }), + ); + bb.on( + "file", + handler(async (fieldname, file) => { + if (fieldname !== "content") error(400, "Invalid request body"); + if (!metadata || content) error(400, "Invalid request body"); + content = file; - bb.on( - "field", - handler(async (fieldname, val) => { - if (fieldname !== "metadata") error(400, "Invalid request body"); - if (metadata || content) error(400, "Invalid request body"); - metadata = parseFileMetadata(userId, val); - }), - ); - bb.on( - "file", - handler(async (fieldname, file) => { - if (fieldname !== "content") error(400, "Invalid request body"); - if (!metadata || content) error(400, "Invalid request body"); - content = file; - - await uploadFile(metadata, content); - resolve(text("File uploaded", { headers: { "Content-Type": "text/plain" } })); - }), - ); - bb.on("error", (e) => content?.emit("error", e) ?? reject(e)); + await uploadFile(metadata, content, checksum); + resolve(text("File uploaded", { headers: { "Content-Type": "text/plain" } })); + }), + ); + bb.on("finish", () => rejectChecksum(new Error("Invalid request body"))); + bb.on("error", (e) => { + content?.emit("error", e) ?? reject(e); + rejectChecksum(e); + }); + }); request.body!.pipeTo(Writable.toWeb(bb)).catch(() => {}); // busboy will handle the error }); From 53bc4264876227bc49945bdb17c72c3c50e5b29e Mon Sep 17 00:00:00 2001 From: static Date: Sat, 18 Jan 2025 18:54:49 +0900 Subject: [PATCH 05/53] =?UTF-8?q?=ED=8C=8C=EC=9D=BC=EC=9D=84=20=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=ED=95=A0=20=EB=95=8C,=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=EC=9D=98=20=EB=82=B4=EC=9A=A9=EC=9D=B4=20=EC=95=84=EB=8B=8C=20?= =?UTF-8?q?=ED=95=B4=EC=8B=9C=EA=B0=80=20=EC=84=9C=EB=B2=84=EC=9D=98=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=8B=9C=EC=8A=A4=ED=85=9C=EC=97=90=20?= =?UTF-8?q?=EA=B8=B0=EB=A1=9D=EB=90=98=EB=8D=98=20=EB=B2=84=EA=B7=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/server/services/file.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/lib/server/services/file.ts b/src/lib/server/services/file.ts index cabcc1d..ea01f16 100644 --- a/src/lib/server/services/file.ts +++ b/src/lib/server/services/file.ts @@ -112,7 +112,16 @@ export const uploadFile = async ( try { const hashStream = createHash("sha256"); const [_, hash] = await Promise.all([ - pipeline(encContentStream, hashStream, createWriteStream(path, { flags: "wx", mode: 0o600 })), + pipeline( + encContentStream, + async function* (source) { + for await (const chunk of source) { + hashStream.update(chunk); + yield chunk; + } + }, + createWriteStream(path, { flags: "wx", mode: 0o600 }), + ), encContentHash, ]); if (hashStream.digest("base64") != hash) { From 5517d9f81117d8b11d4da82a3061e49c39fbd0d7 Mon Sep 17 00:00:00 2001 From: static Date: Sun, 19 Jan 2025 00:19:10 +0900 Subject: [PATCH 06/53] =?UTF-8?q?=EC=82=AD=EC=A0=9C=EB=90=9C=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=EC=9D=B4=EB=82=98=20=EB=94=94=EB=A0=89=ED=84=B0?= =?UTF-8?q?=EB=A6=AC=EC=9D=98=20=EA=B2=BD=EC=9A=B0=EC=97=90=EB=8F=84=20?= =?UTF-8?q?=EB=A9=94=ED=83=80=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=A5=BC=20?= =?UTF-8?q?=EB=B3=B5=ED=98=B8=ED=99=94=ED=95=98=EB=A0=A4=EA=B3=A0=20?= =?UTF-8?q?=EC=8B=9C=EB=8F=84=ED=95=98=EB=8D=98=20=EB=B2=84=EA=B7=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/modules/filesystem.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/modules/filesystem.ts b/src/lib/modules/filesystem.ts index 7313cb5..1a1ff0f 100644 --- a/src/lib/modules/filesystem.ts +++ b/src/lib/modules/filesystem.ts @@ -77,6 +77,7 @@ const fetchDirectoryInfoFromServer = async ( if (res.status === 404) { info.set(null); await deleteDirectoryInfo(id as number); + return; } else if (!res.ok) { throw new Error("Failed to fetch directory information"); } @@ -149,6 +150,7 @@ const fetchFileInfoFromServer = async ( if (res.status === 404) { info.set(null); await deleteFileInfo(id); + return; } else if (!res.ok) { throw new Error("Failed to fetch file information"); } From 6018b03523ddd9276b4fe1deac7215f3547ee59c Mon Sep 17 00:00:00 2001 From: static Date: Sun, 19 Jan 2025 01:21:52 +0900 Subject: [PATCH 07/53] =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=97=85=EB=A1=9C?= =?UTF-8?q?=EB=93=9C/=EB=8B=A4=EC=9A=B4=EB=A1=9C=EB=93=9C=20=ED=98=84?= =?UTF-8?q?=ED=99=A9=20=ED=8E=98=EC=9D=B4=EC=A7=80=EC=97=90=EC=84=9C=20Top?= =?UTF-8?q?Bar=EA=B0=80=20=EA=B3=A0=EC=A0=95=EB=90=98=EC=96=B4=20=EC=9E=88?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8D=98=20=EB=B2=84=EA=B7=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes/(fullscreen)/file/downloads/+page.svelte | 4 ++-- src/routes/(fullscreen)/file/uploads/+page.svelte | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/routes/(fullscreen)/file/downloads/+page.svelte b/src/routes/(fullscreen)/file/downloads/+page.svelte index a29b147..a3ce387 100644 --- a/src/routes/(fullscreen)/file/downloads/+page.svelte +++ b/src/routes/(fullscreen)/file/downloads/+page.svelte @@ -19,9 +19,9 @@ 진행 중인 다운로드 -
+
-
+
{#each downloadingFiles as status} {/each} diff --git a/src/routes/(fullscreen)/file/uploads/+page.svelte b/src/routes/(fullscreen)/file/uploads/+page.svelte index 9f59e3f..4e61868 100644 --- a/src/routes/(fullscreen)/file/uploads/+page.svelte +++ b/src/routes/(fullscreen)/file/uploads/+page.svelte @@ -19,9 +19,9 @@ 진행 중인 업로드 -
+
-
+
{#each uploadingFiles as status} {/each} From 0002b4e5f2979caf3eee314aaf4033898be1037a Mon Sep 17 00:00:00 2001 From: static Date: Sun, 19 Jan 2025 02:03:44 +0900 Subject: [PATCH 08/53] =?UTF-8?q?hskLog=20=ED=85=8C=EC=9D=B4=EB=B8=94?= =?UTF-8?q?=EC=9D=98=20actionBy=20=ED=95=84=EB=93=9C=EC=9D=98=20Foreign=20?= =?UTF-8?q?key=20constraint=EC=9D=B4=20=EC=9E=98=EB=AA=BB=20=EC=A7=80?= =?UTF-8?q?=EC=A0=95=EB=90=98=EC=96=B4=20=EC=9E=88=EB=8D=98=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ries.sql => 0000_regular_the_watchers.sql} | 5 +- drizzle/0001_blushing_alice.sql | 2 - drizzle/0002_good_talisman.sql | 1 - drizzle/meta/0000_snapshot.json | 29 +- drizzle/meta/0001_snapshot.json | 1301 ---------------- drizzle/meta/0002_snapshot.json | 1308 ----------------- drizzle/meta/_journal.json | 18 +- src/lib/server/db/schema/hsk.ts | 3 +- 8 files changed, 33 insertions(+), 2634 deletions(-) rename drizzle/{0000_unknown_stark_industries.sql => 0000_regular_the_watchers.sql} (97%) delete mode 100644 drizzle/0001_blushing_alice.sql delete mode 100644 drizzle/0002_good_talisman.sql delete mode 100644 drizzle/meta/0001_snapshot.json delete mode 100644 drizzle/meta/0002_snapshot.json diff --git a/drizzle/0000_unknown_stark_industries.sql b/drizzle/0000_regular_the_watchers.sql similarity index 97% rename from drizzle/0000_unknown_stark_industries.sql rename to drizzle/0000_regular_the_watchers.sql index 28a9787..6cbdec8 100644 --- a/drizzle/0000_unknown_stark_industries.sql +++ b/drizzle/0000_regular_the_watchers.sql @@ -59,7 +59,10 @@ CREATE TABLE `file` ( `content_hmac` text, `content_type` text NOT NULL, `encrypted_content_iv` text NOT NULL, + `encrypted_content_hash` text NOT NULL, `encrypted_name` text NOT NULL, + `encrypted_created_at` text, + `encrypted_last_modified_at` text NOT NULL, FOREIGN KEY (`parent_id`) REFERENCES `directory`(`id`) ON UPDATE no action ON DELETE no action, FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action, FOREIGN KEY (`user_id`,`master_encryption_key_version`) REFERENCES `master_encryption_key`(`user_id`,`version`) ON UPDATE no action ON DELETE no action, @@ -94,7 +97,7 @@ CREATE TABLE `hmac_secret_key_log` ( `action` text NOT NULL, `action_by` integer, FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action, - FOREIGN KEY (`action_by`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action, + FOREIGN KEY (`action_by`) REFERENCES `client`(`id`) ON UPDATE no action ON DELETE no action, FOREIGN KEY (`user_id`,`hmac_secret_key_version`) REFERENCES `hmac_secret_key`(`user_id`,`version`) ON UPDATE no action ON DELETE no action ); --> statement-breakpoint diff --git a/drizzle/0001_blushing_alice.sql b/drizzle/0001_blushing_alice.sql deleted file mode 100644 index f68ba02..0000000 --- a/drizzle/0001_blushing_alice.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE `file` ADD `encrypted_created_at` text;--> statement-breakpoint -ALTER TABLE `file` ADD `encrypted_last_modified_at` text NOT NULL; \ No newline at end of file diff --git a/drizzle/0002_good_talisman.sql b/drizzle/0002_good_talisman.sql deleted file mode 100644 index 55387df..0000000 --- a/drizzle/0002_good_talisman.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE `file` ADD `encrypted_content_hash` text NOT NULL; \ No newline at end of file diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json index 42641f1..27a42bc 100644 --- a/drizzle/meta/0000_snapshot.json +++ b/drizzle/meta/0000_snapshot.json @@ -1,7 +1,7 @@ { "version": "6", "dialect": "sqlite", - "id": "928e5669-81cf-486c-9122-8ee64fc9f457", + "id": "396a26d6-6f55-4162-a23e-c1117f3a3757", "prevId": "00000000-0000-0000-0000-000000000000", "tables": { "client": { @@ -470,12 +470,33 @@ "notNull": true, "autoincrement": false }, + "encrypted_content_hash": { + "name": "encrypted_content_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, "encrypted_name": { "name": "encrypted_name", "type": "text", "primaryKey": false, "notNull": true, "autoincrement": false + }, + "encrypted_created_at": { + "name": "encrypted_created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_last_modified_at": { + "name": "encrypted_last_modified_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false } }, "indexes": { @@ -763,10 +784,10 @@ "onDelete": "no action", "onUpdate": "no action" }, - "hmac_secret_key_log_action_by_user_id_fk": { - "name": "hmac_secret_key_log_action_by_user_id_fk", + "hmac_secret_key_log_action_by_client_id_fk": { + "name": "hmac_secret_key_log_action_by_client_id_fk", "tableFrom": "hmac_secret_key_log", - "tableTo": "user", + "tableTo": "client", "columnsFrom": [ "action_by" ], diff --git a/drizzle/meta/0001_snapshot.json b/drizzle/meta/0001_snapshot.json deleted file mode 100644 index 3425d7b..0000000 --- a/drizzle/meta/0001_snapshot.json +++ /dev/null @@ -1,1301 +0,0 @@ -{ - "version": "6", - "dialect": "sqlite", - "id": "5e999e6f-1ec4-40b0-bb10-741ffc6da4af", - "prevId": "928e5669-81cf-486c-9122-8ee64fc9f457", - "tables": { - "client": { - "name": "client", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "encryption_public_key": { - "name": "encryption_public_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "signature_public_key": { - "name": "signature_public_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "client_encryption_public_key_unique": { - "name": "client_encryption_public_key_unique", - "columns": [ - "encryption_public_key" - ], - "isUnique": true - }, - "client_signature_public_key_unique": { - "name": "client_signature_public_key_unique", - "columns": [ - "signature_public_key" - ], - "isUnique": true - }, - "client_encryption_public_key_signature_public_key_unique": { - "name": "client_encryption_public_key_signature_public_key_unique", - "columns": [ - "encryption_public_key", - "signature_public_key" - ], - "isUnique": true - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "user_client": { - "name": "user_client", - "columns": { - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "client_id": { - "name": "client_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "state": { - "name": "state", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'challenging'" - } - }, - "indexes": {}, - "foreignKeys": { - "user_client_user_id_user_id_fk": { - "name": "user_client_user_id_user_id_fk", - "tableFrom": "user_client", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "user_client_client_id_client_id_fk": { - "name": "user_client_client_id_client_id_fk", - "tableFrom": "user_client", - "tableTo": "client", - "columnsFrom": [ - "client_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "user_client_user_id_client_id_pk": { - "columns": [ - "client_id", - "user_id" - ], - "name": "user_client_user_id_client_id_pk" - } - }, - "uniqueConstraints": {} - }, - "user_client_challenge": { - "name": "user_client_challenge", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "client_id": { - "name": "client_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "answer": { - "name": "answer", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "allowed_ip": { - "name": "allowed_ip", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires_at": { - "name": "expires_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "user_client_challenge_answer_unique": { - "name": "user_client_challenge_answer_unique", - "columns": [ - "answer" - ], - "isUnique": true - } - }, - "foreignKeys": { - "user_client_challenge_user_id_user_id_fk": { - "name": "user_client_challenge_user_id_user_id_fk", - "tableFrom": "user_client_challenge", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "user_client_challenge_client_id_client_id_fk": { - "name": "user_client_challenge_client_id_client_id_fk", - "tableFrom": "user_client_challenge", - "tableTo": "client", - "columnsFrom": [ - "client_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "user_client_challenge_user_id_client_id_user_client_user_id_client_id_fk": { - "name": "user_client_challenge_user_id_client_id_user_client_user_id_client_id_fk", - "tableFrom": "user_client_challenge", - "tableTo": "user_client", - "columnsFrom": [ - "user_id", - "client_id" - ], - "columnsTo": [ - "user_id", - "client_id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "directory": { - "name": "directory", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "parent_id": { - "name": "parent_id", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "master_encryption_key_version": { - "name": "master_encryption_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_data_encryption_key": { - "name": "encrypted_data_encryption_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "data_encryption_key_version": { - "name": "data_encryption_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_name": { - "name": "encrypted_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "directory_encrypted_data_encryption_key_unique": { - "name": "directory_encrypted_data_encryption_key_unique", - "columns": [ - "encrypted_data_encryption_key" - ], - "isUnique": true - } - }, - "foreignKeys": { - "directory_user_id_user_id_fk": { - "name": "directory_user_id_user_id_fk", - "tableFrom": "directory", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "directory_parent_id_directory_id_fk": { - "name": "directory_parent_id_directory_id_fk", - "tableFrom": "directory", - "tableTo": "directory", - "columnsFrom": [ - "parent_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "directory_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { - "name": "directory_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", - "tableFrom": "directory", - "tableTo": "master_encryption_key", - "columnsFrom": [ - "user_id", - "master_encryption_key_version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "directory_log": { - "name": "directory_log", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "directory_id": { - "name": "directory_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "timestamp": { - "name": "timestamp", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "action": { - "name": "action", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "new_name": { - "name": "new_name", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "directory_log_directory_id_directory_id_fk": { - "name": "directory_log_directory_id_directory_id_fk", - "tableFrom": "directory_log", - "tableTo": "directory", - "columnsFrom": [ - "directory_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "file": { - "name": "file", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "parent_id": { - "name": "parent_id", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "path": { - "name": "path", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "master_encryption_key_version": { - "name": "master_encryption_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_data_encryption_key": { - "name": "encrypted_data_encryption_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "data_encryption_key_version": { - "name": "data_encryption_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "hmac_secret_key_version": { - "name": "hmac_secret_key_version", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "content_hmac": { - "name": "content_hmac", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "content_type": { - "name": "content_type", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_content_iv": { - "name": "encrypted_content_iv", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_name": { - "name": "encrypted_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_created_at": { - "name": "encrypted_created_at", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "encrypted_last_modified_at": { - "name": "encrypted_last_modified_at", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "file_path_unique": { - "name": "file_path_unique", - "columns": [ - "path" - ], - "isUnique": true - }, - "file_encrypted_data_encryption_key_unique": { - "name": "file_encrypted_data_encryption_key_unique", - "columns": [ - "encrypted_data_encryption_key" - ], - "isUnique": true - } - }, - "foreignKeys": { - "file_parent_id_directory_id_fk": { - "name": "file_parent_id_directory_id_fk", - "tableFrom": "file", - "tableTo": "directory", - "columnsFrom": [ - "parent_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "file_user_id_user_id_fk": { - "name": "file_user_id_user_id_fk", - "tableFrom": "file", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "file_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { - "name": "file_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", - "tableFrom": "file", - "tableTo": "master_encryption_key", - "columnsFrom": [ - "user_id", - "master_encryption_key_version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "file_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk": { - "name": "file_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk", - "tableFrom": "file", - "tableTo": "hmac_secret_key", - "columnsFrom": [ - "user_id", - "hmac_secret_key_version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "file_log": { - "name": "file_log", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "file_id": { - "name": "file_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "timestamp": { - "name": "timestamp", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "action": { - "name": "action", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "new_name": { - "name": "new_name", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "file_log_file_id_file_id_fk": { - "name": "file_log_file_id_file_id_fk", - "tableFrom": "file_log", - "tableTo": "file", - "columnsFrom": [ - "file_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "hmac_secret_key": { - "name": "hmac_secret_key", - "columns": { - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "version": { - "name": "version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "state": { - "name": "state", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "master_encryption_key_version": { - "name": "master_encryption_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_key": { - "name": "encrypted_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "hmac_secret_key_encrypted_key_unique": { - "name": "hmac_secret_key_encrypted_key_unique", - "columns": [ - "encrypted_key" - ], - "isUnique": true - } - }, - "foreignKeys": { - "hmac_secret_key_user_id_user_id_fk": { - "name": "hmac_secret_key_user_id_user_id_fk", - "tableFrom": "hmac_secret_key", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "hmac_secret_key_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { - "name": "hmac_secret_key_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", - "tableFrom": "hmac_secret_key", - "tableTo": "master_encryption_key", - "columnsFrom": [ - "user_id", - "master_encryption_key_version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "hmac_secret_key_user_id_version_pk": { - "columns": [ - "user_id", - "version" - ], - "name": "hmac_secret_key_user_id_version_pk" - } - }, - "uniqueConstraints": {} - }, - "hmac_secret_key_log": { - "name": "hmac_secret_key_log", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "hmac_secret_key_version": { - "name": "hmac_secret_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "timestamp": { - "name": "timestamp", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "action": { - "name": "action", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "action_by": { - "name": "action_by", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "hmac_secret_key_log_user_id_user_id_fk": { - "name": "hmac_secret_key_log_user_id_user_id_fk", - "tableFrom": "hmac_secret_key_log", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "hmac_secret_key_log_action_by_user_id_fk": { - "name": "hmac_secret_key_log_action_by_user_id_fk", - "tableFrom": "hmac_secret_key_log", - "tableTo": "user", - "columnsFrom": [ - "action_by" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "hmac_secret_key_log_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk": { - "name": "hmac_secret_key_log_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk", - "tableFrom": "hmac_secret_key_log", - "tableTo": "hmac_secret_key", - "columnsFrom": [ - "user_id", - "hmac_secret_key_version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "client_master_encryption_key": { - "name": "client_master_encryption_key", - "columns": { - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "client_id": { - "name": "client_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "version": { - "name": "version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_key": { - "name": "encrypted_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_key_signature": { - "name": "encrypted_key_signature", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "client_master_encryption_key_user_id_user_id_fk": { - "name": "client_master_encryption_key_user_id_user_id_fk", - "tableFrom": "client_master_encryption_key", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "client_master_encryption_key_client_id_client_id_fk": { - "name": "client_master_encryption_key_client_id_client_id_fk", - "tableFrom": "client_master_encryption_key", - "tableTo": "client", - "columnsFrom": [ - "client_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "client_master_encryption_key_user_id_version_master_encryption_key_user_id_version_fk": { - "name": "client_master_encryption_key_user_id_version_master_encryption_key_user_id_version_fk", - "tableFrom": "client_master_encryption_key", - "tableTo": "master_encryption_key", - "columnsFrom": [ - "user_id", - "version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "client_master_encryption_key_user_id_client_id_version_pk": { - "columns": [ - "client_id", - "user_id", - "version" - ], - "name": "client_master_encryption_key_user_id_client_id_version_pk" - } - }, - "uniqueConstraints": {} - }, - "master_encryption_key": { - "name": "master_encryption_key", - "columns": { - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "version": { - "name": "version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "state": { - "name": "state", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "retired_at": { - "name": "retired_at", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "master_encryption_key_user_id_user_id_fk": { - "name": "master_encryption_key_user_id_user_id_fk", - "tableFrom": "master_encryption_key", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "master_encryption_key_user_id_version_pk": { - "columns": [ - "user_id", - "version" - ], - "name": "master_encryption_key_user_id_version_pk" - } - }, - "uniqueConstraints": {} - }, - "master_encryption_key_log": { - "name": "master_encryption_key_log", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "master_encryption_key_version": { - "name": "master_encryption_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "timestamp": { - "name": "timestamp", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "action": { - "name": "action", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "action_by": { - "name": "action_by", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "master_encryption_key_log_user_id_user_id_fk": { - "name": "master_encryption_key_log_user_id_user_id_fk", - "tableFrom": "master_encryption_key_log", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "master_encryption_key_log_action_by_client_id_fk": { - "name": "master_encryption_key_log_action_by_client_id_fk", - "tableFrom": "master_encryption_key_log", - "tableTo": "client", - "columnsFrom": [ - "action_by" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "master_encryption_key_log_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { - "name": "master_encryption_key_log_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", - "tableFrom": "master_encryption_key_log", - "tableTo": "master_encryption_key", - "columnsFrom": [ - "user_id", - "master_encryption_key_version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "session": { - "name": "session", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "client_id": { - "name": "client_id", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "last_used_at": { - "name": "last_used_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "last_used_by_ip": { - "name": "last_used_by_ip", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "last_used_by_user_agent": { - "name": "last_used_by_user_agent", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": { - "session_user_id_client_id_unique": { - "name": "session_user_id_client_id_unique", - "columns": [ - "user_id", - "client_id" - ], - "isUnique": true - } - }, - "foreignKeys": { - "session_user_id_user_id_fk": { - "name": "session_user_id_user_id_fk", - "tableFrom": "session", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "session_client_id_client_id_fk": { - "name": "session_client_id_client_id_fk", - "tableFrom": "session", - "tableTo": "client", - "columnsFrom": [ - "client_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "session_upgrade_challenge": { - "name": "session_upgrade_challenge", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "session_id": { - "name": "session_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "client_id": { - "name": "client_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "answer": { - "name": "answer", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "allowed_ip": { - "name": "allowed_ip", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires_at": { - "name": "expires_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "session_upgrade_challenge_session_id_unique": { - "name": "session_upgrade_challenge_session_id_unique", - "columns": [ - "session_id" - ], - "isUnique": true - }, - "session_upgrade_challenge_answer_unique": { - "name": "session_upgrade_challenge_answer_unique", - "columns": [ - "answer" - ], - "isUnique": true - } - }, - "foreignKeys": { - "session_upgrade_challenge_session_id_session_id_fk": { - "name": "session_upgrade_challenge_session_id_session_id_fk", - "tableFrom": "session_upgrade_challenge", - "tableTo": "session", - "columnsFrom": [ - "session_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "session_upgrade_challenge_client_id_client_id_fk": { - "name": "session_upgrade_challenge_client_id_client_id_fk", - "tableFrom": "session_upgrade_challenge", - "tableTo": "client", - "columnsFrom": [ - "client_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "user": { - "name": "user", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "password": { - "name": "password", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "nickname": { - "name": "nickname", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "user_email_unique": { - "name": "user_email_unique", - "columns": [ - "email" - ], - "isUnique": true - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - } - }, - "enums": {}, - "_meta": { - "schemas": {}, - "tables": {}, - "columns": {} - }, - "internal": { - "indexes": {} - } -} \ No newline at end of file diff --git a/drizzle/meta/0002_snapshot.json b/drizzle/meta/0002_snapshot.json deleted file mode 100644 index d9da594..0000000 --- a/drizzle/meta/0002_snapshot.json +++ /dev/null @@ -1,1308 +0,0 @@ -{ - "version": "6", - "dialect": "sqlite", - "id": "93a0c9c7-dde3-4025-bd59-0fe3a6b70fa0", - "prevId": "5e999e6f-1ec4-40b0-bb10-741ffc6da4af", - "tables": { - "client": { - "name": "client", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "encryption_public_key": { - "name": "encryption_public_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "signature_public_key": { - "name": "signature_public_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "client_encryption_public_key_unique": { - "name": "client_encryption_public_key_unique", - "columns": [ - "encryption_public_key" - ], - "isUnique": true - }, - "client_signature_public_key_unique": { - "name": "client_signature_public_key_unique", - "columns": [ - "signature_public_key" - ], - "isUnique": true - }, - "client_encryption_public_key_signature_public_key_unique": { - "name": "client_encryption_public_key_signature_public_key_unique", - "columns": [ - "encryption_public_key", - "signature_public_key" - ], - "isUnique": true - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "user_client": { - "name": "user_client", - "columns": { - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "client_id": { - "name": "client_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "state": { - "name": "state", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'challenging'" - } - }, - "indexes": {}, - "foreignKeys": { - "user_client_user_id_user_id_fk": { - "name": "user_client_user_id_user_id_fk", - "tableFrom": "user_client", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "user_client_client_id_client_id_fk": { - "name": "user_client_client_id_client_id_fk", - "tableFrom": "user_client", - "tableTo": "client", - "columnsFrom": [ - "client_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "user_client_user_id_client_id_pk": { - "columns": [ - "client_id", - "user_id" - ], - "name": "user_client_user_id_client_id_pk" - } - }, - "uniqueConstraints": {} - }, - "user_client_challenge": { - "name": "user_client_challenge", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "client_id": { - "name": "client_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "answer": { - "name": "answer", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "allowed_ip": { - "name": "allowed_ip", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires_at": { - "name": "expires_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "user_client_challenge_answer_unique": { - "name": "user_client_challenge_answer_unique", - "columns": [ - "answer" - ], - "isUnique": true - } - }, - "foreignKeys": { - "user_client_challenge_user_id_user_id_fk": { - "name": "user_client_challenge_user_id_user_id_fk", - "tableFrom": "user_client_challenge", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "user_client_challenge_client_id_client_id_fk": { - "name": "user_client_challenge_client_id_client_id_fk", - "tableFrom": "user_client_challenge", - "tableTo": "client", - "columnsFrom": [ - "client_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "user_client_challenge_user_id_client_id_user_client_user_id_client_id_fk": { - "name": "user_client_challenge_user_id_client_id_user_client_user_id_client_id_fk", - "tableFrom": "user_client_challenge", - "tableTo": "user_client", - "columnsFrom": [ - "user_id", - "client_id" - ], - "columnsTo": [ - "user_id", - "client_id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "directory": { - "name": "directory", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "parent_id": { - "name": "parent_id", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "master_encryption_key_version": { - "name": "master_encryption_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_data_encryption_key": { - "name": "encrypted_data_encryption_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "data_encryption_key_version": { - "name": "data_encryption_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_name": { - "name": "encrypted_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "directory_encrypted_data_encryption_key_unique": { - "name": "directory_encrypted_data_encryption_key_unique", - "columns": [ - "encrypted_data_encryption_key" - ], - "isUnique": true - } - }, - "foreignKeys": { - "directory_user_id_user_id_fk": { - "name": "directory_user_id_user_id_fk", - "tableFrom": "directory", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "directory_parent_id_directory_id_fk": { - "name": "directory_parent_id_directory_id_fk", - "tableFrom": "directory", - "tableTo": "directory", - "columnsFrom": [ - "parent_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "directory_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { - "name": "directory_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", - "tableFrom": "directory", - "tableTo": "master_encryption_key", - "columnsFrom": [ - "user_id", - "master_encryption_key_version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "directory_log": { - "name": "directory_log", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "directory_id": { - "name": "directory_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "timestamp": { - "name": "timestamp", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "action": { - "name": "action", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "new_name": { - "name": "new_name", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "directory_log_directory_id_directory_id_fk": { - "name": "directory_log_directory_id_directory_id_fk", - "tableFrom": "directory_log", - "tableTo": "directory", - "columnsFrom": [ - "directory_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "file": { - "name": "file", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "parent_id": { - "name": "parent_id", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "path": { - "name": "path", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "master_encryption_key_version": { - "name": "master_encryption_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_data_encryption_key": { - "name": "encrypted_data_encryption_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "data_encryption_key_version": { - "name": "data_encryption_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "hmac_secret_key_version": { - "name": "hmac_secret_key_version", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "content_hmac": { - "name": "content_hmac", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "content_type": { - "name": "content_type", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_content_iv": { - "name": "encrypted_content_iv", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_content_hash": { - "name": "encrypted_content_hash", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_name": { - "name": "encrypted_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_created_at": { - "name": "encrypted_created_at", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "encrypted_last_modified_at": { - "name": "encrypted_last_modified_at", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "file_path_unique": { - "name": "file_path_unique", - "columns": [ - "path" - ], - "isUnique": true - }, - "file_encrypted_data_encryption_key_unique": { - "name": "file_encrypted_data_encryption_key_unique", - "columns": [ - "encrypted_data_encryption_key" - ], - "isUnique": true - } - }, - "foreignKeys": { - "file_parent_id_directory_id_fk": { - "name": "file_parent_id_directory_id_fk", - "tableFrom": "file", - "tableTo": "directory", - "columnsFrom": [ - "parent_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "file_user_id_user_id_fk": { - "name": "file_user_id_user_id_fk", - "tableFrom": "file", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "file_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { - "name": "file_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", - "tableFrom": "file", - "tableTo": "master_encryption_key", - "columnsFrom": [ - "user_id", - "master_encryption_key_version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "file_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk": { - "name": "file_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk", - "tableFrom": "file", - "tableTo": "hmac_secret_key", - "columnsFrom": [ - "user_id", - "hmac_secret_key_version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "file_log": { - "name": "file_log", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "file_id": { - "name": "file_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "timestamp": { - "name": "timestamp", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "action": { - "name": "action", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "new_name": { - "name": "new_name", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "file_log_file_id_file_id_fk": { - "name": "file_log_file_id_file_id_fk", - "tableFrom": "file_log", - "tableTo": "file", - "columnsFrom": [ - "file_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "hmac_secret_key": { - "name": "hmac_secret_key", - "columns": { - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "version": { - "name": "version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "state": { - "name": "state", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "master_encryption_key_version": { - "name": "master_encryption_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_key": { - "name": "encrypted_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "hmac_secret_key_encrypted_key_unique": { - "name": "hmac_secret_key_encrypted_key_unique", - "columns": [ - "encrypted_key" - ], - "isUnique": true - } - }, - "foreignKeys": { - "hmac_secret_key_user_id_user_id_fk": { - "name": "hmac_secret_key_user_id_user_id_fk", - "tableFrom": "hmac_secret_key", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "hmac_secret_key_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { - "name": "hmac_secret_key_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", - "tableFrom": "hmac_secret_key", - "tableTo": "master_encryption_key", - "columnsFrom": [ - "user_id", - "master_encryption_key_version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "hmac_secret_key_user_id_version_pk": { - "columns": [ - "user_id", - "version" - ], - "name": "hmac_secret_key_user_id_version_pk" - } - }, - "uniqueConstraints": {} - }, - "hmac_secret_key_log": { - "name": "hmac_secret_key_log", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "hmac_secret_key_version": { - "name": "hmac_secret_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "timestamp": { - "name": "timestamp", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "action": { - "name": "action", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "action_by": { - "name": "action_by", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "hmac_secret_key_log_user_id_user_id_fk": { - "name": "hmac_secret_key_log_user_id_user_id_fk", - "tableFrom": "hmac_secret_key_log", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "hmac_secret_key_log_action_by_user_id_fk": { - "name": "hmac_secret_key_log_action_by_user_id_fk", - "tableFrom": "hmac_secret_key_log", - "tableTo": "user", - "columnsFrom": [ - "action_by" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "hmac_secret_key_log_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk": { - "name": "hmac_secret_key_log_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk", - "tableFrom": "hmac_secret_key_log", - "tableTo": "hmac_secret_key", - "columnsFrom": [ - "user_id", - "hmac_secret_key_version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "client_master_encryption_key": { - "name": "client_master_encryption_key", - "columns": { - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "client_id": { - "name": "client_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "version": { - "name": "version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_key": { - "name": "encrypted_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_key_signature": { - "name": "encrypted_key_signature", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "client_master_encryption_key_user_id_user_id_fk": { - "name": "client_master_encryption_key_user_id_user_id_fk", - "tableFrom": "client_master_encryption_key", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "client_master_encryption_key_client_id_client_id_fk": { - "name": "client_master_encryption_key_client_id_client_id_fk", - "tableFrom": "client_master_encryption_key", - "tableTo": "client", - "columnsFrom": [ - "client_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "client_master_encryption_key_user_id_version_master_encryption_key_user_id_version_fk": { - "name": "client_master_encryption_key_user_id_version_master_encryption_key_user_id_version_fk", - "tableFrom": "client_master_encryption_key", - "tableTo": "master_encryption_key", - "columnsFrom": [ - "user_id", - "version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "client_master_encryption_key_user_id_client_id_version_pk": { - "columns": [ - "client_id", - "user_id", - "version" - ], - "name": "client_master_encryption_key_user_id_client_id_version_pk" - } - }, - "uniqueConstraints": {} - }, - "master_encryption_key": { - "name": "master_encryption_key", - "columns": { - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "version": { - "name": "version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "state": { - "name": "state", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "retired_at": { - "name": "retired_at", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "master_encryption_key_user_id_user_id_fk": { - "name": "master_encryption_key_user_id_user_id_fk", - "tableFrom": "master_encryption_key", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "master_encryption_key_user_id_version_pk": { - "columns": [ - "user_id", - "version" - ], - "name": "master_encryption_key_user_id_version_pk" - } - }, - "uniqueConstraints": {} - }, - "master_encryption_key_log": { - "name": "master_encryption_key_log", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "master_encryption_key_version": { - "name": "master_encryption_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "timestamp": { - "name": "timestamp", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "action": { - "name": "action", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "action_by": { - "name": "action_by", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "master_encryption_key_log_user_id_user_id_fk": { - "name": "master_encryption_key_log_user_id_user_id_fk", - "tableFrom": "master_encryption_key_log", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "master_encryption_key_log_action_by_client_id_fk": { - "name": "master_encryption_key_log_action_by_client_id_fk", - "tableFrom": "master_encryption_key_log", - "tableTo": "client", - "columnsFrom": [ - "action_by" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "master_encryption_key_log_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { - "name": "master_encryption_key_log_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", - "tableFrom": "master_encryption_key_log", - "tableTo": "master_encryption_key", - "columnsFrom": [ - "user_id", - "master_encryption_key_version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "session": { - "name": "session", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "client_id": { - "name": "client_id", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "last_used_at": { - "name": "last_used_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "last_used_by_ip": { - "name": "last_used_by_ip", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "last_used_by_user_agent": { - "name": "last_used_by_user_agent", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": { - "session_user_id_client_id_unique": { - "name": "session_user_id_client_id_unique", - "columns": [ - "user_id", - "client_id" - ], - "isUnique": true - } - }, - "foreignKeys": { - "session_user_id_user_id_fk": { - "name": "session_user_id_user_id_fk", - "tableFrom": "session", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "session_client_id_client_id_fk": { - "name": "session_client_id_client_id_fk", - "tableFrom": "session", - "tableTo": "client", - "columnsFrom": [ - "client_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "session_upgrade_challenge": { - "name": "session_upgrade_challenge", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "session_id": { - "name": "session_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "client_id": { - "name": "client_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "answer": { - "name": "answer", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "allowed_ip": { - "name": "allowed_ip", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires_at": { - "name": "expires_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "session_upgrade_challenge_session_id_unique": { - "name": "session_upgrade_challenge_session_id_unique", - "columns": [ - "session_id" - ], - "isUnique": true - }, - "session_upgrade_challenge_answer_unique": { - "name": "session_upgrade_challenge_answer_unique", - "columns": [ - "answer" - ], - "isUnique": true - } - }, - "foreignKeys": { - "session_upgrade_challenge_session_id_session_id_fk": { - "name": "session_upgrade_challenge_session_id_session_id_fk", - "tableFrom": "session_upgrade_challenge", - "tableTo": "session", - "columnsFrom": [ - "session_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "session_upgrade_challenge_client_id_client_id_fk": { - "name": "session_upgrade_challenge_client_id_client_id_fk", - "tableFrom": "session_upgrade_challenge", - "tableTo": "client", - "columnsFrom": [ - "client_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "user": { - "name": "user", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "password": { - "name": "password", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "nickname": { - "name": "nickname", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "user_email_unique": { - "name": "user_email_unique", - "columns": [ - "email" - ], - "isUnique": true - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - } - }, - "enums": {}, - "_meta": { - "schemas": {}, - "tables": {}, - "columns": {} - }, - "internal": { - "indexes": {} - } -} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index f5d931a..bdbdf66 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -5,22 +5,8 @@ { "idx": 0, "version": "6", - "when": 1736704436996, - "tag": "0000_unknown_stark_industries", - "breakpoints": true - }, - { - "idx": 1, - "version": "6", - "when": 1736720831242, - "tag": "0001_blushing_alice", - "breakpoints": true - }, - { - "idx": 2, - "version": "6", - "when": 1737191517463, - "tag": "0002_good_talisman", + "when": 1737219722656, + "tag": "0000_regular_the_watchers", "breakpoints": true } ] diff --git a/src/lib/server/db/schema/hsk.ts b/src/lib/server/db/schema/hsk.ts index b78c512..51f25cc 100644 --- a/src/lib/server/db/schema/hsk.ts +++ b/src/lib/server/db/schema/hsk.ts @@ -1,4 +1,5 @@ import { sqliteTable, text, integer, primaryKey, foreignKey } from "drizzle-orm/sqlite-core"; +import { client } from "./client"; import { mek } from "./mek"; import { user } from "./user"; @@ -32,7 +33,7 @@ export const hskLog = sqliteTable( hskVersion: integer("hmac_secret_key_version").notNull(), timestamp: integer("timestamp", { mode: "timestamp_ms" }).notNull(), action: text("action", { enum: ["create"] }).notNull(), - actionBy: integer("action_by").references(() => user.id), + actionBy: integer("action_by").references(() => client.id), }, (t) => ({ ref: foreignKey({ From 63eacbb1b3c31a4de1278bf5251d301013006d72 Mon Sep 17 00:00:00 2001 From: static Date: Mon, 20 Jan 2025 10:56:58 +0900 Subject: [PATCH 09/53] =?UTF-8?q?Kysely=20=EB=B0=8F=20PostgreSQL=20?= =?UTF-8?q?=EB=8F=84=EC=9E=85=20(WiP)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 + pnpm-lock.yaml | 194 +++++++++++++++++++++++++++- src/lib/server/db/kysely.ts | 15 +++ src/lib/server/db/schema/client.ts | 30 +++++ src/lib/server/db/schema/file.ts | 59 +++++++++ src/lib/server/db/schema/hsk.ts | 25 ++++ src/lib/server/db/schema/index.ts | 2 + src/lib/server/db/schema/mek.ts | 32 +++++ src/lib/server/db/schema/session.ts | 27 ++++ src/lib/server/db/schema/user.ts | 14 ++ 10 files changed, 399 insertions(+), 2 deletions(-) create mode 100644 src/lib/server/db/kysely.ts diff --git a/package.json b/package.json index 3a4adf2..02c4be9 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@types/file-saver": "^2.0.7", "@types/ms": "^0.7.34", "@types/node-schedule": "^2.1.7", + "@types/pg": "^8.11.10", "autoprefixer": "^10.4.20", "axios": "^1.7.9", "dexie": "^4.0.10", @@ -56,8 +57,10 @@ "argon2": "^0.41.1", "better-sqlite3": "^11.7.2", "drizzle-orm": "^0.33.0", + "kysely": "^0.27.5", "ms": "^2.1.3", "node-schedule": "^2.1.1", + "pg": "^8.13.1", "uuid": "^11.0.4", "zod": "^3.24.1" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ae948dc..3dd6588 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,13 +19,19 @@ importers: version: 11.7.2 drizzle-orm: specifier: ^0.33.0 - version: 0.33.0(@types/better-sqlite3@7.6.12)(better-sqlite3@11.7.2) + version: 0.33.0(@types/better-sqlite3@7.6.12)(@types/pg@8.11.10)(better-sqlite3@11.7.2)(kysely@0.27.5)(pg@8.13.1) + kysely: + specifier: ^0.27.5 + version: 0.27.5 ms: specifier: ^2.1.3 version: 2.1.3 node-schedule: specifier: ^2.1.1 version: 2.1.1 + pg: + specifier: ^8.13.1 + version: 8.13.1 uuid: specifier: ^11.0.4 version: 11.0.4 @@ -60,6 +66,9 @@ importers: '@types/node-schedule': specifier: ^2.1.7 version: 2.1.7 + '@types/pg': + specifier: ^8.11.10 + version: 8.11.10 autoprefixer: specifier: ^10.4.20 version: 10.4.20(postcss@8.4.49) @@ -872,6 +881,9 @@ packages: '@types/node@22.10.5': resolution: {integrity: sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==} + '@types/pg@8.11.10': + resolution: {integrity: sha512-LczQUW4dbOQzsH2RQ5qoeJ6qJPdrcM/DcMLoqWQkMLMsq83J5lAX3LXjdkWdpscFy67JSOWDnh7Ny/sPFykmkg==} + '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} @@ -1602,6 +1614,10 @@ packages: kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + kysely@0.27.5: + resolution: {integrity: sha512-s7hZHcQeSNKpzCkHRm8yA+0JPLjncSWnjb+2TIElwS2JAqYr+Kv3Ess+9KFfJS0C1xcQ1i9NkNHpWwCYpHMWsA==} + engines: {node: '>=14.0.0'} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -1749,6 +1765,9 @@ packages: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} + obuf@1.1.2: + resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -1796,6 +1815,48 @@ packages: pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + pg-cloudflare@1.1.1: + resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} + + pg-connection-string@2.7.0: + resolution: {integrity: sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==} + + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-numeric@1.0.2: + resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==} + engines: {node: '>=4'} + + pg-pool@3.7.0: + resolution: {integrity: sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==} + peerDependencies: + pg: '>=8.0' + + pg-protocol@1.7.0: + resolution: {integrity: sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + + pg-types@4.0.2: + resolution: {integrity: sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==} + engines: {node: '>=10'} + + pg@8.13.1: + resolution: {integrity: sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==} + engines: {node: '>= 8.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + + pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -1883,6 +1944,41 @@ packages: resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} engines: {node: ^10 || ^12 || >=14} + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + postgres-array@3.0.2: + resolution: {integrity: sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==} + engines: {node: '>=12'} + + postgres-bytea@1.0.0: + resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} + engines: {node: '>=0.10.0'} + + postgres-bytea@3.0.0: + resolution: {integrity: sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==} + engines: {node: '>= 6'} + + postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + postgres-date@2.1.0: + resolution: {integrity: sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==} + engines: {node: '>=12'} + + postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + + postgres-interval@3.0.0: + resolution: {integrity: sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==} + engines: {node: '>=12'} + + postgres-range@1.1.4: + resolution: {integrity: sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==} + prebuild-install@7.1.2: resolution: {integrity: sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==} engines: {node: '>=10'} @@ -2065,6 +2161,10 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -2298,6 +2398,10 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} @@ -2843,6 +2947,12 @@ snapshots: dependencies: undici-types: 6.20.0 + '@types/pg@8.11.10': + dependencies: + '@types/node': 22.10.5 + pg-protocol: 1.7.0 + pg-types: 4.0.2 + '@types/resolve@1.20.2': {} '@typescript-eslint/eslint-plugin@8.19.1(@typescript-eslint/parser@8.19.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3))(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3)': @@ -3138,10 +3248,13 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-orm@0.33.0(@types/better-sqlite3@7.6.12)(better-sqlite3@11.7.2): + drizzle-orm@0.33.0(@types/better-sqlite3@7.6.12)(@types/pg@8.11.10)(better-sqlite3@11.7.2)(kysely@0.27.5)(pg@8.13.1): optionalDependencies: '@types/better-sqlite3': 7.6.12 + '@types/pg': 8.11.10 better-sqlite3: 11.7.2 + kysely: 0.27.5 + pg: 8.13.1 eastasianwidth@0.2.0: {} @@ -3554,6 +3667,8 @@ snapshots: kolorist@1.8.0: {} + kysely@0.27.5: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -3668,6 +3783,8 @@ snapshots: object-hash@3.0.0: {} + obuf@1.1.2: {} + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -3714,6 +3831,53 @@ snapshots: pathe@1.1.2: {} + pg-cloudflare@1.1.1: + optional: true + + pg-connection-string@2.7.0: {} + + pg-int8@1.0.1: {} + + pg-numeric@1.0.2: {} + + pg-pool@3.7.0(pg@8.13.1): + dependencies: + pg: 8.13.1 + + pg-protocol@1.7.0: {} + + pg-types@2.2.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.0 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + + pg-types@4.0.2: + dependencies: + pg-int8: 1.0.1 + pg-numeric: 1.0.2 + postgres-array: 3.0.2 + postgres-bytea: 3.0.0 + postgres-date: 2.1.0 + postgres-interval: 3.0.0 + postgres-range: 1.1.4 + + pg@8.13.1: + dependencies: + pg-connection-string: 2.7.0 + pg-pool: 3.7.0(pg@8.13.1) + pg-protocol: 1.7.0 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.1.1 + + pgpass@1.0.5: + dependencies: + split2: 4.2.0 + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -3782,6 +3946,28 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + postgres-array@2.0.0: {} + + postgres-array@3.0.2: {} + + postgres-bytea@1.0.0: {} + + postgres-bytea@3.0.0: + dependencies: + obuf: 1.1.2 + + postgres-date@1.0.7: {} + + postgres-date@2.1.0: {} + + postgres-interval@1.2.0: + dependencies: + xtend: 4.0.2 + + postgres-interval@3.0.0: {} + + postgres-range@1.1.4: {} + prebuild-install@7.1.2: dependencies: detect-libc: 2.0.3 @@ -3930,6 +4116,8 @@ snapshots: source-map@0.6.1: {} + split2@4.2.0: {} + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -4174,6 +4362,8 @@ snapshots: wrappy@1.0.2: {} + xtend@4.0.2: {} + yaml@1.10.2: {} yaml@2.7.0: {} diff --git a/src/lib/server/db/kysely.ts b/src/lib/server/db/kysely.ts new file mode 100644 index 0000000..908e090 --- /dev/null +++ b/src/lib/server/db/kysely.ts @@ -0,0 +1,15 @@ +import { Kysely, PostgresDialect } from "kysely"; +import { Pool } from "pg"; +import type { Database } from "./schema"; + +const dialect = new PostgresDialect({ + pool: new Pool({ + // TODO + }), +}); + +const db = new Kysely({ dialect }); + +// TODO: Migration + +export default db; diff --git a/src/lib/server/db/schema/client.ts b/src/lib/server/db/schema/client.ts index 1e9eb85..08d16ed 100644 --- a/src/lib/server/db/schema/client.ts +++ b/src/lib/server/db/schema/client.ts @@ -6,6 +6,7 @@ import { foreignKey, unique, } from "drizzle-orm/sqlite-core"; +import type { ColumnType, Generated } from "kysely"; import { user } from "./user"; export const client = sqliteTable( @@ -59,3 +60,32 @@ export const userClientChallenge = sqliteTable( }), }), ); + +interface ClientTable { + id: Generated; + encryption_public_key: string; // Base64 + signature_public_key: string; // Base64 +} + +interface UserClientTable { + user_id: number; + client_id: number; + state: "challenging" | "pending" | "active"; +} + +interface UserClientChallengeTable { + id: Generated; + user_id: number; + client_id: number; + answer: string; // Base64 + allowed_ip: string; + expires_at: ColumnType; +} + +declare module "./index" { + interface Database { + client: ClientTable; + user_client: UserClientTable; + user_client_challenge: UserClientChallengeTable; + } +} diff --git a/src/lib/server/db/schema/file.ts b/src/lib/server/db/schema/file.ts index ffe303b..feda927 100644 --- a/src/lib/server/db/schema/file.ts +++ b/src/lib/server/db/schema/file.ts @@ -1,4 +1,5 @@ import { sqliteTable, text, integer, foreignKey } from "drizzle-orm/sqlite-core"; +import type { ColumnType, Generated, JSONColumnType } from "kysely"; import { hsk } from "./hsk"; import { mek } from "./mek"; import { user } from "./user"; @@ -86,3 +87,61 @@ export const fileLog = sqliteTable("file_log", { action: text("action", { enum: ["create", "rename"] }).notNull(), newName: ciphertext("new_name"), }); + +type Ciphertext = JSONColumnType<{ + ciphertext: string; // Base64 + iv: string; // Base64 +}>; + +interface DirectoryTable { + id: Generated; + parent_id: number | null; + user_id: number; + master_encryption_key_version: number; + encrypted_data_encryption_key: string; // Base64 + data_encryption_key_version: Date; + encrypted_name: Ciphertext; +} + +interface DirectoryLogTable { + id: Generated; + directory_id: number; + timestamp: ColumnType; + action: "create" | "rename"; + new_name: Ciphertext | null; +} + +interface FileTable { + id: Generated; + parent_id: number | null; + user_id: number; + path: string; + master_encryption_key_version: number; + encrypted_data_encryption_key: string; // Base64 + data_encryption_key_version: Date; + hmac_secret_key_version: number | null; + content_hmac: string | null; // Base64 + content_type: string; + encrypted_content_iv: string; // Base64 + encrypted_content_hash: string; // Base64 + encrypted_name: Ciphertext; + encrypted_created_at: Ciphertext | null; + encrypted_last_modified_at: Ciphertext; +} + +interface FileLogTable { + id: Generated; + file_id: number; + timestamp: ColumnType; + action: "create" | "rename"; + new_name: Ciphertext | null; +} + +declare module "./index" { + interface Database { + directory: DirectoryTable; + directory_log: DirectoryLogTable; + file: FileTable; + file_log: FileLogTable; + } +} diff --git a/src/lib/server/db/schema/hsk.ts b/src/lib/server/db/schema/hsk.ts index 51f25cc..28b7a89 100644 --- a/src/lib/server/db/schema/hsk.ts +++ b/src/lib/server/db/schema/hsk.ts @@ -1,4 +1,5 @@ import { sqliteTable, text, integer, primaryKey, foreignKey } from "drizzle-orm/sqlite-core"; +import type { ColumnType, Generated } from "kysely"; import { client } from "./client"; import { mek } from "./mek"; import { user } from "./user"; @@ -42,3 +43,27 @@ export const hskLog = sqliteTable( }), }), ); + +interface HskTable { + user_id: number; + version: number; + state: "active"; + master_encryption_key_version: number; + encrypted_key: string; // Base64 +} + +interface HskLogTable { + id: Generated; + user_id: number; + hmac_secret_key_version: number; + timestamp: ColumnType; + action: "create"; + action_by: number | null; +} + +declare module "./index" { + interface Database { + hmac_secret_key: HskTable; + hmac_secret_key_log: HskLogTable; + } +} diff --git a/src/lib/server/db/schema/index.ts b/src/lib/server/db/schema/index.ts index 40cb9be..4292231 100644 --- a/src/lib/server/db/schema/index.ts +++ b/src/lib/server/db/schema/index.ts @@ -4,3 +4,5 @@ export * from "./hsk"; export * from "./mek"; export * from "./session"; export * from "./user"; + +export interface Database {} diff --git a/src/lib/server/db/schema/mek.ts b/src/lib/server/db/schema/mek.ts index e496d9e..e0ac10d 100644 --- a/src/lib/server/db/schema/mek.ts +++ b/src/lib/server/db/schema/mek.ts @@ -1,4 +1,5 @@ import { sqliteTable, text, integer, primaryKey, foreignKey } from "drizzle-orm/sqlite-core"; +import type { ColumnType, Generated } from "kysely"; import { client } from "./client"; import { user } from "./user"; @@ -58,3 +59,34 @@ export const clientMek = sqliteTable( }), }), ); + +interface MekTable { + user_id: number; + version: number; + state: "active" | "retired" | "dead"; +} + +interface MekLogTable { + id: Generated; + user_id: number; + master_encryption_key_version: number; + timestamp: ColumnType; + action: "create"; + action_by: number | null; +} + +interface ClientMekTable { + user_id: number; + client_id: number; + version: number; + encrypted_key: string; // Base64 + encrypted_key_signature: string; // Base64 +} + +declare module "./index" { + interface Database { + master_encryption_key: MekTable; + master_encryption_key_log: MekLogTable; + client_master_encryption_key: ClientMekTable; + } +} diff --git a/src/lib/server/db/schema/session.ts b/src/lib/server/db/schema/session.ts index 5f2129d..d74f099 100644 --- a/src/lib/server/db/schema/session.ts +++ b/src/lib/server/db/schema/session.ts @@ -1,4 +1,5 @@ import { sqliteTable, text, integer, unique } from "drizzle-orm/sqlite-core"; +import type { ColumnType, Generated } from "kysely"; import { client } from "./client"; import { user } from "./user"; @@ -33,3 +34,29 @@ export const sessionUpgradeChallenge = sqliteTable("session_upgrade_challenge", allowedIp: text("allowed_ip").notNull(), expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(), }); + +interface SessionTable { + id: string; + user_id: number; + client_id: number | null; + created_at: ColumnType; + last_used_at: Date; + last_used_by_ip: string | null; + last_used_by_agent: string | null; +} + +interface SessionUpgradeChallengeTable { + id: Generated; + session_id: string; + client_id: number; + answer: string; // Base64 + allowed_ip: string; + expires_at: ColumnType; +} + +declare module "./index" { + interface Database { + session: SessionTable; + session_upgrade_challenge: SessionUpgradeChallengeTable; + } +} diff --git a/src/lib/server/db/schema/user.ts b/src/lib/server/db/schema/user.ts index c98fa01..7a34988 100644 --- a/src/lib/server/db/schema/user.ts +++ b/src/lib/server/db/schema/user.ts @@ -1,4 +1,5 @@ import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core"; +import type { Generated } from "kysely"; export const user = sqliteTable("user", { id: integer("id").primaryKey({ autoIncrement: true }), @@ -6,3 +7,16 @@ export const user = sqliteTable("user", { password: text("password").notNull(), nickname: text("nickname").notNull(), }); + +interface UserTable { + id: Generated; + email: string; + nickname: string; + password: string; +} + +declare module "./index" { + interface Database { + user: UserTable; + } +} From a3c169f70624869251889ef172c3a13474100052 Mon Sep 17 00:00:00 2001 From: static Date: Mon, 20 Jan 2025 16:05:35 +0900 Subject: [PATCH 10/53] =?UTF-8?q?=EB=A0=88=ED=8F=AC=EC=A7=80=ED=86=A0?= =?UTF-8?q?=EB=A6=AC=20=EB=A0=88=EC=9D=B4=EC=96=B4=EC=9D=98=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=EB=A5=BC=20Kysely=20=EA=B8=B0=EB=B0=98=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=AA=A8=EB=91=90=20=EB=A7=88=EC=9D=B4=EA=B7=B8?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=85=98=20(WiP)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 6 +- docker-compose.dev.yaml | 15 + docker-compose.yaml | 19 +- package.json | 1 + src/lib/server/db/client.ts | 234 +++++++++----- src/lib/server/db/file.ts | 499 +++++++++++++++++------------ src/lib/server/db/hsk.ts | 80 +++-- src/lib/server/db/kysely.ts | 7 +- src/lib/server/db/mek.ts | 127 +++++--- src/lib/server/db/schema/client.ts | 4 +- src/lib/server/db/schema/file.ts | 6 +- src/lib/server/db/schema/hsk.ts | 4 +- src/lib/server/db/schema/index.ts | 1 + src/lib/server/db/schema/mek.ts | 4 +- src/lib/server/db/session.ts | 125 ++++---- src/lib/server/db/user.ts | 39 ++- src/lib/server/loadenv.ts | 9 +- 17 files changed, 724 insertions(+), 456 deletions(-) create mode 100644 docker-compose.dev.yaml diff --git a/.env.example b/.env.example index 128bd9f..f492443 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,12 @@ # Required environment variables +DATABASE_PASSWORD= SESSION_SECRET= # Optional environment variables -DATABASE_URL= +DATABASE_HOST= +DATABASE_PORT= +DATABASE_USER= +DATABASE_NAME= SESSION_EXPIRES= USER_CLIENT_CHALLENGE_EXPIRES= SESSION_UPGRADE_CHALLENGE_EXPIRES= diff --git a/docker-compose.dev.yaml b/docker-compose.dev.yaml new file mode 100644 index 0000000..f6570a5 --- /dev/null +++ b/docker-compose.dev.yaml @@ -0,0 +1,15 @@ +services: + database: + image: postgres:17.2 + restart: on-failure + volumes: + - database:/var/lib/postgresql/data + environment: + - POSTGRES_USER=${DATABASE_USER:-} + - POSTGRES_PASSWORD=${DATABASE_PASSWORD:?} # Required + - POSTGRES_DB=${DATABASE_NAME:-} + ports: + - ${DATABASE_PORT:-5432}:5432 + +volumes: + database: diff --git a/docker-compose.yaml b/docker-compose.yaml index aecd8c8..b14f0df 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,13 +1,17 @@ services: server: build: . - restart: unless-stopped + restart: on-failure + depends_on: + - database user: ${CONTAINER_UID:-0}:${CONTAINER_GID:-0} volumes: - - ./data:/app/data + - ./data/library:/app/data/library environment: # ArkVault - - DATABASE_URL=/app/data/database.sqlite + - DATABASE_HOST=database + - DATABASE_USER=arkvault + - DATABASE_PASSWORD=${DATABASE_PASSWORD:?} # Required - SESSION_SECRET=${SESSION_SECRET:?} # Required - SESSION_EXPIRES - USER_CLIENT_CHALLENGE_EXPIRES @@ -19,3 +23,12 @@ services: - NODE_ENV=${NODE_ENV:-production} ports: - ${PORT:-80}:3000 + + database: + image: postgres:17.2-alpine + restart: on-failure + volumes: + - ./data/database:/var/lib/postgresql/data + environment: + - POSTGRES_USER=arkvault + - POSTGRES_PASSWORD=${DATABASE_PASSWORD:?} diff --git a/package.json b/package.json index 02c4be9..b71f912 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "type": "module", "scripts": { "dev": "vite dev", + "dev:db": "docker compose -f docker-compose.dev.yaml up -d", "build": "vite build", "preview": "vite preview", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", diff --git a/src/lib/server/db/client.ts b/src/lib/server/db/client.ts index 37a1054..4fd23f3 100644 --- a/src/lib/server/db/client.ts +++ b/src/lib/server/db/client.ts @@ -1,53 +1,97 @@ -import { SqliteError } from "better-sqlite3"; -import { and, or, eq, gt, lte } from "drizzle-orm"; -import db from "./drizzle"; +import { DatabaseError } from "pg"; import { IntegrityError } from "./error"; -import { client, userClient, userClientChallenge } from "./schema"; +import db from "./kysely"; +import type { UserClientState } from "./schema"; + +interface Client { + id: number; + encPubKey: string; + sigPubKey: string; +} + +interface UserClient { + userId: number; + clientId: number; + state: UserClientState; +} + +interface UserClientWithDetails extends UserClient { + encPubKey: string; + sigPubKey: string; +} export const createClient = async (encPubKey: string, sigPubKey: string, userId: number) => { - return await db.transaction( - async (tx) => { - const clients = await tx - .select({ id: client.id }) - .from(client) - .where(or(eq(client.encPubKey, sigPubKey), eq(client.sigPubKey, encPubKey))) - .limit(1); - if (clients.length !== 0) { + return await db + .transaction() + .setIsolationLevel("serializable") + .execute(async (trx) => { + const client = await trx + .selectFrom("client") + .where((eb) => + eb.or([ + eb("encryption_public_key", "=", encPubKey), + eb("encryption_public_key", "=", sigPubKey), + eb("signature_public_key", "=", encPubKey), + eb("signature_public_key", "=", sigPubKey), + ]), + ) + .limit(1) + .executeTakeFirst(); + if (client) { throw new IntegrityError("Public key(s) already registered"); } - const newClients = await tx - .insert(client) - .values({ encPubKey, sigPubKey }) - .returning({ id: client.id }); - const { id: clientId } = newClients[0]!; - await tx.insert(userClient).values({ userId, clientId }); - - return clientId; - }, - { behavior: "exclusive" }, - ); + const { clientId } = await trx + .insertInto("client") + .values({ encryption_public_key: encPubKey, signature_public_key: sigPubKey }) + .returning("id as clientId") + .executeTakeFirstOrThrow(); + await trx + .insertInto("user_client") + .values({ user_id: userId, client_id: clientId }) + .execute(); + return { clientId }; + }); }; export const getClient = async (clientId: number) => { - const clients = await db.select().from(client).where(eq(client.id, clientId)).limit(1); - return clients[0] ?? null; + const client = await db + .selectFrom("client") + .selectAll() + .where("id", "=", clientId) + .limit(1) + .executeTakeFirst(); + return client + ? ({ + id: client.id, + encPubKey: client.encryption_public_key, + sigPubKey: client.signature_public_key, + } satisfies Client) + : null; }; export const getClientByPubKeys = async (encPubKey: string, sigPubKey: string) => { - const clients = await db - .select() - .from(client) - .where(and(eq(client.encPubKey, encPubKey), eq(client.sigPubKey, sigPubKey))) - .limit(1); - return clients[0] ?? null; + const client = await db + .selectFrom("client") + .selectAll() + .where("encryption_public_key", "=", encPubKey) + .where("signature_public_key", "=", sigPubKey) + .limit(1) + .executeTakeFirst(); + return client + ? ({ + id: client.id, + encPubKey: client.encryption_public_key, + sigPubKey: client.signature_public_key, + } satisfies Client) + : null; }; export const createUserClient = async (userId: number, clientId: number) => { try { - await db.insert(userClient).values({ userId, clientId }); + await db.insertInto("user_client").values({ user_id: userId, client_id: clientId }).execute(); } catch (e) { - if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_PRIMARYKEY") { + if (e instanceof DatabaseError && e.code === "23505") { throw new IntegrityError("User client already exists"); } throw e; @@ -55,52 +99,76 @@ export const createUserClient = async (userId: number, clientId: number) => { }; export const getAllUserClients = async (userId: number) => { - return await db.select().from(userClient).where(eq(userClient.userId, userId)); + const userClients = await db + .selectFrom("user_client") + .selectAll() + .where("user_id", "=", userId) + .execute(); + return userClients.map( + ({ user_id, client_id, state }) => + ({ + userId: user_id, + clientId: client_id, + state, + }) satisfies UserClient, + ); }; export const getUserClient = async (userId: number, clientId: number) => { - const userClients = await db - .select() - .from(userClient) - .where(and(eq(userClient.userId, userId), eq(userClient.clientId, clientId))) - .limit(1); - return userClients[0] ?? null; + const userClient = await db + .selectFrom("user_client") + .selectAll() + .where("user_id", "=", userId) + .where("client_id", "=", clientId) + .limit(1) + .executeTakeFirst(); + return userClient + ? ({ + userId: userClient.user_id, + clientId: userClient.client_id, + state: userClient.state, + } satisfies UserClient) + : null; }; export const getUserClientWithDetails = async (userId: number, clientId: number) => { - const userClients = await db - .select() - .from(userClient) - .innerJoin(client, eq(userClient.clientId, client.id)) - .where(and(eq(userClient.userId, userId), eq(userClient.clientId, clientId))) - .limit(1); - return userClients[0] ?? null; + const userClient = await db + .selectFrom("user_client") + .innerJoin("client", "user_client.client_id", "client.id") + .selectAll() + .where("user_id", "=", userId) + .where("client_id", "=", clientId) + .limit(1) + .executeTakeFirst(); + return userClient + ? ({ + userId: userClient.user_id, + clientId: userClient.client_id, + state: userClient.state, + encPubKey: userClient.encryption_public_key, + sigPubKey: userClient.signature_public_key, + } satisfies UserClientWithDetails) + : null; }; export const setUserClientStateToPending = async (userId: number, clientId: number) => { await db - .update(userClient) + .updateTable("user_client") .set({ state: "pending" }) - .where( - and( - eq(userClient.userId, userId), - eq(userClient.clientId, clientId), - eq(userClient.state, "challenging"), - ), - ); + .where("user_id", "=", userId) + .where("client_id", "=", clientId) + .where("state", "=", "challenging") + .execute(); }; export const setUserClientStateToActive = async (userId: number, clientId: number) => { await db - .update(userClient) + .updateTable("user_client") .set({ state: "active" }) - .where( - and( - eq(userClient.userId, userId), - eq(userClient.clientId, clientId), - eq(userClient.state, "pending"), - ), - ); + .where("user_id", "=", userId) + .where("client_id", "=", clientId) + .where("state", "=", "pending") + .execute(); }; export const registerUserClientChallenge = async ( @@ -110,30 +178,30 @@ export const registerUserClientChallenge = async ( allowedIp: string, expiresAt: Date, ) => { - await db.insert(userClientChallenge).values({ - userId, - clientId, - answer, - allowedIp, - expiresAt, - }); + await db + .insertInto("user_client_challenge") + .values({ + user_id: userId, + client_id: clientId, + answer, + allowed_ip: allowedIp, + expires_at: expiresAt, + }) + .execute(); }; export const consumeUserClientChallenge = async (userId: number, answer: string, ip: string) => { - const challenges = await db - .delete(userClientChallenge) - .where( - and( - eq(userClientChallenge.userId, userId), - eq(userClientChallenge.answer, answer), - eq(userClientChallenge.allowedIp, ip), - gt(userClientChallenge.expiresAt, new Date()), - ), - ) - .returning({ clientId: userClientChallenge.clientId }); - return challenges[0] ?? null; + const challenge = await db + .deleteFrom("user_client_challenge") + .where("user_id", "=", userId) + .where("answer", "=", answer) + .where("allowed_ip", "=", ip) + .where("expires_at", ">", new Date()) + .returning("client_id") + .executeTakeFirst(); + return challenge ? { clientId: challenge.client_id } : null; }; export const cleanupExpiredUserClientChallenges = async () => { - await db.delete(userClientChallenge).where(lte(userClientChallenge.expiresAt, new Date())); + await db.deleteFrom("user_client_challenge").where("expires_at", "<=", new Date()).execute(); }; diff --git a/src/lib/server/db/file.ts b/src/lib/server/db/file.ts index 6bd0452..a893b7d 100644 --- a/src/lib/server/db/file.ts +++ b/src/lib/server/db/file.ts @@ -1,21 +1,23 @@ -import { and, eq, isNull } from "drizzle-orm"; -import db from "./drizzle"; import { IntegrityError } from "./error"; -import { directory, directoryLog, file, fileLog, hsk, mek } from "./schema"; +import db from "./kysely"; +import type { Ciphertext } from "./schema"; type DirectoryId = "root" | number; -export interface NewDirectoryParams { +interface Directory { + id: number; parentId: DirectoryId; userId: number; mekVersion: number; encDek: string; dekVersion: Date; - encName: string; - encNameIv: string; + encName: Ciphertext; } -export interface NewFileParams { +export type NewDirectory = Omit; + +interface File { + id: number; parentId: DirectoryId; userId: number; path: string; @@ -27,217 +29,264 @@ export interface NewFileParams { contentType: string; encContentIv: string; encContentHash: string; - encName: string; - encNameIv: string; - encCreatedAt: string | null; - encCreatedAtIv: string | null; - encLastModifiedAt: string; - encLastModifiedAtIv: string; + encName: Ciphertext; + encCreatedAt: Ciphertext | null; + encLastModifiedAt: Ciphertext; } -export const registerDirectory = async (params: NewDirectoryParams) => { - await db.transaction( - async (tx) => { - const meks = await tx - .select({ version: mek.version }) - .from(mek) - .where(and(eq(mek.userId, params.userId), eq(mek.state, "active"))) - .limit(1); - if (meks[0]?.version !== params.mekVersion) { - throw new IntegrityError("Inactive MEK version"); - } +export type NewFile = Omit; - const newDirectories = await tx - .insert(directory) - .values({ - parentId: params.parentId === "root" ? null : params.parentId, - userId: params.userId, - mekVersion: params.mekVersion, - encDek: params.encDek, - dekVersion: params.dekVersion, - encName: { ciphertext: params.encName, iv: params.encNameIv }, - }) - .returning({ id: directory.id }); - const { id: directoryId } = newDirectories[0]!; - await tx.insert(directoryLog).values({ - directoryId, +export const registerDirectory = async (params: NewDirectory) => { + await db.transaction().execute(async (trx) => { + const mek = await trx + .selectFrom("master_encryption_key") + .select("version") + .where("user_id", "=", params.userId) + .where("state", "=", "active") + .limit(1) + .forUpdate() + .executeTakeFirst(); + if (mek?.version !== params.mekVersion) { + throw new IntegrityError("Inactive MEK version"); + } + + const { directoryId } = await trx + .insertInto("directory") + .values({ + parent_id: params.parentId !== "root" ? params.parentId : null, + user_id: params.userId, + master_encryption_key_version: params.mekVersion, + encrypted_data_encryption_key: params.encDek, + data_encryption_key_version: params.dekVersion, + encrypted_name: params.encName, + }) + .returning("id as directoryId") + .executeTakeFirstOrThrow(); + await trx + .insertInto("directory_log") + .values({ + directory_id: directoryId, timestamp: new Date(), action: "create", - newName: { ciphertext: params.encName, iv: params.encNameIv }, - }); - }, - { behavior: "exclusive" }, - ); + new_name: params.encName, + }) + .execute(); + }); }; export const getAllDirectoriesByParent = async (userId: number, parentId: DirectoryId) => { - return await db - .select() - .from(directory) - .where( - and( - eq(directory.userId, userId), - parentId === "root" ? isNull(directory.parentId) : eq(directory.parentId, parentId), - ), - ); + let query = db.selectFrom("directory").selectAll().where("user_id", "=", userId); + query = + parentId === "root" + ? query.where("parent_id", "is", null) + : query.where("parent_id", "=", parentId); + const directories = await query.execute(); + return directories.map( + (directory) => + ({ + id: directory.id, + parentId: directory.parent_id ?? "root", + userId: directory.user_id, + mekVersion: directory.master_encryption_key_version, + encDek: directory.encrypted_data_encryption_key, + dekVersion: directory.data_encryption_key_version, + encName: directory.encrypted_name, + }) satisfies Directory, + ); }; export const getDirectory = async (userId: number, directoryId: number) => { - const res = await db - .select() - .from(directory) - .where(and(eq(directory.userId, userId), eq(directory.id, directoryId))) - .limit(1); - return res[0] ?? null; + const directory = await db + .selectFrom("directory") + .selectAll() + .where("id", "=", directoryId) + .where("user_id", "=", userId) + .limit(1) + .executeTakeFirst(); + return directory + ? ({ + id: directory.id, + parentId: directory.parent_id ?? "root", + userId: directory.user_id, + mekVersion: directory.master_encryption_key_version, + encDek: directory.encrypted_data_encryption_key, + dekVersion: directory.data_encryption_key_version, + encName: directory.encrypted_name, + } satisfies Directory) + : null; }; export const setDirectoryEncName = async ( userId: number, directoryId: number, dekVersion: Date, - encName: string, - encNameIv: string, + encName: Ciphertext, ) => { - await db.transaction( - async (tx) => { - const directories = await tx - .select({ version: directory.dekVersion }) - .from(directory) - .where(and(eq(directory.userId, userId), eq(directory.id, directoryId))) - .limit(1); - if (!directories[0]) { - throw new IntegrityError("Directory not found"); - } else if (directories[0].version.getTime() !== dekVersion.getTime()) { - throw new IntegrityError("Invalid DEK version"); - } + await db.transaction().execute(async (trx) => { + const directory = await trx + .selectFrom("directory") + .select("data_encryption_key_version") + .where("id", "=", directoryId) + .where("user_id", "=", userId) + .limit(1) + .forUpdate() + .executeTakeFirst(); + if (!directory) { + throw new IntegrityError("Directory not found"); + } else if (directory.data_encryption_key_version.getTime() !== dekVersion.getTime()) { + throw new IntegrityError("Invalid DEK version"); + } - await tx - .update(directory) - .set({ encName: { ciphertext: encName, iv: encNameIv } }) - .where(and(eq(directory.userId, userId), eq(directory.id, directoryId))); - await tx.insert(directoryLog).values({ - directoryId, + await trx + .updateTable("directory") + .set({ encrypted_name: encName }) + .where("id", "=", directoryId) + .where("user_id", "=", userId) + .execute(); + await trx + .insertInto("directory_log") + .values({ + directory_id: directoryId, timestamp: new Date(), action: "rename", - newName: { ciphertext: encName, iv: encNameIv }, - }); - }, - { behavior: "exclusive" }, - ); + new_name: encName, + }) + .execute(); + }); }; export const unregisterDirectory = async (userId: number, directoryId: number) => { - return await db.transaction( - async (tx) => { + return await db + .transaction() + .setIsolationLevel("repeatable read") // TODO: Sufficient? + .execute(async (trx) => { const unregisterFiles = async (parentId: number) => { - return await tx - .delete(file) - .where(and(eq(file.userId, userId), eq(file.parentId, parentId))) - .returning({ id: file.id, path: file.path }); + return await trx + .deleteFrom("file") + .where("parent_id", "=", parentId) + .where("user_id", "=", userId) + .returning(["id", "path"]) + .execute(); }; const unregisterDirectoryRecursively = async ( directoryId: number, ): Promise<{ id: number; path: string }[]> => { const files = await unregisterFiles(directoryId); - const subDirectories = await tx - .select({ id: directory.id }) - .from(directory) - .where(and(eq(directory.userId, userId), eq(directory.parentId, directoryId))); + const subDirectories = await trx + .selectFrom("directory") + .select("id") + .where("parent_id", "=", directoryId) + .where("user_id", "=", userId) + .execute(); const subDirectoryFilePaths = await Promise.all( subDirectories.map(async ({ id }) => await unregisterDirectoryRecursively(id)), ); - const deleteRes = await tx.delete(directory).where(eq(directory.id, directoryId)); - if (deleteRes.changes === 0) { + const deleteRes = await trx + .deleteFrom("directory") + .where("id", "=", directoryId) + .where("user_id", "=", userId) + .executeTakeFirst(); + if (deleteRes.numDeletedRows === 0n) { throw new IntegrityError("Directory not found"); } return files.concat(...subDirectoryFilePaths); }; return await unregisterDirectoryRecursively(directoryId); - }, - { behavior: "exclusive" }, - ); + }); }; -export const registerFile = async (params: NewFileParams) => { - if ( - (params.hskVersion && !params.contentHmac) || - (!params.hskVersion && params.contentHmac) || - (params.encCreatedAt && !params.encCreatedAtIv) || - (!params.encCreatedAt && params.encCreatedAtIv) - ) { +export const registerFile = async (params: NewFile) => { + if ((params.hskVersion && !params.contentHmac) || (!params.hskVersion && params.contentHmac)) { throw new Error("Invalid arguments"); } - await db.transaction( - async (tx) => { - const meks = await tx - .select({ version: mek.version }) - .from(mek) - .where(and(eq(mek.userId, params.userId), eq(mek.state, "active"))) - .limit(1); - if (meks[0]?.version !== params.mekVersion) { - throw new IntegrityError("Inactive MEK version"); - } + await db.transaction().execute(async (trx) => { + const mek = await trx + .selectFrom("master_encryption_key") + .select("version") + .where("user_id", "=", params.userId) + .where("state", "=", "active") + .limit(1) + .forUpdate() + .executeTakeFirst(); + if (mek?.version !== params.mekVersion) { + throw new IntegrityError("Inactive MEK version"); + } - if (params.hskVersion) { - const hsks = await tx - .select({ version: hsk.version }) - .from(hsk) - .where(and(eq(hsk.userId, params.userId), eq(hsk.state, "active"))) - .limit(1); - if (hsks[0]?.version !== params.hskVersion) { - throw new IntegrityError("Inactive HSK version"); - } + if (params.hskVersion) { + const hsk = await trx + .selectFrom("hmac_secret_key") + .select("version") + .where("user_id", "=", params.userId) + .where("state", "=", "active") + .limit(1) + .forUpdate() + .executeTakeFirst(); + if (hsk?.version !== params.hskVersion) { + throw new IntegrityError("Inactive HSK version"); } + } - const newFiles = await tx - .insert(file) - .values({ - path: params.path, - parentId: params.parentId === "root" ? null : params.parentId, - userId: params.userId, - mekVersion: params.mekVersion, - hskVersion: params.hskVersion, - encDek: params.encDek, - dekVersion: params.dekVersion, - contentHmac: params.contentHmac, - contentType: params.contentType, - encContentIv: params.encContentIv, - encContentHash: params.encContentHash, - encName: { ciphertext: params.encName, iv: params.encNameIv }, - encCreatedAt: - params.encCreatedAt && params.encCreatedAtIv - ? { ciphertext: params.encCreatedAt, iv: params.encCreatedAtIv } - : null, - encLastModifiedAt: { - ciphertext: params.encLastModifiedAt, - iv: params.encLastModifiedAtIv, - }, - }) - .returning({ id: file.id }); - const { id: fileId } = newFiles[0]!; - await tx.insert(fileLog).values({ - fileId, + const { fileId } = await trx + .insertInto("file") + .values({ + parent_id: params.parentId !== "root" ? params.parentId : null, + user_id: params.userId, + path: params.path, + master_encryption_key_version: params.mekVersion, + encrypted_data_encryption_key: params.encDek, + data_encryption_key_version: params.dekVersion, + hmac_secret_key_version: params.hskVersion, + content_hmac: params.contentHmac, + content_type: params.contentType, + encrypted_content_iv: params.encContentIv, + encrypted_content_hash: params.encContentHash, + encrypted_name: params.encName, + encrypted_created_at: params.encCreatedAt, + encrypted_last_modified_at: params.encLastModifiedAt, + }) + .returning("id as fileId") + .executeTakeFirstOrThrow(); + await trx + .insertInto("file_log") + .values({ + file_id: fileId, timestamp: new Date(), action: "create", - newName: { ciphertext: params.encName, iv: params.encNameIv }, - }); - }, - { behavior: "exclusive" }, - ); + new_name: params.encName, + }) + .execute(); + }); }; export const getAllFilesByParent = async (userId: number, parentId: DirectoryId) => { - return await db - .select() - .from(file) - .where( - and( - eq(file.userId, userId), - parentId === "root" ? isNull(file.parentId) : eq(file.parentId, parentId), - ), - ); + let query = db.selectFrom("file").selectAll().where("user_id", "=", userId); + query = + parentId === "root" + ? query.where("parent_id", "is", null) + : query.where("parent_id", "=", parentId); + const files = await query.execute(); + return files.map( + (file) => + ({ + id: file.id, + parentId: file.parent_id ?? "root", + userId: file.user_id, + path: file.path, + mekVersion: file.master_encryption_key_version, + encDek: file.encrypted_data_encryption_key, + dekVersion: file.data_encryption_key_version, + hskVersion: file.hmac_secret_key_version, + contentHmac: file.content_hmac, + contentType: file.content_type, + encContentIv: file.encrypted_content_iv, + encContentHash: file.encrypted_content_hash, + encName: file.encrypted_name, + encCreatedAt: file.encrypted_created_at, + encLastModifiedAt: file.encrypted_last_modified_at, + }) satisfies File, + ); }; export const getAllFileIdsByContentHmac = async ( @@ -245,69 +294,93 @@ export const getAllFileIdsByContentHmac = async ( hskVersion: number, contentHmac: string, ) => { - return await db - .select({ id: file.id }) - .from(file) - .where( - and( - eq(file.userId, userId), - eq(file.hskVersion, hskVersion), - eq(file.contentHmac, contentHmac), - ), - ); + const files = await db + .selectFrom("file") + .select("id") + .where("user_id", "=", userId) + .where("hmac_secret_key_version", "=", hskVersion) + .where("content_hmac", "=", contentHmac) + .execute(); + return files.map(({ id }) => ({ id })); }; export const getFile = async (userId: number, fileId: number) => { - const res = await db - .select() - .from(file) - .where(and(eq(file.userId, userId), eq(file.id, fileId))) - .limit(1); - return res[0] ?? null; + const file = await db + .selectFrom("file") + .selectAll() + .where("id", "=", fileId) + .where("user_id", "=", userId) + .limit(1) + .executeTakeFirst(); + return file + ? ({ + id: file.id, + parentId: file.parent_id ?? "root", + userId: file.user_id, + path: file.path, + mekVersion: file.master_encryption_key_version, + encDek: file.encrypted_data_encryption_key, + dekVersion: file.data_encryption_key_version, + hskVersion: file.hmac_secret_key_version, + contentHmac: file.content_hmac, + contentType: file.content_type, + encContentIv: file.encrypted_content_iv, + encContentHash: file.encrypted_content_hash, + encName: file.encrypted_name, + encCreatedAt: file.encrypted_created_at, + encLastModifiedAt: file.encrypted_last_modified_at, + } satisfies File) + : null; }; export const setFileEncName = async ( userId: number, fileId: number, dekVersion: Date, - encName: string, - encNameIv: string, + encName: Ciphertext, ) => { - await db.transaction( - async (tx) => { - const files = await tx - .select({ version: file.dekVersion }) - .from(file) - .where(and(eq(file.userId, userId), eq(file.id, fileId))) - .limit(1); - if (!files[0]) { - throw new IntegrityError("File not found"); - } else if (files[0].version.getTime() !== dekVersion.getTime()) { - throw new IntegrityError("Invalid DEK version"); - } + await db.transaction().execute(async (trx) => { + const file = await trx + .selectFrom("file") + .select("data_encryption_key_version") + .where("id", "=", fileId) + .where("user_id", "=", userId) + .limit(1) + .forUpdate() + .executeTakeFirst(); + if (!file) { + throw new IntegrityError("File not found"); + } else if (file.data_encryption_key_version.getTime() !== dekVersion.getTime()) { + throw new IntegrityError("Invalid DEK version"); + } - await tx - .update(file) - .set({ encName: { ciphertext: encName, iv: encNameIv } }) - .where(and(eq(file.userId, userId), eq(file.id, fileId))); - await tx.insert(fileLog).values({ - fileId, + await trx + .updateTable("file") + .set({ encrypted_name: encName }) + .where("id", "=", fileId) + .where("user_id", "=", userId) + .execute(); + await trx + .insertInto("file_log") + .values({ + file_id: fileId, timestamp: new Date(), action: "rename", - newName: { ciphertext: encName, iv: encNameIv }, - }); - }, - { behavior: "exclusive" }, - ); + new_name: encName, + }) + .execute(); + }); }; export const unregisterFile = async (userId: number, fileId: number) => { - const files = await db - .delete(file) - .where(and(eq(file.userId, userId), eq(file.id, fileId))) - .returning({ path: file.path }); - if (!files[0]) { + const file = await db + .deleteFrom("file") + .where("id", "=", fileId) + .where("user_id", "=", userId) + .returning("path") + .executeTakeFirst(); + if (!file) { throw new IntegrityError("File not found"); } - return files[0].path; + return { path: file.path }; }; diff --git a/src/lib/server/db/hsk.ts b/src/lib/server/db/hsk.ts index faf7dc6..a1bf66b 100644 --- a/src/lib/server/db/hsk.ts +++ b/src/lib/server/db/hsk.ts @@ -1,8 +1,15 @@ -import { SqliteError } from "better-sqlite3"; -import { and, eq } from "drizzle-orm"; -import db from "./drizzle"; +import { DatabaseError } from "pg"; import { IntegrityError } from "./error"; -import { hsk, hskLog } from "./schema"; +import db from "./kysely"; +import type { HskState } from "./schema"; + +interface Hsk { + userId: number; + version: number; + state: HskState; + mekVersion: number; + encHsk: string; +} export const registerInitialHsk = async ( userId: number, @@ -10,37 +17,52 @@ export const registerInitialHsk = async ( mekVersion: number, encHsk: string, ) => { - await db.transaction( - async (tx) => { - try { - await tx.insert(hsk).values({ - userId, + await db.transaction().execute(async (trx) => { + try { + await trx + .insertInto("hmac_secret_key") + .values({ + user_id: userId, version: 1, state: "active", - mekVersion, - encHsk, - }); - await tx.insert(hskLog).values({ - userId, - hskVersion: 1, + master_encryption_key_version: mekVersion, + encrypted_key: encHsk, + }) + .execute(); + await trx + .insertInto("hmac_secret_key_log") + .values({ + user_id: userId, + hmac_secret_key_version: 1, timestamp: new Date(), action: "create", - actionBy: createdBy, - }); - } catch (e) { - if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_PRIMARYKEY") { - throw new IntegrityError("HSK already registered"); - } - throw e; + action_by: createdBy, + }) + .execute(); + } catch (e) { + if (e instanceof DatabaseError && e.code === "23505") { + throw new IntegrityError("HSK already registered"); } - }, - { behavior: "exclusive" }, - ); + throw e; + } + }); }; export const getAllValidHsks = async (userId: number) => { - return await db - .select() - .from(hsk) - .where(and(eq(hsk.userId, userId), eq(hsk.state, "active"))); + const hsks = await db + .selectFrom("hmac_secret_key") + .selectAll() + .where("user_id", "=", userId) + .where("state", "=", "active") + .execute(); + return hsks.map( + ({ user_id, version, state, master_encryption_key_version, encrypted_key }) => + ({ + userId: user_id, + version, + state: state as "active", + mekVersion: master_encryption_key_version, + encHsk: encrypted_key, + }) satisfies Hsk, + ); }; diff --git a/src/lib/server/db/kysely.ts b/src/lib/server/db/kysely.ts index 908e090..9665bb3 100644 --- a/src/lib/server/db/kysely.ts +++ b/src/lib/server/db/kysely.ts @@ -1,10 +1,15 @@ import { Kysely, PostgresDialect } from "kysely"; import { Pool } from "pg"; +import env from "$lib/server/loadenv"; import type { Database } from "./schema"; const dialect = new PostgresDialect({ pool: new Pool({ - // TODO + host: env.database.host, + port: env.database.port, + user: env.database.user, + password: env.database.password, + database: env.database.name, }), }); diff --git a/src/lib/server/db/mek.ts b/src/lib/server/db/mek.ts index 944636e..4d57013 100644 --- a/src/lib/server/db/mek.ts +++ b/src/lib/server/db/mek.ts @@ -1,8 +1,19 @@ -import { SqliteError } from "better-sqlite3"; -import { and, or, eq } from "drizzle-orm"; -import db from "./drizzle"; +import { DatabaseError } from "pg"; import { IntegrityError } from "./error"; -import { mek, mekLog, clientMek } from "./schema"; +import db from "./kysely"; +import type { MekState } from "./schema"; + +interface Mek { + userId: number; + version: number; + state: MekState; +} + +interface ClientMekWithDetails extends Mek { + clientId: number; + encMek: string; + encMekSig: string; +} export const registerInitialMek = async ( userId: number, @@ -10,58 +21,80 @@ export const registerInitialMek = async ( encMek: string, encMekSig: string, ) => { - await db.transaction( - async (tx) => { - try { - await tx.insert(mek).values({ - userId, + await db.transaction().execute(async (trx) => { + try { + await trx + .insertInto("master_encryption_key") + .values({ + user_id: userId, version: 1, state: "active", - }); - await tx.insert(clientMek).values({ - userId, - clientId: createdBy, - mekVersion: 1, - encMek, - encMekSig, - }); - await tx.insert(mekLog).values({ - userId, - mekVersion: 1, + }) + .execute(); + await trx + .insertInto("client_master_encryption_key") + .values({ + user_id: userId, + client_id: createdBy, + version: 1, + encrypted_key: encMek, + encrypted_key_signature: encMekSig, + }) + .execute(); + await trx + .insertInto("master_encryption_key_log") + .values({ + user_id: userId, + master_encryption_key_version: 1, timestamp: new Date(), action: "create", - actionBy: createdBy, - }); - } catch (e) { - if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_PRIMARYKEY") { - throw new IntegrityError("MEK already registered"); - } - throw e; + action_by: createdBy, + }) + .execute(); + } catch (e) { + if (e instanceof DatabaseError && e.code === "23505") { + throw new IntegrityError("MEK already registered"); } - }, - { behavior: "exclusive" }, - ); + throw e; + } + }); }; export const getInitialMek = async (userId: number) => { - const meks = await db - .select() - .from(mek) - .where(and(eq(mek.userId, userId), eq(mek.version, 1))) - .limit(1); - return meks[0] ?? null; + const mek = await db + .selectFrom("master_encryption_key") + .selectAll() + .where("user_id", "=", userId) + .where("version", "=", 1) + .limit(1) + .executeTakeFirst(); + return mek + ? ({ userId: mek.user_id, version: mek.version, state: mek.state } satisfies Mek) + : null; }; export const getAllValidClientMeks = async (userId: number, clientId: number) => { - return await db - .select() - .from(clientMek) - .innerJoin(mek, and(eq(clientMek.userId, mek.userId), eq(clientMek.mekVersion, mek.version))) - .where( - and( - eq(clientMek.userId, userId), - eq(clientMek.clientId, clientId), - or(eq(mek.state, "active"), eq(mek.state, "retired")), - ), - ); + const clientMeks = await db + .selectFrom("client_master_encryption_key") + .innerJoin("master_encryption_key", (join) => + join + .onRef("client_master_encryption_key.user_id", "=", "master_encryption_key.user_id") + .onRef("client_master_encryption_key.version", "=", "master_encryption_key.version"), + ) + .selectAll() + .where("user_id", "=", userId) + .where("client_id", "=", clientId) + .where((eb) => eb.or([eb("state", "=", "active"), eb("state", "=", "retired")])) + .execute(); + return clientMeks.map( + ({ user_id, client_id, version, state, encrypted_key, encrypted_key_signature }) => + ({ + userId: user_id, + version, + state: state as "active" | "retired", + clientId: client_id, + encMek: encrypted_key, + encMekSig: encrypted_key_signature, + }) satisfies ClientMekWithDetails, + ); }; diff --git a/src/lib/server/db/schema/client.ts b/src/lib/server/db/schema/client.ts index 08d16ed..e5642a6 100644 --- a/src/lib/server/db/schema/client.ts +++ b/src/lib/server/db/schema/client.ts @@ -67,10 +67,12 @@ interface ClientTable { signature_public_key: string; // Base64 } +export type UserClientState = "challenging" | "pending" | "active"; + interface UserClientTable { user_id: number; client_id: number; - state: "challenging" | "pending" | "active"; + state: ColumnType; } interface UserClientChallengeTable { diff --git a/src/lib/server/db/schema/file.ts b/src/lib/server/db/schema/file.ts index feda927..60e3f22 100644 --- a/src/lib/server/db/schema/file.ts +++ b/src/lib/server/db/schema/file.ts @@ -1,5 +1,5 @@ import { sqliteTable, text, integer, foreignKey } from "drizzle-orm/sqlite-core"; -import type { ColumnType, Generated, JSONColumnType } from "kysely"; +import type { ColumnType, Generated } from "kysely"; import { hsk } from "./hsk"; import { mek } from "./mek"; import { user } from "./user"; @@ -88,10 +88,10 @@ export const fileLog = sqliteTable("file_log", { newName: ciphertext("new_name"), }); -type Ciphertext = JSONColumnType<{ +export type Ciphertext = { ciphertext: string; // Base64 iv: string; // Base64 -}>; +}; interface DirectoryTable { id: Generated; diff --git a/src/lib/server/db/schema/hsk.ts b/src/lib/server/db/schema/hsk.ts index 28b7a89..aca5193 100644 --- a/src/lib/server/db/schema/hsk.ts +++ b/src/lib/server/db/schema/hsk.ts @@ -44,10 +44,12 @@ export const hskLog = sqliteTable( }), ); +export type HskState = "active"; + interface HskTable { user_id: number; version: number; - state: "active"; + state: HskState; master_encryption_key_version: number; encrypted_key: string; // Base64 } diff --git a/src/lib/server/db/schema/index.ts b/src/lib/server/db/schema/index.ts index 4292231..64aa270 100644 --- a/src/lib/server/db/schema/index.ts +++ b/src/lib/server/db/schema/index.ts @@ -5,4 +5,5 @@ export * from "./mek"; export * from "./session"; export * from "./user"; +// eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface Database {} diff --git a/src/lib/server/db/schema/mek.ts b/src/lib/server/db/schema/mek.ts index e0ac10d..ae454c1 100644 --- a/src/lib/server/db/schema/mek.ts +++ b/src/lib/server/db/schema/mek.ts @@ -60,10 +60,12 @@ export const clientMek = sqliteTable( }), ); +export type MekState = "active" | "retired" | "dead"; + interface MekTable { user_id: number; version: number; - state: "active" | "retired" | "dead"; + state: MekState; } interface MekLogTable { diff --git a/src/lib/server/db/session.ts b/src/lib/server/db/session.ts index 819dd86..cd4d558 100644 --- a/src/lib/server/db/session.ts +++ b/src/lib/server/db/session.ts @@ -1,30 +1,31 @@ -import { SqliteError } from "better-sqlite3"; -import { and, eq, ne, gt, lte, isNull } from "drizzle-orm"; +import { DatabaseError } from "pg"; import env from "$lib/server/loadenv"; -import db from "./drizzle"; import { IntegrityError } from "./error"; -import { session, sessionUpgradeChallenge } from "./schema"; +import db from "./kysely"; export const createSession = async ( userId: number, clientId: number | null, sessionId: string, ip: string | null, - userAgent: string | null, + agent: string | null, ) => { try { const now = new Date(); - await db.insert(session).values({ - id: sessionId, - userId, - clientId, - createdAt: now, - lastUsedAt: now, - lastUsedByIp: ip || null, - lastUsedByUserAgent: userAgent || null, - }); + await db + .insertInto("session") + .values({ + id: sessionId, + user_id: userId, + client_id: clientId, + created_at: now, + last_used_at: now, + last_used_by_ip: ip || null, + last_used_by_agent: agent || null, + }) + .execute(); } catch (e) { - if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_UNIQUE") { + if (e instanceof DatabaseError && e.code === "23505") { throw new IntegrityError("Session already exists"); } throw e; @@ -34,49 +35,55 @@ export const createSession = async ( export const refreshSession = async ( sessionId: string, ip: string | null, - userAgent: string | null, + agent: string | null, ) => { const now = new Date(); - const sessions = await db - .update(session) + const session = await db + .updateTable("session") .set({ - lastUsedAt: now, - lastUsedByIp: ip || undefined, - lastUsedByUserAgent: userAgent || undefined, + last_used_at: now, + last_used_by_ip: ip !== "" ? ip : undefined, // Don't update if empty + last_used_by_agent: agent !== "" ? agent : undefined, // Don't update if empty }) - .where( - and( - eq(session.id, sessionId), - gt(session.lastUsedAt, new Date(now.getTime() - env.session.exp)), - ), - ) - .returning({ userId: session.userId, clientId: session.clientId }); - if (!sessions[0]) { + .where("id", "=", sessionId) + .where("last_used_at", ">", new Date(now.getTime() - env.session.exp)) + .returning(["user_id", "client_id"]) + .executeTakeFirst(); + if (!session) { throw new IntegrityError("Session not found"); } - return sessions[0]; + return { userId: session.user_id, clientId: session.client_id }; }; export const upgradeSession = async (sessionId: string, clientId: number) => { const res = await db - .update(session) - .set({ clientId }) - .where(and(eq(session.id, sessionId), isNull(session.clientId))); - if (res.changes === 0) { + .updateTable("session") + .set({ client_id: clientId }) + .where("id", "=", sessionId) + .where("client_id", "is", null) + .executeTakeFirst(); + if (res.numUpdatedRows === 0n) { throw new IntegrityError("Session not found"); } }; export const deleteSession = async (sessionId: string) => { - await db.delete(session).where(eq(session.id, sessionId)); + await db.deleteFrom("session").where("id", "=", sessionId).execute(); }; export const deleteAllOtherSessions = async (userId: number, sessionId: string) => { - await db.delete(session).where(and(eq(session.userId, userId), ne(session.id, sessionId))); + await db + .deleteFrom("session") + .where("id", "!=", sessionId) + .where("user_id", "=", userId) + .execute(); }; export const cleanupExpiredSessions = async () => { - await db.delete(session).where(lte(session.lastUsedAt, new Date(Date.now() - env.session.exp))); + await db + .deleteFrom("session") + .where("last_used_at", "<=", new Date(Date.now() - env.session.exp)) + .execute(); }; export const registerSessionUpgradeChallenge = async ( @@ -87,15 +94,18 @@ export const registerSessionUpgradeChallenge = async ( expiresAt: Date, ) => { try { - await db.insert(sessionUpgradeChallenge).values({ - sessionId, - clientId, - answer, - allowedIp, - expiresAt, - }); + await db + .insertInto("session_upgrade_challenge") + .values({ + session_id: sessionId, + client_id: clientId, + answer, + allowed_ip: allowedIp, + expires_at: expiresAt, + }) + .execute(); } catch (e) { - if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_UNIQUE") { + if (e instanceof DatabaseError && e.code === "23505") { throw new IntegrityError("Challenge already registered"); } throw e; @@ -107,22 +117,17 @@ export const consumeSessionUpgradeChallenge = async ( answer: string, ip: string, ) => { - const challenges = await db - .delete(sessionUpgradeChallenge) - .where( - and( - eq(sessionUpgradeChallenge.sessionId, sessionId), - eq(sessionUpgradeChallenge.answer, answer), - eq(sessionUpgradeChallenge.allowedIp, ip), - gt(sessionUpgradeChallenge.expiresAt, new Date()), - ), - ) - .returning({ clientId: sessionUpgradeChallenge.clientId }); - return challenges[0] ?? null; + const challenge = await db + .deleteFrom("session_upgrade_challenge") + .where("session_id", "=", sessionId) + .where("answer", "=", answer) + .where("allowed_ip", "=", ip) + .where("expires_at", ">", new Date()) + .returning("client_id") + .executeTakeFirst(); + return challenge ? { clientId: challenge.client_id } : null; }; export const cleanupExpiredSessionUpgradeChallenges = async () => { - await db - .delete(sessionUpgradeChallenge) - .where(lte(sessionUpgradeChallenge.expiresAt, new Date())); + await db.deleteFrom("session_upgrade_challenge").where("expires_at", "<=", new Date()).execute(); }; diff --git a/src/lib/server/db/user.ts b/src/lib/server/db/user.ts index d970438..3964a94 100644 --- a/src/lib/server/db/user.ts +++ b/src/lib/server/db/user.ts @@ -1,21 +1,36 @@ -import { eq } from "drizzle-orm"; -import db from "./drizzle"; -import { user } from "./schema"; +import db from "./kysely"; + +interface User { + id: number; + email: string; + nickname: string; + password: string; +} export const getUser = async (userId: number) => { - const users = await db.select().from(user).where(eq(user.id, userId)).limit(1); - return users[0] ?? null; + const user = await db + .selectFrom("user") + .selectAll() + .where("id", "=", userId) + .limit(1) + .executeTakeFirst(); + return user ? (user satisfies User) : null; }; export const getUserByEmail = async (email: string) => { - const users = await db.select().from(user).where(eq(user.email, email)).limit(1); - return users[0] ?? null; -}; - -export const setUserPassword = async (userId: number, password: string) => { - await db.update(user).set({ password }).where(eq(user.id, userId)); + const user = await db + .selectFrom("user") + .selectAll() + .where("email", "=", email) + .limit(1) + .executeTakeFirst(); + return user ? (user satisfies User) : null; }; export const setUserNickname = async (userId: number, nickname: string) => { - await db.update(user).set({ nickname }).where(eq(user.id, userId)); + await db.updateTable("user").set({ nickname }).where("id", "=", userId).execute(); +}; + +export const setUserPassword = async (userId: number, password: string) => { + await db.updateTable("user").set({ password }).where("id", "=", userId).execute(); }; diff --git a/src/lib/server/loadenv.ts b/src/lib/server/loadenv.ts index 01e442a..0ebb110 100644 --- a/src/lib/server/loadenv.ts +++ b/src/lib/server/loadenv.ts @@ -3,11 +3,18 @@ import { building } from "$app/environment"; import { env } from "$env/dynamic/private"; if (!building) { + if (!env.DATABASE_PASSWORD) throw new Error("DATABASE_PASSWORD not set"); if (!env.SESSION_SECRET) throw new Error("SESSION_SECRET not set"); } export default { - databaseUrl: env.DATABASE_URL || "local.db", + database: { + host: env.DATABASE_HOST || "localhost", + port: parseInt(env.DATABASE_PORT || "5432", 10), + user: env.DATABASE_USER, + password: env.DATABASE_PASSWORD!, + name: env.DATABASE_NAME, + }, session: { secret: env.SESSION_SECRET!, exp: ms(env.SESSION_EXPIRES || "14d"), From ce329891ae50d31929db1c0223e21477331b48bc Mon Sep 17 00:00:00 2001 From: static Date: Mon, 20 Jan 2025 17:37:34 +0900 Subject: [PATCH 11/53] =?UTF-8?q?Drizzle=20=EB=B0=8F=20SQLite3=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=ED=8C=A8=ED=82=A4=EC=A7=80/=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- drizzle.config.ts | 13 - drizzle/0000_regular_the_watchers.sql | 178 --- drizzle/meta/0000_snapshot.json | 1308 ----------------- drizzle/meta/_journal.json | 13 - kysely.config.ts | 18 + package.json | 9 +- pnpm-lock.yaml | 1283 ++++++---------- src/lib/server/db/drizzle.ts | 15 - .../db/migrations/1737357000-Initial.ts | 222 +++ src/lib/server/db/schema/client.ts | 61 - src/lib/server/db/schema/file.ts | 88 -- src/lib/server/db/schema/hsk.ts | 44 - src/lib/server/db/schema/mek.ts | 60 - src/lib/server/db/schema/session.ts | 35 - src/lib/server/db/schema/user.ts | 8 - src/lib/server/loadenv.ts | 4 +- 16 files changed, 722 insertions(+), 2637 deletions(-) delete mode 100644 drizzle.config.ts delete mode 100644 drizzle/0000_regular_the_watchers.sql delete mode 100644 drizzle/meta/0000_snapshot.json delete mode 100644 drizzle/meta/_journal.json create mode 100644 kysely.config.ts delete mode 100644 src/lib/server/db/drizzle.ts create mode 100644 src/lib/server/db/migrations/1737357000-Initial.ts diff --git a/drizzle.config.ts b/drizzle.config.ts deleted file mode 100644 index c0b54d5..0000000 --- a/drizzle.config.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { defineConfig } from "drizzle-kit"; - -export default defineConfig({ - schema: "./src/lib/server/db/schema", - - dbCredentials: { - url: process.env.DATABASE_URL || "local.db", - }, - - verbose: true, - strict: true, - dialect: "sqlite", -}); diff --git a/drizzle/0000_regular_the_watchers.sql b/drizzle/0000_regular_the_watchers.sql deleted file mode 100644 index 6cbdec8..0000000 --- a/drizzle/0000_regular_the_watchers.sql +++ /dev/null @@ -1,178 +0,0 @@ -CREATE TABLE `client` ( - `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - `encryption_public_key` text NOT NULL, - `signature_public_key` text NOT NULL -); ---> statement-breakpoint -CREATE TABLE `user_client` ( - `user_id` integer NOT NULL, - `client_id` integer NOT NULL, - `state` text DEFAULT 'challenging' NOT NULL, - PRIMARY KEY(`client_id`, `user_id`), - FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action, - FOREIGN KEY (`client_id`) REFERENCES `client`(`id`) ON UPDATE no action ON DELETE no action -); ---> statement-breakpoint -CREATE TABLE `user_client_challenge` ( - `id` integer PRIMARY KEY NOT NULL, - `user_id` integer NOT NULL, - `client_id` integer NOT NULL, - `answer` text NOT NULL, - `allowed_ip` text NOT NULL, - `expires_at` integer NOT NULL, - FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action, - FOREIGN KEY (`client_id`) REFERENCES `client`(`id`) ON UPDATE no action ON DELETE no action, - FOREIGN KEY (`user_id`,`client_id`) REFERENCES `user_client`(`user_id`,`client_id`) ON UPDATE no action ON DELETE no action -); ---> statement-breakpoint -CREATE TABLE `directory` ( - `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - `parent_id` integer, - `user_id` integer NOT NULL, - `master_encryption_key_version` integer NOT NULL, - `encrypted_data_encryption_key` text NOT NULL, - `data_encryption_key_version` integer NOT NULL, - `encrypted_name` text NOT NULL, - FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action, - FOREIGN KEY (`parent_id`) REFERENCES `directory`(`id`) ON UPDATE no action ON DELETE no action, - FOREIGN KEY (`user_id`,`master_encryption_key_version`) REFERENCES `master_encryption_key`(`user_id`,`version`) ON UPDATE no action ON DELETE no action -); ---> statement-breakpoint -CREATE TABLE `directory_log` ( - `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - `directory_id` integer NOT NULL, - `timestamp` integer NOT NULL, - `action` text NOT NULL, - `new_name` text, - FOREIGN KEY (`directory_id`) REFERENCES `directory`(`id`) ON UPDATE no action ON DELETE cascade -); ---> statement-breakpoint -CREATE TABLE `file` ( - `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - `parent_id` integer, - `user_id` integer NOT NULL, - `path` text NOT NULL, - `master_encryption_key_version` integer NOT NULL, - `encrypted_data_encryption_key` text NOT NULL, - `data_encryption_key_version` integer NOT NULL, - `hmac_secret_key_version` integer, - `content_hmac` text, - `content_type` text NOT NULL, - `encrypted_content_iv` text NOT NULL, - `encrypted_content_hash` text NOT NULL, - `encrypted_name` text NOT NULL, - `encrypted_created_at` text, - `encrypted_last_modified_at` text NOT NULL, - FOREIGN KEY (`parent_id`) REFERENCES `directory`(`id`) ON UPDATE no action ON DELETE no action, - FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action, - FOREIGN KEY (`user_id`,`master_encryption_key_version`) REFERENCES `master_encryption_key`(`user_id`,`version`) ON UPDATE no action ON DELETE no action, - FOREIGN KEY (`user_id`,`hmac_secret_key_version`) REFERENCES `hmac_secret_key`(`user_id`,`version`) ON UPDATE no action ON DELETE no action -); ---> statement-breakpoint -CREATE TABLE `file_log` ( - `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - `file_id` integer NOT NULL, - `timestamp` integer NOT NULL, - `action` text NOT NULL, - `new_name` text, - FOREIGN KEY (`file_id`) REFERENCES `file`(`id`) ON UPDATE no action ON DELETE cascade -); ---> statement-breakpoint -CREATE TABLE `hmac_secret_key` ( - `user_id` integer NOT NULL, - `version` integer NOT NULL, - `state` text NOT NULL, - `master_encryption_key_version` integer NOT NULL, - `encrypted_key` text NOT NULL, - PRIMARY KEY(`user_id`, `version`), - FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action, - FOREIGN KEY (`user_id`,`master_encryption_key_version`) REFERENCES `master_encryption_key`(`user_id`,`version`) ON UPDATE no action ON DELETE no action -); ---> statement-breakpoint -CREATE TABLE `hmac_secret_key_log` ( - `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - `user_id` integer NOT NULL, - `hmac_secret_key_version` integer NOT NULL, - `timestamp` integer NOT NULL, - `action` text NOT NULL, - `action_by` integer, - FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action, - FOREIGN KEY (`action_by`) REFERENCES `client`(`id`) ON UPDATE no action ON DELETE no action, - FOREIGN KEY (`user_id`,`hmac_secret_key_version`) REFERENCES `hmac_secret_key`(`user_id`,`version`) ON UPDATE no action ON DELETE no action -); ---> statement-breakpoint -CREATE TABLE `client_master_encryption_key` ( - `user_id` integer NOT NULL, - `client_id` integer NOT NULL, - `version` integer NOT NULL, - `encrypted_key` text NOT NULL, - `encrypted_key_signature` text NOT NULL, - PRIMARY KEY(`client_id`, `user_id`, `version`), - FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action, - FOREIGN KEY (`client_id`) REFERENCES `client`(`id`) ON UPDATE no action ON DELETE no action, - FOREIGN KEY (`user_id`,`version`) REFERENCES `master_encryption_key`(`user_id`,`version`) ON UPDATE no action ON DELETE no action -); ---> statement-breakpoint -CREATE TABLE `master_encryption_key` ( - `user_id` integer NOT NULL, - `version` integer NOT NULL, - `state` text NOT NULL, - `retired_at` integer, - PRIMARY KEY(`user_id`, `version`), - FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action -); ---> statement-breakpoint -CREATE TABLE `master_encryption_key_log` ( - `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - `user_id` integer NOT NULL, - `master_encryption_key_version` integer NOT NULL, - `timestamp` integer NOT NULL, - `action` text NOT NULL, - `action_by` integer, - FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action, - FOREIGN KEY (`action_by`) REFERENCES `client`(`id`) ON UPDATE no action ON DELETE no action, - FOREIGN KEY (`user_id`,`master_encryption_key_version`) REFERENCES `master_encryption_key`(`user_id`,`version`) ON UPDATE no action ON DELETE no action -); ---> statement-breakpoint -CREATE TABLE `session` ( - `id` text PRIMARY KEY NOT NULL, - `user_id` integer NOT NULL, - `client_id` integer, - `created_at` integer NOT NULL, - `last_used_at` integer NOT NULL, - `last_used_by_ip` text, - `last_used_by_user_agent` text, - FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action, - FOREIGN KEY (`client_id`) REFERENCES `client`(`id`) ON UPDATE no action ON DELETE no action -); ---> statement-breakpoint -CREATE TABLE `session_upgrade_challenge` ( - `id` integer PRIMARY KEY NOT NULL, - `session_id` text NOT NULL, - `client_id` integer NOT NULL, - `answer` text NOT NULL, - `allowed_ip` text NOT NULL, - `expires_at` integer NOT NULL, - FOREIGN KEY (`session_id`) REFERENCES `session`(`id`) ON UPDATE no action ON DELETE no action, - FOREIGN KEY (`client_id`) REFERENCES `client`(`id`) ON UPDATE no action ON DELETE no action -); ---> statement-breakpoint -CREATE TABLE `user` ( - `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - `email` text NOT NULL, - `password` text NOT NULL, - `nickname` text NOT NULL -); ---> statement-breakpoint -CREATE UNIQUE INDEX `client_encryption_public_key_unique` ON `client` (`encryption_public_key`);--> statement-breakpoint -CREATE UNIQUE INDEX `client_signature_public_key_unique` ON `client` (`signature_public_key`);--> statement-breakpoint -CREATE UNIQUE INDEX `client_encryption_public_key_signature_public_key_unique` ON `client` (`encryption_public_key`,`signature_public_key`);--> statement-breakpoint -CREATE UNIQUE INDEX `user_client_challenge_answer_unique` ON `user_client_challenge` (`answer`);--> statement-breakpoint -CREATE UNIQUE INDEX `directory_encrypted_data_encryption_key_unique` ON `directory` (`encrypted_data_encryption_key`);--> statement-breakpoint -CREATE UNIQUE INDEX `file_path_unique` ON `file` (`path`);--> statement-breakpoint -CREATE UNIQUE INDEX `file_encrypted_data_encryption_key_unique` ON `file` (`encrypted_data_encryption_key`);--> statement-breakpoint -CREATE UNIQUE INDEX `hmac_secret_key_encrypted_key_unique` ON `hmac_secret_key` (`encrypted_key`);--> statement-breakpoint -CREATE UNIQUE INDEX `session_user_id_client_id_unique` ON `session` (`user_id`,`client_id`);--> statement-breakpoint -CREATE UNIQUE INDEX `session_upgrade_challenge_session_id_unique` ON `session_upgrade_challenge` (`session_id`);--> statement-breakpoint -CREATE UNIQUE INDEX `session_upgrade_challenge_answer_unique` ON `session_upgrade_challenge` (`answer`);--> statement-breakpoint -CREATE UNIQUE INDEX `user_email_unique` ON `user` (`email`); \ No newline at end of file diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json deleted file mode 100644 index 27a42bc..0000000 --- a/drizzle/meta/0000_snapshot.json +++ /dev/null @@ -1,1308 +0,0 @@ -{ - "version": "6", - "dialect": "sqlite", - "id": "396a26d6-6f55-4162-a23e-c1117f3a3757", - "prevId": "00000000-0000-0000-0000-000000000000", - "tables": { - "client": { - "name": "client", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "encryption_public_key": { - "name": "encryption_public_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "signature_public_key": { - "name": "signature_public_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "client_encryption_public_key_unique": { - "name": "client_encryption_public_key_unique", - "columns": [ - "encryption_public_key" - ], - "isUnique": true - }, - "client_signature_public_key_unique": { - "name": "client_signature_public_key_unique", - "columns": [ - "signature_public_key" - ], - "isUnique": true - }, - "client_encryption_public_key_signature_public_key_unique": { - "name": "client_encryption_public_key_signature_public_key_unique", - "columns": [ - "encryption_public_key", - "signature_public_key" - ], - "isUnique": true - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "user_client": { - "name": "user_client", - "columns": { - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "client_id": { - "name": "client_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "state": { - "name": "state", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'challenging'" - } - }, - "indexes": {}, - "foreignKeys": { - "user_client_user_id_user_id_fk": { - "name": "user_client_user_id_user_id_fk", - "tableFrom": "user_client", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "user_client_client_id_client_id_fk": { - "name": "user_client_client_id_client_id_fk", - "tableFrom": "user_client", - "tableTo": "client", - "columnsFrom": [ - "client_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "user_client_user_id_client_id_pk": { - "columns": [ - "client_id", - "user_id" - ], - "name": "user_client_user_id_client_id_pk" - } - }, - "uniqueConstraints": {} - }, - "user_client_challenge": { - "name": "user_client_challenge", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "client_id": { - "name": "client_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "answer": { - "name": "answer", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "allowed_ip": { - "name": "allowed_ip", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires_at": { - "name": "expires_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "user_client_challenge_answer_unique": { - "name": "user_client_challenge_answer_unique", - "columns": [ - "answer" - ], - "isUnique": true - } - }, - "foreignKeys": { - "user_client_challenge_user_id_user_id_fk": { - "name": "user_client_challenge_user_id_user_id_fk", - "tableFrom": "user_client_challenge", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "user_client_challenge_client_id_client_id_fk": { - "name": "user_client_challenge_client_id_client_id_fk", - "tableFrom": "user_client_challenge", - "tableTo": "client", - "columnsFrom": [ - "client_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "user_client_challenge_user_id_client_id_user_client_user_id_client_id_fk": { - "name": "user_client_challenge_user_id_client_id_user_client_user_id_client_id_fk", - "tableFrom": "user_client_challenge", - "tableTo": "user_client", - "columnsFrom": [ - "user_id", - "client_id" - ], - "columnsTo": [ - "user_id", - "client_id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "directory": { - "name": "directory", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "parent_id": { - "name": "parent_id", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "master_encryption_key_version": { - "name": "master_encryption_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_data_encryption_key": { - "name": "encrypted_data_encryption_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "data_encryption_key_version": { - "name": "data_encryption_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_name": { - "name": "encrypted_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "directory_encrypted_data_encryption_key_unique": { - "name": "directory_encrypted_data_encryption_key_unique", - "columns": [ - "encrypted_data_encryption_key" - ], - "isUnique": true - } - }, - "foreignKeys": { - "directory_user_id_user_id_fk": { - "name": "directory_user_id_user_id_fk", - "tableFrom": "directory", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "directory_parent_id_directory_id_fk": { - "name": "directory_parent_id_directory_id_fk", - "tableFrom": "directory", - "tableTo": "directory", - "columnsFrom": [ - "parent_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "directory_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { - "name": "directory_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", - "tableFrom": "directory", - "tableTo": "master_encryption_key", - "columnsFrom": [ - "user_id", - "master_encryption_key_version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "directory_log": { - "name": "directory_log", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "directory_id": { - "name": "directory_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "timestamp": { - "name": "timestamp", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "action": { - "name": "action", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "new_name": { - "name": "new_name", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "directory_log_directory_id_directory_id_fk": { - "name": "directory_log_directory_id_directory_id_fk", - "tableFrom": "directory_log", - "tableTo": "directory", - "columnsFrom": [ - "directory_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "file": { - "name": "file", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "parent_id": { - "name": "parent_id", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "path": { - "name": "path", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "master_encryption_key_version": { - "name": "master_encryption_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_data_encryption_key": { - "name": "encrypted_data_encryption_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "data_encryption_key_version": { - "name": "data_encryption_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "hmac_secret_key_version": { - "name": "hmac_secret_key_version", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "content_hmac": { - "name": "content_hmac", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "content_type": { - "name": "content_type", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_content_iv": { - "name": "encrypted_content_iv", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_content_hash": { - "name": "encrypted_content_hash", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_name": { - "name": "encrypted_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_created_at": { - "name": "encrypted_created_at", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "encrypted_last_modified_at": { - "name": "encrypted_last_modified_at", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "file_path_unique": { - "name": "file_path_unique", - "columns": [ - "path" - ], - "isUnique": true - }, - "file_encrypted_data_encryption_key_unique": { - "name": "file_encrypted_data_encryption_key_unique", - "columns": [ - "encrypted_data_encryption_key" - ], - "isUnique": true - } - }, - "foreignKeys": { - "file_parent_id_directory_id_fk": { - "name": "file_parent_id_directory_id_fk", - "tableFrom": "file", - "tableTo": "directory", - "columnsFrom": [ - "parent_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "file_user_id_user_id_fk": { - "name": "file_user_id_user_id_fk", - "tableFrom": "file", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "file_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { - "name": "file_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", - "tableFrom": "file", - "tableTo": "master_encryption_key", - "columnsFrom": [ - "user_id", - "master_encryption_key_version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "file_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk": { - "name": "file_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk", - "tableFrom": "file", - "tableTo": "hmac_secret_key", - "columnsFrom": [ - "user_id", - "hmac_secret_key_version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "file_log": { - "name": "file_log", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "file_id": { - "name": "file_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "timestamp": { - "name": "timestamp", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "action": { - "name": "action", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "new_name": { - "name": "new_name", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "file_log_file_id_file_id_fk": { - "name": "file_log_file_id_file_id_fk", - "tableFrom": "file_log", - "tableTo": "file", - "columnsFrom": [ - "file_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "hmac_secret_key": { - "name": "hmac_secret_key", - "columns": { - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "version": { - "name": "version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "state": { - "name": "state", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "master_encryption_key_version": { - "name": "master_encryption_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_key": { - "name": "encrypted_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "hmac_secret_key_encrypted_key_unique": { - "name": "hmac_secret_key_encrypted_key_unique", - "columns": [ - "encrypted_key" - ], - "isUnique": true - } - }, - "foreignKeys": { - "hmac_secret_key_user_id_user_id_fk": { - "name": "hmac_secret_key_user_id_user_id_fk", - "tableFrom": "hmac_secret_key", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "hmac_secret_key_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { - "name": "hmac_secret_key_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", - "tableFrom": "hmac_secret_key", - "tableTo": "master_encryption_key", - "columnsFrom": [ - "user_id", - "master_encryption_key_version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "hmac_secret_key_user_id_version_pk": { - "columns": [ - "user_id", - "version" - ], - "name": "hmac_secret_key_user_id_version_pk" - } - }, - "uniqueConstraints": {} - }, - "hmac_secret_key_log": { - "name": "hmac_secret_key_log", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "hmac_secret_key_version": { - "name": "hmac_secret_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "timestamp": { - "name": "timestamp", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "action": { - "name": "action", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "action_by": { - "name": "action_by", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "hmac_secret_key_log_user_id_user_id_fk": { - "name": "hmac_secret_key_log_user_id_user_id_fk", - "tableFrom": "hmac_secret_key_log", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "hmac_secret_key_log_action_by_client_id_fk": { - "name": "hmac_secret_key_log_action_by_client_id_fk", - "tableFrom": "hmac_secret_key_log", - "tableTo": "client", - "columnsFrom": [ - "action_by" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "hmac_secret_key_log_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk": { - "name": "hmac_secret_key_log_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk", - "tableFrom": "hmac_secret_key_log", - "tableTo": "hmac_secret_key", - "columnsFrom": [ - "user_id", - "hmac_secret_key_version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "client_master_encryption_key": { - "name": "client_master_encryption_key", - "columns": { - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "client_id": { - "name": "client_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "version": { - "name": "version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_key": { - "name": "encrypted_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_key_signature": { - "name": "encrypted_key_signature", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "client_master_encryption_key_user_id_user_id_fk": { - "name": "client_master_encryption_key_user_id_user_id_fk", - "tableFrom": "client_master_encryption_key", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "client_master_encryption_key_client_id_client_id_fk": { - "name": "client_master_encryption_key_client_id_client_id_fk", - "tableFrom": "client_master_encryption_key", - "tableTo": "client", - "columnsFrom": [ - "client_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "client_master_encryption_key_user_id_version_master_encryption_key_user_id_version_fk": { - "name": "client_master_encryption_key_user_id_version_master_encryption_key_user_id_version_fk", - "tableFrom": "client_master_encryption_key", - "tableTo": "master_encryption_key", - "columnsFrom": [ - "user_id", - "version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "client_master_encryption_key_user_id_client_id_version_pk": { - "columns": [ - "client_id", - "user_id", - "version" - ], - "name": "client_master_encryption_key_user_id_client_id_version_pk" - } - }, - "uniqueConstraints": {} - }, - "master_encryption_key": { - "name": "master_encryption_key", - "columns": { - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "version": { - "name": "version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "state": { - "name": "state", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "retired_at": { - "name": "retired_at", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "master_encryption_key_user_id_user_id_fk": { - "name": "master_encryption_key_user_id_user_id_fk", - "tableFrom": "master_encryption_key", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "master_encryption_key_user_id_version_pk": { - "columns": [ - "user_id", - "version" - ], - "name": "master_encryption_key_user_id_version_pk" - } - }, - "uniqueConstraints": {} - }, - "master_encryption_key_log": { - "name": "master_encryption_key_log", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "master_encryption_key_version": { - "name": "master_encryption_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "timestamp": { - "name": "timestamp", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "action": { - "name": "action", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "action_by": { - "name": "action_by", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "master_encryption_key_log_user_id_user_id_fk": { - "name": "master_encryption_key_log_user_id_user_id_fk", - "tableFrom": "master_encryption_key_log", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "master_encryption_key_log_action_by_client_id_fk": { - "name": "master_encryption_key_log_action_by_client_id_fk", - "tableFrom": "master_encryption_key_log", - "tableTo": "client", - "columnsFrom": [ - "action_by" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "master_encryption_key_log_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { - "name": "master_encryption_key_log_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", - "tableFrom": "master_encryption_key_log", - "tableTo": "master_encryption_key", - "columnsFrom": [ - "user_id", - "master_encryption_key_version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "session": { - "name": "session", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "client_id": { - "name": "client_id", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "last_used_at": { - "name": "last_used_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "last_used_by_ip": { - "name": "last_used_by_ip", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "last_used_by_user_agent": { - "name": "last_used_by_user_agent", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": { - "session_user_id_client_id_unique": { - "name": "session_user_id_client_id_unique", - "columns": [ - "user_id", - "client_id" - ], - "isUnique": true - } - }, - "foreignKeys": { - "session_user_id_user_id_fk": { - "name": "session_user_id_user_id_fk", - "tableFrom": "session", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "session_client_id_client_id_fk": { - "name": "session_client_id_client_id_fk", - "tableFrom": "session", - "tableTo": "client", - "columnsFrom": [ - "client_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "session_upgrade_challenge": { - "name": "session_upgrade_challenge", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "session_id": { - "name": "session_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "client_id": { - "name": "client_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "answer": { - "name": "answer", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "allowed_ip": { - "name": "allowed_ip", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires_at": { - "name": "expires_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "session_upgrade_challenge_session_id_unique": { - "name": "session_upgrade_challenge_session_id_unique", - "columns": [ - "session_id" - ], - "isUnique": true - }, - "session_upgrade_challenge_answer_unique": { - "name": "session_upgrade_challenge_answer_unique", - "columns": [ - "answer" - ], - "isUnique": true - } - }, - "foreignKeys": { - "session_upgrade_challenge_session_id_session_id_fk": { - "name": "session_upgrade_challenge_session_id_session_id_fk", - "tableFrom": "session_upgrade_challenge", - "tableTo": "session", - "columnsFrom": [ - "session_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "session_upgrade_challenge_client_id_client_id_fk": { - "name": "session_upgrade_challenge_client_id_client_id_fk", - "tableFrom": "session_upgrade_challenge", - "tableTo": "client", - "columnsFrom": [ - "client_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "user": { - "name": "user", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "password": { - "name": "password", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "nickname": { - "name": "nickname", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "user_email_unique": { - "name": "user_email_unique", - "columns": [ - "email" - ], - "isUnique": true - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - } - }, - "enums": {}, - "_meta": { - "schemas": {}, - "tables": {}, - "columns": {} - }, - "internal": { - "indexes": {} - } -} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json deleted file mode 100644 index bdbdf66..0000000 --- a/drizzle/meta/_journal.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "version": "7", - "dialect": "sqlite", - "entries": [ - { - "idx": 0, - "version": "6", - "when": 1737219722656, - "tag": "0000_regular_the_watchers", - "breakpoints": true - } - ] -} \ No newline at end of file diff --git a/kysely.config.ts b/kysely.config.ts new file mode 100644 index 0000000..1fe5a76 --- /dev/null +++ b/kysely.config.ts @@ -0,0 +1,18 @@ +import { defineConfig } from "kysely-ctl"; +import { Pool } from "pg"; + +export default defineConfig({ + dialect: "pg", + dialectConfig: { + pool: new Pool({ + host: process.env.DATABASE_HOST, + port: process.env.DATABASE_PORT ? parseInt(process.env.DATABASE_PORT) : undefined, + user: process.env.DATABASE_USER, + password: process.env.DATABASE_PASSWORD, + database: process.env.DATABASE_NAME, + }), + }, + migrations: { + migrationFolder: "./src/lib/server/db/migrations", + }, +}); diff --git a/package.json b/package.json index b71f912..cfed32a 100644 --- a/package.json +++ b/package.json @@ -12,10 +12,7 @@ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "format": "prettier --write .", "lint": "prettier --check . && eslint .", - "db:push": "drizzle-kit push", - "db:generate": "drizzle-kit generate", - "db:migrate": "drizzle-kit migrate", - "db:studio": "drizzle-kit studio" + "db:migrate": "kysely migrate" }, "devDependencies": { "@eslint/compat": "^1.2.4", @@ -31,7 +28,6 @@ "autoprefixer": "^10.4.20", "axios": "^1.7.9", "dexie": "^4.0.10", - "drizzle-kit": "^0.22.8", "eslint": "^9.17.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-svelte": "^2.46.1", @@ -40,6 +36,7 @@ "file-saver": "^2.0.5", "globals": "^15.14.0", "heic2any": "^0.0.4", + "kysely-ctl": "^0.10.1", "mime": "^4.0.6", "p-limit": "^6.2.0", "prettier": "^3.4.2", @@ -56,8 +53,6 @@ "dependencies": { "@fastify/busboy": "^3.1.1", "argon2": "^0.41.1", - "better-sqlite3": "^11.7.2", - "drizzle-orm": "^0.33.0", "kysely": "^0.27.5", "ms": "^2.1.3", "node-schedule": "^2.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3dd6588..5092a35 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,12 +14,6 @@ importers: argon2: specifier: ^0.41.1 version: 0.41.1 - better-sqlite3: - specifier: ^11.7.2 - version: 11.7.2 - drizzle-orm: - specifier: ^0.33.0 - version: 0.33.0(@types/better-sqlite3@7.6.12)(@types/pg@8.11.10)(better-sqlite3@11.7.2)(kysely@0.27.5)(pg@8.13.1) kysely: specifier: ^0.27.5 version: 0.27.5 @@ -41,7 +35,7 @@ importers: devDependencies: '@eslint/compat': specifier: ^1.2.4 - version: 1.2.4(eslint@9.17.0(jiti@1.21.7)) + version: 1.2.4(eslint@9.17.0(jiti@2.4.2)) '@iconify-json/material-symbols': specifier: ^1.2.12 version: 1.2.12 @@ -78,18 +72,15 @@ importers: dexie: specifier: ^4.0.10 version: 4.0.10 - drizzle-kit: - specifier: ^0.22.8 - version: 0.22.8 eslint: specifier: ^9.17.0 - version: 9.17.0(jiti@1.21.7) + version: 9.17.0(jiti@2.4.2) eslint-config-prettier: specifier: ^9.1.0 - version: 9.1.0(eslint@9.17.0(jiti@1.21.7)) + version: 9.1.0(eslint@9.17.0(jiti@2.4.2)) eslint-plugin-svelte: specifier: ^2.46.1 - version: 2.46.1(eslint@9.17.0(jiti@1.21.7))(svelte@5.17.1) + version: 2.46.1(eslint@9.17.0(jiti@2.4.2))(svelte@5.17.1) eslint-plugin-tailwindcss: specifier: ^3.17.5 version: 3.17.5(tailwindcss@3.4.17) @@ -105,6 +96,9 @@ importers: heic2any: specifier: ^0.0.4 version: 0.0.4 + kysely-ctl: + specifier: ^0.10.1 + version: 0.10.1(kysely@0.27.5) mime: specifier: ^4.0.6 version: 4.0.6 @@ -134,7 +128,7 @@ importers: version: 5.7.3 typescript-eslint: specifier: ^8.19.1 - version: 8.19.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3) + version: 8.19.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3) unplugin-icons: specifier: ^0.22.0 version: 0.22.0(svelte@5.17.1) @@ -161,37 +155,17 @@ packages: '@antfu/utils@0.7.10': resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==} - '@esbuild-kit/core-utils@3.3.2': - resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} - deprecated: 'Merged into tsx: https://tsx.is' - - '@esbuild-kit/esm-loader@2.6.5': - resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==} - deprecated: 'Merged into tsx: https://tsx.is' - - '@esbuild/aix-ppc64@0.19.12': - resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [aix] - '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.18.20': - resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm64@0.19.12': - resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] + '@esbuild/aix-ppc64@0.23.1': + resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] '@esbuild/android-arm64@0.21.5': resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} @@ -199,16 +173,10 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm@0.18.20': - resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - - '@esbuild/android-arm@0.19.12': - resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} - engines: {node: '>=12'} - cpu: [arm] + '@esbuild/android-arm64@0.23.1': + resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} + engines: {node: '>=18'} + cpu: [arm64] os: [android] '@esbuild/android-arm@0.21.5': @@ -217,16 +185,10 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-x64@0.18.20': - resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - - '@esbuild/android-x64@0.19.12': - resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} - engines: {node: '>=12'} - cpu: [x64] + '@esbuild/android-arm@0.23.1': + resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} + engines: {node: '>=18'} + cpu: [arm] os: [android] '@esbuild/android-x64@0.21.5': @@ -235,17 +197,11 @@ packages: cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.18.20': - resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-arm64@0.19.12': - resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] + '@esbuild/android-x64@0.23.1': + resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] '@esbuild/darwin-arm64@0.21.5': resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} @@ -253,16 +209,10 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.18.20': - resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - - '@esbuild/darwin-x64@0.19.12': - resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} - engines: {node: '>=12'} - cpu: [x64] + '@esbuild/darwin-arm64@0.23.1': + resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} + engines: {node: '>=18'} + cpu: [arm64] os: [darwin] '@esbuild/darwin-x64@0.21.5': @@ -271,17 +221,11 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.18.20': - resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-arm64@0.19.12': - resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] + '@esbuild/darwin-x64@0.23.1': + resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] '@esbuild/freebsd-arm64@0.21.5': resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} @@ -289,16 +233,10 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.18.20': - resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.19.12': - resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} - engines: {node: '>=12'} - cpu: [x64] + '@esbuild/freebsd-arm64@0.23.1': + resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} + engines: {node: '>=18'} + cpu: [arm64] os: [freebsd] '@esbuild/freebsd-x64@0.21.5': @@ -307,17 +245,11 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.18.20': - resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm64@0.19.12': - resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] + '@esbuild/freebsd-x64@0.23.1': + resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] '@esbuild/linux-arm64@0.21.5': resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} @@ -325,16 +257,10 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.18.20': - resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-arm@0.19.12': - resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} - engines: {node: '>=12'} - cpu: [arm] + '@esbuild/linux-arm64@0.23.1': + resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} + engines: {node: '>=18'} + cpu: [arm64] os: [linux] '@esbuild/linux-arm@0.21.5': @@ -343,16 +269,10 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.18.20': - resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-ia32@0.19.12': - resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} - engines: {node: '>=12'} - cpu: [ia32] + '@esbuild/linux-arm@0.23.1': + resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} + engines: {node: '>=18'} + cpu: [arm] os: [linux] '@esbuild/linux-ia32@0.21.5': @@ -361,16 +281,10 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.18.20': - resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-loong64@0.19.12': - resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} - engines: {node: '>=12'} - cpu: [loong64] + '@esbuild/linux-ia32@0.23.1': + resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} + engines: {node: '>=18'} + cpu: [ia32] os: [linux] '@esbuild/linux-loong64@0.21.5': @@ -379,16 +293,10 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.18.20': - resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-mips64el@0.19.12': - resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} - engines: {node: '>=12'} - cpu: [mips64el] + '@esbuild/linux-loong64@0.23.1': + resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} + engines: {node: '>=18'} + cpu: [loong64] os: [linux] '@esbuild/linux-mips64el@0.21.5': @@ -397,16 +305,10 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.18.20': - resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-ppc64@0.19.12': - resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} - engines: {node: '>=12'} - cpu: [ppc64] + '@esbuild/linux-mips64el@0.23.1': + resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} + engines: {node: '>=18'} + cpu: [mips64el] os: [linux] '@esbuild/linux-ppc64@0.21.5': @@ -415,16 +317,10 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.18.20': - resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-riscv64@0.19.12': - resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} - engines: {node: '>=12'} - cpu: [riscv64] + '@esbuild/linux-ppc64@0.23.1': + resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} + engines: {node: '>=18'} + cpu: [ppc64] os: [linux] '@esbuild/linux-riscv64@0.21.5': @@ -433,16 +329,10 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.18.20': - resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-s390x@0.19.12': - resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} - engines: {node: '>=12'} - cpu: [s390x] + '@esbuild/linux-riscv64@0.23.1': + resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} + engines: {node: '>=18'} + cpu: [riscv64] os: [linux] '@esbuild/linux-s390x@0.21.5': @@ -451,16 +341,10 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.18.20': - resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - - '@esbuild/linux-x64@0.19.12': - resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} - engines: {node: '>=12'} - cpu: [x64] + '@esbuild/linux-s390x@0.23.1': + resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} + engines: {node: '>=18'} + cpu: [s390x] os: [linux] '@esbuild/linux-x64@0.21.5': @@ -469,17 +353,11 @@ packages: cpu: [x64] os: [linux] - '@esbuild/netbsd-x64@0.18.20': - resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} - engines: {node: '>=12'} + '@esbuild/linux-x64@0.23.1': + resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} + engines: {node: '>=18'} cpu: [x64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.19.12': - resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] + os: [linux] '@esbuild/netbsd-x64@0.21.5': resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} @@ -487,16 +365,16 @@ packages: cpu: [x64] os: [netbsd] - '@esbuild/openbsd-x64@0.18.20': - resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} - engines: {node: '>=12'} + '@esbuild/netbsd-x64@0.23.1': + resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} + engines: {node: '>=18'} cpu: [x64] - os: [openbsd] + os: [netbsd] - '@esbuild/openbsd-x64@0.19.12': - resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} - engines: {node: '>=12'} - cpu: [x64] + '@esbuild/openbsd-arm64@0.23.1': + resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} + engines: {node: '>=18'} + cpu: [arm64] os: [openbsd] '@esbuild/openbsd-x64@0.21.5': @@ -505,17 +383,11 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/sunos-x64@0.18.20': - resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} - engines: {node: '>=12'} + '@esbuild/openbsd-x64@0.23.1': + resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} + engines: {node: '>=18'} cpu: [x64] - os: [sunos] - - '@esbuild/sunos-x64@0.19.12': - resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] + os: [openbsd] '@esbuild/sunos-x64@0.21.5': resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} @@ -523,17 +395,11 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.18.20': - resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-arm64@0.19.12': - resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] + '@esbuild/sunos-x64@0.23.1': + resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] '@esbuild/win32-arm64@0.21.5': resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} @@ -541,16 +407,10 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.18.20': - resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-ia32@0.19.12': - resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} - engines: {node: '>=12'} - cpu: [ia32] + '@esbuild/win32-arm64@0.23.1': + resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} + engines: {node: '>=18'} + cpu: [arm64] os: [win32] '@esbuild/win32-ia32@0.21.5': @@ -559,16 +419,10 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.18.20': - resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - - '@esbuild/win32-x64@0.19.12': - resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} - engines: {node: '>=12'} - cpu: [x64] + '@esbuild/win32-ia32@0.23.1': + resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} + engines: {node: '>=18'} + cpu: [ia32] os: [win32] '@esbuild/win32-x64@0.21.5': @@ -577,6 +431,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.23.1': + resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.4.1': resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1013,22 +873,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - - better-sqlite3@11.7.2: - resolution: {integrity: sha512-10a57cHVDmfNQS4jrZ9AH2t+2ekzYh5Rhbcnb4ytpmYweoLdogDmyTt5D+hLiY9b44Mx9foowb/4iXBTO2yP3Q==} - binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} - bindings@1.5.0: - resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} - - bl@4.1.0: - resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -1044,11 +892,13 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - - buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + c12@2.0.1: + resolution: {integrity: sha512-Z4JgsKXHG37C6PYUtIxCfLJZvo6FyhHJoClwwb9ftUkLpPSkuYqn6Tr+vnaN8hymm0kIbcg6Ey3kv/Q71k5w/A==} + peerDependencies: + magicast: ^0.3.5 + peerDependenciesMeta: + magicast: + optional: true callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} @@ -1073,8 +923,12 @@ packages: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} - chownr@1.1.4: - resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + + citty@0.1.6: + resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} @@ -1104,6 +958,10 @@ packages: confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + consola@3.4.0: + resolution: {integrity: sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA==} + engines: {node: ^14.18.0 || >=16.10.0} + cookie@0.6.0: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} @@ -1130,14 +988,6 @@ packages: supports-color: optional: true - decompress-response@6.0.0: - resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} - engines: {node: '>=10'} - - deep-extend@0.6.0: - resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} - engines: {node: '>=4.0.0'} - deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -1145,13 +995,15 @@ packages: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} - detect-libc@2.0.3: - resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} - engines: {node: '>=8'} + destr@2.0.3: + resolution: {integrity: sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==} devalue@5.1.1: resolution: {integrity: sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==} @@ -1165,98 +1017,9 @@ packages: dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} - drizzle-kit@0.22.8: - resolution: {integrity: sha512-VjI4wsJjk3hSqHSa3TwBf+uvH6M6pRHyxyoVbt935GUzP9tUR/BRZ+MhEJNgryqbzN2Za1KP0eJMTgKEPsalYQ==} - hasBin: true - - drizzle-orm@0.33.0: - resolution: {integrity: sha512-SHy72R2Rdkz0LEq0PSG/IdvnT3nGiWuRk+2tXZQ90GVq/XQhpCzu/EFT3V2rox+w8MlkBQxifF8pCStNYnERfA==} - peerDependencies: - '@aws-sdk/client-rds-data': '>=3' - '@cloudflare/workers-types': '>=3' - '@electric-sql/pglite': '>=0.1.1' - '@libsql/client': '*' - '@neondatabase/serverless': '>=0.1' - '@op-engineering/op-sqlite': '>=2' - '@opentelemetry/api': ^1.4.1 - '@planetscale/database': '>=1' - '@prisma/client': '*' - '@tidbcloud/serverless': '*' - '@types/better-sqlite3': '*' - '@types/pg': '*' - '@types/react': '>=18' - '@types/sql.js': '*' - '@vercel/postgres': '>=0.8.0' - '@xata.io/client': '*' - better-sqlite3: '>=7' - bun-types: '*' - expo-sqlite: '>=13.2.0' - knex: '*' - kysely: '*' - mysql2: '>=2' - pg: '>=8' - postgres: '>=3' - prisma: '*' - react: '>=18' - sql.js: '>=1' - sqlite3: '>=5' - peerDependenciesMeta: - '@aws-sdk/client-rds-data': - optional: true - '@cloudflare/workers-types': - optional: true - '@electric-sql/pglite': - optional: true - '@libsql/client': - optional: true - '@neondatabase/serverless': - optional: true - '@op-engineering/op-sqlite': - optional: true - '@opentelemetry/api': - optional: true - '@planetscale/database': - optional: true - '@prisma/client': - optional: true - '@tidbcloud/serverless': - optional: true - '@types/better-sqlite3': - optional: true - '@types/pg': - optional: true - '@types/react': - optional: true - '@types/sql.js': - optional: true - '@vercel/postgres': - optional: true - '@xata.io/client': - optional: true - better-sqlite3: - optional: true - bun-types: - optional: true - expo-sqlite: - optional: true - knex: - optional: true - kysely: - optional: true - mysql2: - optional: true - pg: - optional: true - postgres: - optional: true - prisma: - optional: true - react: - optional: true - sql.js: - optional: true - sqlite3: - optional: true + dotenv@16.4.7: + resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} + engines: {node: '>=12'} eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -1270,29 +1033,16 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - end-of-stream@1.4.4: - resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} - - esbuild-register@3.6.0: - resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==} - peerDependencies: - esbuild: '>=0.12 <1' - - esbuild@0.18.20: - resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} - engines: {node: '>=12'} - hasBin: true - - esbuild@0.19.12: - resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} - engines: {node: '>=12'} - hasBin: true - esbuild@0.21.5: resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} engines: {node: '>=12'} hasBin: true + esbuild@0.23.1: + resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -1388,13 +1138,13 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + exifreader@4.26.0: resolution: {integrity: sha512-nNN9B0oaXTOpArdnIdJBAro2Sa620m7wMjMA5Xy1rcua0EYHVjzGKM5syBOWDqIG2Qay6Pes/5FOdj65hvZ9Vw==} - expand-template@2.0.3: - resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} - engines: {node: '>=6'} - fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1426,9 +1176,6 @@ packages: file-saver@2.0.5: resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==} - file-uri-to-path@1.0.0: - resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} - fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -1464,8 +1211,9 @@ packages: fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} - fs-constants@1.0.0: - resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} @@ -1475,11 +1223,16 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + get-tsconfig@4.8.1: resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} - github-from-package@0.0.0: - resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + giget@1.2.3: + resolution: {integrity: sha512-8EHPljDvs7qKykr6uw8b+lqLiUc/vUg+KVTI0uND4s63TdsZM2Xus3mflvF0DDG9SiM4RlCkFGL+7aAjRmV7KA==} + hasBin: true glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} @@ -1521,8 +1274,9 @@ packages: heic2any@0.0.4: resolution: {integrity: sha512-3lLnZiDELfabVH87htnRolZ2iehX9zwpRyGNz22GKXIu0fznlblf0/ftppXKNqS26dqFSeqfIBhAmAj/uSp0cA==} - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} @@ -1539,12 +1293,6 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - ini@1.3.8: - resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} @@ -1578,6 +1326,10 @@ packages: is-reference@3.0.3: resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} + is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -1588,6 +1340,10 @@ packages: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true + jiti@2.4.2: + resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} + hasBin: true + js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true @@ -1614,6 +1370,17 @@ packages: kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + kysely-ctl@0.10.1: + resolution: {integrity: sha512-tB2mGV/MbfaQC6Lo582Rs2OdtfX23ueWDscCSDT42Iy8pYHYbhMy9ncXU35ee8LQz4BO2apQihyY8rDProP+9w==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + kysely: '>=0.18.1 <0.28.0' + kysely-postgres-js: ^2 + peerDependenciesMeta: + kysely-postgres-js: + optional: true + kysely@0.27.5: resolution: {integrity: sha512-s7hZHcQeSNKpzCkHRm8yA+0JPLjncSWnjb+2TIElwS2JAqYr+Kv3Ess+9KFfJS0C1xcQ1i9NkNHpWwCYpHMWsA==} engines: {node: '>=14.0.0'} @@ -1660,6 +1427,9 @@ packages: magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -1681,9 +1451,9 @@ packages: engines: {node: '>=16'} hasBin: true - mimic-response@3.1.0: - resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} - engines: {node: '>=10'} + mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -1692,15 +1462,26 @@ packages: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - mkdirp-classic@0.5.3: - resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true mlly@1.7.3: resolution: {integrity: sha512-xUsx5n/mN0uQf4V548PKQ+YShA4/IW0KI1dZhrNrPCLG+xizETbHTkOa1f8/xut9JRPp8kQuMnz0oqwkTiLo/A==} @@ -1724,20 +1505,16 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - napi-build-utils@1.0.2: - resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} - natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - node-abi@3.71.0: - resolution: {integrity: sha512-SZ40vRiy/+wRTf21hxkkEjPJZpARzUMVcJoQse2EF8qkUWbbO2z7vd5oA/H6bVH6SZQ5STGcu0KRDS7biNRfxw==} - engines: {node: '>=10'} - node-addon-api@8.3.0: resolution: {integrity: sha512-8VOpLHFrOQlAH+qA0ZzuGRlALRA6/LVh8QJldbrC4DY0hXoMP0l4Acq8TzFC018HztWiRqyCEj2aTWY2UvnJUg==} engines: {node: ^18 || ^20 || >= 21} + node-fetch-native@1.6.4: + resolution: {integrity: sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==} + node-gyp-build@4.8.4: resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} hasBin: true @@ -1757,6 +1534,20 @@ packages: resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} engines: {node: '>=0.10.0'} + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + nypm@0.3.12: + resolution: {integrity: sha512-D3pzNDWIvgA+7IORhD/IuWzEk4uXv6GsgOxiid4UU3h9oq5IqV1KtPDi63n4sZJ/xcWlr88c0QM2RgN5VbOhFA==} + engines: {node: ^14.16.0 || >=16.10.0} + hasBin: true + + nypm@0.4.1: + resolution: {integrity: sha512-1b9mihliBh8UCcKtcGRu//G50iHpjxIQVUqkdhPT/SDVE7KdJKoHXLS0heuYTQCx95dFqiyUbXZB9r8ikn+93g==} + engines: {node: ^14.16.0 || >=16.10.0} + hasBin: true + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -1768,8 +1559,15 @@ packages: obuf@1.1.2: resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + ofetch@1.4.1: + resolution: {integrity: sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==} + + ohash@1.1.4: + resolution: {integrity: sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g==} + + onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} @@ -1805,6 +1603,10 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} @@ -1815,6 +1617,9 @@ packages: pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + pg-cloudflare@1.1.1: resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} @@ -1979,11 +1784,6 @@ packages: postgres-range@1.1.4: resolution: {integrity: sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==} - prebuild-install@7.1.2: - resolution: {integrity: sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==} - engines: {node: '>=10'} - hasBin: true - prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -2057,9 +1857,6 @@ packages: proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - pump@3.0.2: - resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} - punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -2067,17 +1864,12 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - rc@1.2.8: - resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} - hasBin: true + rc9@2.1.2: + resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -2114,9 +1906,6 @@ packages: resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} engines: {node: '>=6'} - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - semver@7.6.3: resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} engines: {node: '>=10'} @@ -2137,12 +1926,6 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - simple-concat@1.0.1: - resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} - - simple-get@4.0.1: - resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} - sirv@3.0.0: resolution: {integrity: sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==} engines: {node: '>=18'} @@ -2154,17 +1937,13 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} - source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - - source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - split2@4.2.0: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} + std-env@3.8.0: + resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -2173,9 +1952,6 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -2184,9 +1960,9 @@ packages: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} - strip-json-comments@2.0.1: - resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} - engines: {node: '>=0.10.0'} + strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} @@ -2231,12 +2007,9 @@ packages: engines: {node: '>=14.0.0'} hasBin: true - tar-fs@2.1.1: - resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} - - tar-stream@2.2.0: - resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} - engines: {node: '>=6'} + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} @@ -2268,8 +2041,10 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - tunnel-agent@0.6.0: - resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + tsx@4.19.2: + resolution: {integrity: sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==} + engines: {node: '>=18.0.0'} + hasBin: true type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} @@ -2395,13 +2170,13 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} @@ -2446,230 +2221,157 @@ snapshots: '@antfu/utils@0.7.10': {} - '@esbuild-kit/core-utils@3.3.2': - dependencies: - esbuild: 0.18.20 - source-map-support: 0.5.21 - - '@esbuild-kit/esm-loader@2.6.5': - dependencies: - '@esbuild-kit/core-utils': 3.3.2 - get-tsconfig: 4.8.1 - - '@esbuild/aix-ppc64@0.19.12': - optional: true - '@esbuild/aix-ppc64@0.21.5': optional: true - '@esbuild/android-arm64@0.18.20': - optional: true - - '@esbuild/android-arm64@0.19.12': + '@esbuild/aix-ppc64@0.23.1': optional: true '@esbuild/android-arm64@0.21.5': optional: true - '@esbuild/android-arm@0.18.20': - optional: true - - '@esbuild/android-arm@0.19.12': + '@esbuild/android-arm64@0.23.1': optional: true '@esbuild/android-arm@0.21.5': optional: true - '@esbuild/android-x64@0.18.20': - optional: true - - '@esbuild/android-x64@0.19.12': + '@esbuild/android-arm@0.23.1': optional: true '@esbuild/android-x64@0.21.5': optional: true - '@esbuild/darwin-arm64@0.18.20': - optional: true - - '@esbuild/darwin-arm64@0.19.12': + '@esbuild/android-x64@0.23.1': optional: true '@esbuild/darwin-arm64@0.21.5': optional: true - '@esbuild/darwin-x64@0.18.20': - optional: true - - '@esbuild/darwin-x64@0.19.12': + '@esbuild/darwin-arm64@0.23.1': optional: true '@esbuild/darwin-x64@0.21.5': optional: true - '@esbuild/freebsd-arm64@0.18.20': - optional: true - - '@esbuild/freebsd-arm64@0.19.12': + '@esbuild/darwin-x64@0.23.1': optional: true '@esbuild/freebsd-arm64@0.21.5': optional: true - '@esbuild/freebsd-x64@0.18.20': - optional: true - - '@esbuild/freebsd-x64@0.19.12': + '@esbuild/freebsd-arm64@0.23.1': optional: true '@esbuild/freebsd-x64@0.21.5': optional: true - '@esbuild/linux-arm64@0.18.20': - optional: true - - '@esbuild/linux-arm64@0.19.12': + '@esbuild/freebsd-x64@0.23.1': optional: true '@esbuild/linux-arm64@0.21.5': optional: true - '@esbuild/linux-arm@0.18.20': - optional: true - - '@esbuild/linux-arm@0.19.12': + '@esbuild/linux-arm64@0.23.1': optional: true '@esbuild/linux-arm@0.21.5': optional: true - '@esbuild/linux-ia32@0.18.20': - optional: true - - '@esbuild/linux-ia32@0.19.12': + '@esbuild/linux-arm@0.23.1': optional: true '@esbuild/linux-ia32@0.21.5': optional: true - '@esbuild/linux-loong64@0.18.20': - optional: true - - '@esbuild/linux-loong64@0.19.12': + '@esbuild/linux-ia32@0.23.1': optional: true '@esbuild/linux-loong64@0.21.5': optional: true - '@esbuild/linux-mips64el@0.18.20': - optional: true - - '@esbuild/linux-mips64el@0.19.12': + '@esbuild/linux-loong64@0.23.1': optional: true '@esbuild/linux-mips64el@0.21.5': optional: true - '@esbuild/linux-ppc64@0.18.20': - optional: true - - '@esbuild/linux-ppc64@0.19.12': + '@esbuild/linux-mips64el@0.23.1': optional: true '@esbuild/linux-ppc64@0.21.5': optional: true - '@esbuild/linux-riscv64@0.18.20': - optional: true - - '@esbuild/linux-riscv64@0.19.12': + '@esbuild/linux-ppc64@0.23.1': optional: true '@esbuild/linux-riscv64@0.21.5': optional: true - '@esbuild/linux-s390x@0.18.20': - optional: true - - '@esbuild/linux-s390x@0.19.12': + '@esbuild/linux-riscv64@0.23.1': optional: true '@esbuild/linux-s390x@0.21.5': optional: true - '@esbuild/linux-x64@0.18.20': - optional: true - - '@esbuild/linux-x64@0.19.12': + '@esbuild/linux-s390x@0.23.1': optional: true '@esbuild/linux-x64@0.21.5': optional: true - '@esbuild/netbsd-x64@0.18.20': - optional: true - - '@esbuild/netbsd-x64@0.19.12': + '@esbuild/linux-x64@0.23.1': optional: true '@esbuild/netbsd-x64@0.21.5': optional: true - '@esbuild/openbsd-x64@0.18.20': + '@esbuild/netbsd-x64@0.23.1': optional: true - '@esbuild/openbsd-x64@0.19.12': + '@esbuild/openbsd-arm64@0.23.1': optional: true '@esbuild/openbsd-x64@0.21.5': optional: true - '@esbuild/sunos-x64@0.18.20': - optional: true - - '@esbuild/sunos-x64@0.19.12': + '@esbuild/openbsd-x64@0.23.1': optional: true '@esbuild/sunos-x64@0.21.5': optional: true - '@esbuild/win32-arm64@0.18.20': - optional: true - - '@esbuild/win32-arm64@0.19.12': + '@esbuild/sunos-x64@0.23.1': optional: true '@esbuild/win32-arm64@0.21.5': optional: true - '@esbuild/win32-ia32@0.18.20': - optional: true - - '@esbuild/win32-ia32@0.19.12': + '@esbuild/win32-arm64@0.23.1': optional: true '@esbuild/win32-ia32@0.21.5': optional: true - '@esbuild/win32-x64@0.18.20': - optional: true - - '@esbuild/win32-x64@0.19.12': + '@esbuild/win32-ia32@0.23.1': optional: true '@esbuild/win32-x64@0.21.5': optional: true - '@eslint-community/eslint-utils@4.4.1(eslint@9.17.0(jiti@1.21.7))': + '@esbuild/win32-x64@0.23.1': + optional: true + + '@eslint-community/eslint-utils@4.4.1(eslint@9.17.0(jiti@2.4.2))': dependencies: - eslint: 9.17.0(jiti@1.21.7) + eslint: 9.17.0(jiti@2.4.2) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} - '@eslint/compat@1.2.4(eslint@9.17.0(jiti@1.21.7))': + '@eslint/compat@1.2.4(eslint@9.17.0(jiti@2.4.2))': optionalDependencies: - eslint: 9.17.0(jiti@1.21.7) + eslint: 9.17.0(jiti@2.4.2) '@eslint/config-array@0.19.1': dependencies: @@ -2955,15 +2657,15 @@ snapshots: '@types/resolve@1.20.2': {} - '@typescript-eslint/eslint-plugin@8.19.1(@typescript-eslint/parser@8.19.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3))(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3)': + '@typescript-eslint/eslint-plugin@8.19.1(@typescript-eslint/parser@8.19.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.19.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3) + '@typescript-eslint/parser': 8.19.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3) '@typescript-eslint/scope-manager': 8.19.1 - '@typescript-eslint/type-utils': 8.19.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3) - '@typescript-eslint/utils': 8.19.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3) + '@typescript-eslint/type-utils': 8.19.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3) + '@typescript-eslint/utils': 8.19.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3) '@typescript-eslint/visitor-keys': 8.19.1 - eslint: 9.17.0(jiti@1.21.7) + eslint: 9.17.0(jiti@2.4.2) graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 @@ -2972,14 +2674,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.19.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3)': + '@typescript-eslint/parser@8.19.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3)': dependencies: '@typescript-eslint/scope-manager': 8.19.1 '@typescript-eslint/types': 8.19.1 '@typescript-eslint/typescript-estree': 8.19.1(typescript@5.7.3) '@typescript-eslint/visitor-keys': 8.19.1 debug: 4.4.0 - eslint: 9.17.0(jiti@1.21.7) + eslint: 9.17.0(jiti@2.4.2) typescript: 5.7.3 transitivePeerDependencies: - supports-color @@ -2989,12 +2691,12 @@ snapshots: '@typescript-eslint/types': 8.19.1 '@typescript-eslint/visitor-keys': 8.19.1 - '@typescript-eslint/type-utils@8.19.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3)': + '@typescript-eslint/type-utils@8.19.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3)': dependencies: '@typescript-eslint/typescript-estree': 8.19.1(typescript@5.7.3) - '@typescript-eslint/utils': 8.19.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3) + '@typescript-eslint/utils': 8.19.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3) debug: 4.4.0 - eslint: 9.17.0(jiti@1.21.7) + eslint: 9.17.0(jiti@2.4.2) ts-api-utils: 2.0.0(typescript@5.7.3) typescript: 5.7.3 transitivePeerDependencies: @@ -3016,13 +2718,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.19.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3)': + '@typescript-eslint/utils@8.19.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3)': dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@2.4.2)) '@typescript-eslint/scope-manager': 8.19.1 '@typescript-eslint/types': 8.19.1 '@typescript-eslint/typescript-estree': 8.19.1(typescript@5.7.3) - eslint: 9.17.0(jiti@1.21.7) + eslint: 9.17.0(jiti@2.4.2) typescript: 5.7.3 transitivePeerDependencies: - supports-color @@ -3105,25 +2807,8 @@ snapshots: balanced-match@1.0.2: {} - base64-js@1.5.1: {} - - better-sqlite3@11.7.2: - dependencies: - bindings: 1.5.0 - prebuild-install: 7.1.2 - binary-extensions@2.3.0: {} - bindings@1.5.0: - dependencies: - file-uri-to-path: 1.0.0 - - bl@4.1.0: - dependencies: - buffer: 5.7.1 - inherits: 2.0.4 - readable-stream: 3.6.2 - brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 @@ -3144,12 +2829,20 @@ snapshots: node-releases: 2.0.19 update-browserslist-db: 1.1.2(browserslist@4.24.4) - buffer-from@1.1.2: {} - - buffer@5.7.1: + c12@2.0.1: dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 + chokidar: 4.0.3 + confbox: 0.1.8 + defu: 6.1.4 + dotenv: 16.4.7 + giget: 1.2.3 + jiti: 2.4.2 + mlly: 1.7.3 + ohash: 1.1.4 + pathe: 1.1.2 + perfect-debounce: 1.0.0 + pkg-types: 1.3.0 + rc9: 2.1.2 callsites@3.1.0: {} @@ -3178,7 +2871,11 @@ snapshots: dependencies: readdirp: 4.0.2 - chownr@1.1.4: {} + chownr@2.0.0: {} + + citty@0.1.6: + dependencies: + consola: 3.4.0 clsx@2.1.1: {} @@ -3200,6 +2897,8 @@ snapshots: confbox@0.1.8: {} + consola@3.4.0: {} + cookie@0.6.0: {} cron-parser@4.9.0: @@ -3218,19 +2917,15 @@ snapshots: dependencies: ms: 2.1.3 - decompress-response@6.0.0: - dependencies: - mimic-response: 3.1.0 - - deep-extend@0.6.0: {} - deep-is@0.1.4: {} deepmerge@4.3.1: {} + defu@6.1.4: {} + delayed-stream@1.0.0: {} - detect-libc@2.0.3: {} + destr@2.0.3: {} devalue@5.1.1: {} @@ -3240,21 +2935,7 @@ snapshots: dlv@1.1.3: {} - drizzle-kit@0.22.8: - dependencies: - '@esbuild-kit/esm-loader': 2.6.5 - esbuild: 0.19.12 - esbuild-register: 3.6.0(esbuild@0.19.12) - transitivePeerDependencies: - - supports-color - - drizzle-orm@0.33.0(@types/better-sqlite3@7.6.12)(@types/pg@8.11.10)(better-sqlite3@11.7.2)(kysely@0.27.5)(pg@8.13.1): - optionalDependencies: - '@types/better-sqlite3': 7.6.12 - '@types/pg': 8.11.10 - better-sqlite3: 11.7.2 - kysely: 0.27.5 - pg: 8.13.1 + dotenv@16.4.7: {} eastasianwidth@0.2.0: {} @@ -3264,68 +2945,6 @@ snapshots: emoji-regex@9.2.2: {} - end-of-stream@1.4.4: - dependencies: - once: 1.4.0 - - esbuild-register@3.6.0(esbuild@0.19.12): - dependencies: - debug: 4.4.0 - esbuild: 0.19.12 - transitivePeerDependencies: - - supports-color - - esbuild@0.18.20: - optionalDependencies: - '@esbuild/android-arm': 0.18.20 - '@esbuild/android-arm64': 0.18.20 - '@esbuild/android-x64': 0.18.20 - '@esbuild/darwin-arm64': 0.18.20 - '@esbuild/darwin-x64': 0.18.20 - '@esbuild/freebsd-arm64': 0.18.20 - '@esbuild/freebsd-x64': 0.18.20 - '@esbuild/linux-arm': 0.18.20 - '@esbuild/linux-arm64': 0.18.20 - '@esbuild/linux-ia32': 0.18.20 - '@esbuild/linux-loong64': 0.18.20 - '@esbuild/linux-mips64el': 0.18.20 - '@esbuild/linux-ppc64': 0.18.20 - '@esbuild/linux-riscv64': 0.18.20 - '@esbuild/linux-s390x': 0.18.20 - '@esbuild/linux-x64': 0.18.20 - '@esbuild/netbsd-x64': 0.18.20 - '@esbuild/openbsd-x64': 0.18.20 - '@esbuild/sunos-x64': 0.18.20 - '@esbuild/win32-arm64': 0.18.20 - '@esbuild/win32-ia32': 0.18.20 - '@esbuild/win32-x64': 0.18.20 - - esbuild@0.19.12: - optionalDependencies: - '@esbuild/aix-ppc64': 0.19.12 - '@esbuild/android-arm': 0.19.12 - '@esbuild/android-arm64': 0.19.12 - '@esbuild/android-x64': 0.19.12 - '@esbuild/darwin-arm64': 0.19.12 - '@esbuild/darwin-x64': 0.19.12 - '@esbuild/freebsd-arm64': 0.19.12 - '@esbuild/freebsd-x64': 0.19.12 - '@esbuild/linux-arm': 0.19.12 - '@esbuild/linux-arm64': 0.19.12 - '@esbuild/linux-ia32': 0.19.12 - '@esbuild/linux-loong64': 0.19.12 - '@esbuild/linux-mips64el': 0.19.12 - '@esbuild/linux-ppc64': 0.19.12 - '@esbuild/linux-riscv64': 0.19.12 - '@esbuild/linux-s390x': 0.19.12 - '@esbuild/linux-x64': 0.19.12 - '@esbuild/netbsd-x64': 0.19.12 - '@esbuild/openbsd-x64': 0.19.12 - '@esbuild/sunos-x64': 0.19.12 - '@esbuild/win32-arm64': 0.19.12 - '@esbuild/win32-ia32': 0.19.12 - '@esbuild/win32-x64': 0.19.12 - esbuild@0.21.5: optionalDependencies: '@esbuild/aix-ppc64': 0.21.5 @@ -3352,25 +2971,52 @@ snapshots: '@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-x64': 0.21.5 + esbuild@0.23.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.23.1 + '@esbuild/android-arm': 0.23.1 + '@esbuild/android-arm64': 0.23.1 + '@esbuild/android-x64': 0.23.1 + '@esbuild/darwin-arm64': 0.23.1 + '@esbuild/darwin-x64': 0.23.1 + '@esbuild/freebsd-arm64': 0.23.1 + '@esbuild/freebsd-x64': 0.23.1 + '@esbuild/linux-arm': 0.23.1 + '@esbuild/linux-arm64': 0.23.1 + '@esbuild/linux-ia32': 0.23.1 + '@esbuild/linux-loong64': 0.23.1 + '@esbuild/linux-mips64el': 0.23.1 + '@esbuild/linux-ppc64': 0.23.1 + '@esbuild/linux-riscv64': 0.23.1 + '@esbuild/linux-s390x': 0.23.1 + '@esbuild/linux-x64': 0.23.1 + '@esbuild/netbsd-x64': 0.23.1 + '@esbuild/openbsd-arm64': 0.23.1 + '@esbuild/openbsd-x64': 0.23.1 + '@esbuild/sunos-x64': 0.23.1 + '@esbuild/win32-arm64': 0.23.1 + '@esbuild/win32-ia32': 0.23.1 + '@esbuild/win32-x64': 0.23.1 + escalade@3.2.0: {} escape-string-regexp@4.0.0: {} - eslint-compat-utils@0.5.1(eslint@9.17.0(jiti@1.21.7)): + eslint-compat-utils@0.5.1(eslint@9.17.0(jiti@2.4.2)): dependencies: - eslint: 9.17.0(jiti@1.21.7) + eslint: 9.17.0(jiti@2.4.2) semver: 7.6.3 - eslint-config-prettier@9.1.0(eslint@9.17.0(jiti@1.21.7)): + eslint-config-prettier@9.1.0(eslint@9.17.0(jiti@2.4.2)): dependencies: - eslint: 9.17.0(jiti@1.21.7) + eslint: 9.17.0(jiti@2.4.2) - eslint-plugin-svelte@2.46.1(eslint@9.17.0(jiti@1.21.7))(svelte@5.17.1): + eslint-plugin-svelte@2.46.1(eslint@9.17.0(jiti@2.4.2))(svelte@5.17.1): dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@2.4.2)) '@jridgewell/sourcemap-codec': 1.5.0 - eslint: 9.17.0(jiti@1.21.7) - eslint-compat-utils: 0.5.1(eslint@9.17.0(jiti@1.21.7)) + eslint: 9.17.0(jiti@2.4.2) + eslint-compat-utils: 0.5.1(eslint@9.17.0(jiti@2.4.2)) esutils: 2.0.3 known-css-properties: 0.35.0 postcss: 8.4.49 @@ -3404,9 +3050,9 @@ snapshots: eslint-visitor-keys@4.2.0: {} - eslint@9.17.0(jiti@1.21.7): + eslint@9.17.0(jiti@2.4.2): dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@2.4.2)) '@eslint-community/regexpp': 4.12.1 '@eslint/config-array': 0.19.1 '@eslint/core': 0.9.1 @@ -3441,7 +3087,7 @@ snapshots: natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: - jiti: 1.21.7 + jiti: 2.4.2 transitivePeerDependencies: - supports-color @@ -3477,12 +3123,22 @@ snapshots: esutils@2.0.3: {} + execa@8.0.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + exifreader@4.26.0: optionalDependencies: '@xmldom/xmldom': 0.9.6 - expand-template@2.0.3: {} - fast-deep-equal@3.1.3: {} fast-glob@3.3.3: @@ -3511,8 +3167,6 @@ snapshots: file-saver@2.0.5: {} - file-uri-to-path@1.0.0: {} - fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -3544,18 +3198,31 @@ snapshots: fraction.js@4.3.7: {} - fs-constants@1.0.0: {} + fs-minipass@2.1.0: + dependencies: + minipass: 3.3.6 fsevents@2.3.3: optional: true function-bind@1.1.2: {} + get-stream@8.0.1: {} + get-tsconfig@4.8.1: dependencies: resolve-pkg-maps: 1.0.0 - github-from-package@0.0.0: {} + giget@1.2.3: + dependencies: + citty: 0.1.6 + consola: 3.4.0 + defu: 6.1.4 + node-fetch-native: 1.6.4 + nypm: 0.3.12 + ohash: 1.1.4 + pathe: 1.1.2 + tar: 6.2.1 glob-parent@5.1.2: dependencies: @@ -3592,7 +3259,7 @@ snapshots: heic2any@0.0.4: {} - ieee754@1.2.1: {} + human-signals@5.0.0: {} ignore@5.3.2: {} @@ -3605,10 +3272,6 @@ snapshots: imurmurhash@0.1.4: {} - inherits@2.0.4: {} - - ini@1.3.8: {} - is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 @@ -3637,6 +3300,8 @@ snapshots: dependencies: '@types/estree': 1.0.6 + is-stream@3.0.0: {} + isexe@2.0.0: {} jackspeak@3.4.3: @@ -3647,6 +3312,8 @@ snapshots: jiti@1.21.7: {} + jiti@2.4.2: {} + js-yaml@4.1.0: dependencies: argparse: 2.0.1 @@ -3667,6 +3334,21 @@ snapshots: kolorist@1.8.0: {} + kysely-ctl@0.10.1(kysely@0.27.5): + dependencies: + c12: 2.0.1 + citty: 0.1.6 + consola: 3.4.0 + kysely: 0.27.5 + nypm: 0.4.1 + ofetch: 1.4.1 + pathe: 1.1.2 + pkg-types: 1.3.0 + std-env: 3.8.0 + tsx: 4.19.2 + transitivePeerDependencies: + - magicast + kysely@0.27.5: {} levn@0.4.1: @@ -3703,6 +3385,8 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + merge-stream@2.0.0: {} + merge2@1.4.1: {} micromatch@4.0.8: @@ -3718,7 +3402,7 @@ snapshots: mime@4.0.6: {} - mimic-response@3.1.0: {} + mimic-fn@4.0.0: {} minimatch@3.1.2: dependencies: @@ -3728,11 +3412,20 @@ snapshots: dependencies: brace-expansion: 2.0.1 - minimist@1.2.8: {} + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + + minipass@5.0.0: {} minipass@7.1.2: {} - mkdirp-classic@0.5.3: {} + minizlib@2.1.2: + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + + mkdirp@1.0.4: {} mlly@1.7.3: dependencies: @@ -3755,16 +3448,12 @@ snapshots: nanoid@3.3.8: {} - napi-build-utils@1.0.2: {} - natural-compare@1.4.0: {} - node-abi@3.71.0: - dependencies: - semver: 7.6.3 - node-addon-api@8.3.0: {} + node-fetch-native@1.6.4: {} + node-gyp-build@4.8.4: {} node-releases@2.0.19: {} @@ -3779,15 +3468,45 @@ snapshots: normalize-range@0.1.2: {} + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + + nypm@0.3.12: + dependencies: + citty: 0.1.6 + consola: 3.4.0 + execa: 8.0.1 + pathe: 1.1.2 + pkg-types: 1.3.0 + ufo: 1.5.4 + + nypm@0.4.1: + dependencies: + citty: 0.1.6 + consola: 3.4.0 + pathe: 1.1.2 + pkg-types: 1.3.0 + tinyexec: 0.3.2 + ufo: 1.5.4 + object-assign@4.1.1: {} object-hash@3.0.0: {} obuf@1.1.2: {} - once@1.4.0: + ofetch@1.4.1: dependencies: - wrappy: 1.0.2 + destr: 2.0.3 + node-fetch-native: 1.6.4 + ufo: 1.5.4 + + ohash@1.1.4: {} + + onetime@6.0.0: + dependencies: + mimic-fn: 4.0.0 optionator@0.9.4: dependencies: @@ -3822,6 +3541,8 @@ snapshots: path-key@3.1.1: {} + path-key@4.0.0: {} + path-parse@1.0.7: {} path-scurry@1.11.1: @@ -3831,6 +3552,8 @@ snapshots: pathe@1.1.2: {} + perfect-debounce@1.0.0: {} + pg-cloudflare@1.1.1: optional: true @@ -3968,21 +3691,6 @@ snapshots: postgres-range@1.1.4: {} - prebuild-install@7.1.2: - dependencies: - detect-libc: 2.0.3 - expand-template: 2.0.3 - github-from-package: 0.0.0 - minimist: 1.2.8 - mkdirp-classic: 0.5.3 - napi-build-utils: 1.0.2 - node-abi: 3.71.0 - pump: 3.0.2 - rc: 1.2.8 - simple-get: 4.0.1 - tar-fs: 2.1.1 - tunnel-agent: 0.6.0 - prelude-ls@1.2.1: {} prettier-plugin-svelte@3.3.2(prettier@3.4.2)(svelte@5.17.1): @@ -4000,32 +3708,19 @@ snapshots: proxy-from-env@1.1.0: {} - pump@3.0.2: - dependencies: - end-of-stream: 1.4.4 - once: 1.4.0 - punycode@2.3.1: {} queue-microtask@1.2.3: {} - rc@1.2.8: + rc9@2.1.2: dependencies: - deep-extend: 0.6.0 - ini: 1.3.8 - minimist: 1.2.8 - strip-json-comments: 2.0.1 + defu: 6.1.4 + destr: 2.0.3 read-cache@1.0.0: dependencies: pify: 2.3.0 - readable-stream@3.6.2: - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - readdirp@3.6.0: dependencies: picomatch: 2.3.1 @@ -4077,8 +3772,6 @@ snapshots: dependencies: mri: 1.2.0 - safe-buffer@5.2.1: {} - semver@7.6.3: {} set-cookie-parser@2.7.1: {} @@ -4091,14 +3784,6 @@ snapshots: signal-exit@4.1.0: {} - simple-concat@1.0.1: {} - - simple-get@4.0.1: - dependencies: - decompress-response: 6.0.0 - once: 1.4.0 - simple-concat: 1.0.1 - sirv@3.0.0: dependencies: '@polka/url': 1.0.0-next.28 @@ -4109,15 +3794,10 @@ snapshots: source-map-js@1.2.1: {} - source-map-support@0.5.21: - dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 - - source-map@0.6.1: {} - split2@4.2.0: {} + std-env@3.8.0: {} + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -4130,10 +3810,6 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.0 - string_decoder@1.3.0: - dependencies: - safe-buffer: 5.2.1 - strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -4142,7 +3818,7 @@ snapshots: dependencies: ansi-regex: 6.1.0 - strip-json-comments@2.0.1: {} + strip-final-newline@3.0.0: {} strip-json-comments@3.1.1: {} @@ -4228,20 +3904,14 @@ snapshots: transitivePeerDependencies: - ts-node - tar-fs@2.1.1: + tar@6.2.1: dependencies: - chownr: 1.1.4 - mkdirp-classic: 0.5.3 - pump: 3.0.2 - tar-stream: 2.2.0 - - tar-stream@2.2.0: - dependencies: - bl: 4.1.0 - end-of-stream: 1.4.4 - fs-constants: 1.0.0 - inherits: 2.0.4 - readable-stream: 3.6.2 + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 thenify-all@1.6.0: dependencies: @@ -4270,20 +3940,23 @@ snapshots: ts-interface-checker@0.1.13: {} - tunnel-agent@0.6.0: + tsx@4.19.2: dependencies: - safe-buffer: 5.2.1 + esbuild: 0.23.1 + get-tsconfig: 4.8.1 + optionalDependencies: + fsevents: 2.3.3 type-check@0.4.0: dependencies: prelude-ls: 1.2.1 - typescript-eslint@8.19.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3): + typescript-eslint@8.19.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.19.1(@typescript-eslint/parser@8.19.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3))(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3) - '@typescript-eslint/parser': 8.19.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3) - '@typescript-eslint/utils': 8.19.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3) - eslint: 9.17.0(jiti@1.21.7) + '@typescript-eslint/eslint-plugin': 8.19.1(@typescript-eslint/parser@8.19.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3) + '@typescript-eslint/parser': 8.19.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3) + '@typescript-eslint/utils': 8.19.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3) + eslint: 9.17.0(jiti@2.4.2) typescript: 5.7.3 transitivePeerDependencies: - supports-color @@ -4360,10 +4033,10 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.0 - wrappy@1.0.2: {} - xtend@4.0.2: {} + yallist@4.0.0: {} + yaml@1.10.2: {} yaml@2.7.0: {} diff --git a/src/lib/server/db/drizzle.ts b/src/lib/server/db/drizzle.ts deleted file mode 100644 index 589c91e..0000000 --- a/src/lib/server/db/drizzle.ts +++ /dev/null @@ -1,15 +0,0 @@ -import Database from "better-sqlite3"; -import { drizzle } from "drizzle-orm/better-sqlite3"; -import { migrate } from "drizzle-orm/better-sqlite3/migrator"; -import env from "$lib/server/loadenv"; - -const client = new Database(env.databaseUrl); -const db = drizzle(client); - -export const migrateDB = () => { - if (process.env.NODE_ENV === "production") { - migrate(db, { migrationsFolder: "./drizzle" }); - } -}; - -export default db; diff --git a/src/lib/server/db/migrations/1737357000-Initial.ts b/src/lib/server/db/migrations/1737357000-Initial.ts new file mode 100644 index 0000000..9dd987c --- /dev/null +++ b/src/lib/server/db/migrations/1737357000-Initial.ts @@ -0,0 +1,222 @@ +import { Kysely } from "kysely"; + +export const up = async (db: Kysely) => { + // user.ts + await db.schema + .createTable("user") + .addColumn("id", "integer", (col) => col.primaryKey().generatedAlwaysAsIdentity()) + .addColumn("email", "text", (col) => col.unique().notNull()) + .addColumn("nickname", "text", (col) => col.notNull()) + .addColumn("password", "text", (col) => col.notNull()) + .execute(); + + // client.ts + await db.schema + .createTable("client") + .addColumn("id", "integer", (col) => col.primaryKey().generatedAlwaysAsIdentity()) + .addColumn("encryption_public_key", "text", (col) => col.unique().notNull()) + .addColumn("signature_public_key", "text", (col) => col.unique().notNull()) + .addUniqueConstraint("client_ak01", ["encryption_public_key", "signature_public_key"]) + .execute(); + await db.schema + .createTable("user_client") + .addColumn("user_id", "integer", (col) => col.references("user.id").notNull()) + .addColumn("client_id", "integer", (col) => col.references("client.id").notNull()) + .addColumn("state", "text", (col) => col.notNull().defaultTo("challenging")) + .addPrimaryKeyConstraint("user_client_pk", ["user_id", "client_id"]) + .execute(); + await db.schema + .createTable("user_client_challenge") + .addColumn("id", "integer", (col) => col.primaryKey().generatedAlwaysAsIdentity()) + .addColumn("user_id", "integer", (col) => col.references("user.id").notNull()) + .addColumn("client_id", "integer", (col) => col.references("client.id").notNull()) + .addColumn("answer", "text", (col) => col.unique().notNull()) + .addColumn("allowed_ip", "text", (col) => col.notNull()) + .addColumn("expires_at", "timestamp(3)", (col) => col.notNull()) + .addForeignKeyConstraint( + "user_client_challenge_fk01", + ["user_id", "client_id"], + "user_client", + ["user_id", "client_id"], + ) + .execute(); + + // session.ts + await db.schema + .createTable("session") + .addColumn("id", "text", (col) => col.primaryKey()) + .addColumn("user_id", "integer", (col) => col.references("user.id").notNull()) + .addColumn("client_id", "integer", (col) => col.references("client.id")) + .addColumn("created_at", "timestamp(3)", (col) => col.notNull()) + .addColumn("last_used_at", "timestamp(3)", (col) => col.notNull()) + .addColumn("last_used_by_ip", "text") + .addColumn("last_used_by_agent", "text") + .addUniqueConstraint("session_ak01", ["user_id", "client_id"]) + .execute(); + await db.schema + .createTable("session_upgrade_challenge") + .addColumn("id", "integer", (col) => col.primaryKey().generatedAlwaysAsIdentity()) + .addColumn("session_id", "text", (col) => col.references("session.id").unique().notNull()) + .addColumn("client_id", "integer", (col) => col.references("client.id").notNull()) + .addColumn("answer", "text", (col) => col.unique().notNull()) + .addColumn("allowed_ip", "text", (col) => col.notNull()) + .addColumn("expires_at", "timestamp(3)", (col) => col.notNull()) + .execute(); + + // mek.ts + await db.schema + .createTable("master_encryption_key") + .addColumn("user_id", "integer", (col) => col.references("user.id").notNull()) + .addColumn("version", "integer", (col) => col.notNull()) + .addColumn("state", "text", (col) => col.notNull()) + .addPrimaryKeyConstraint("master_encryption_key_pk", ["user_id", "version"]) + .execute(); + await db.schema + .createTable("master_encryption_key_log") + .addColumn("id", "integer", (col) => col.primaryKey().generatedAlwaysAsIdentity()) + .addColumn("user_id", "integer", (col) => col.references("user.id").notNull()) + .addColumn("master_encryption_key_version", "integer", (col) => col.notNull()) + .addColumn("timestamp", "timestamp(3)", (col) => col.notNull()) + .addColumn("action", "text", (col) => col.notNull()) + .addColumn("action_by", "integer", (col) => col.references("client.id")) + .addForeignKeyConstraint( + "master_encryption_key_log_fk01", + ["user_id", "master_encryption_key_version"], + "master_encryption_key", + ["user_id", "version"], + ) + .execute(); + await db.schema + .createTable("client_master_encryption_key") + .addColumn("user_id", "integer", (col) => col.references("user.id").notNull()) + .addColumn("client_id", "integer", (col) => col.references("client.id").notNull()) + .addColumn("version", "integer", (col) => col.notNull()) + .addColumn("encrypted_key", "text", (col) => col.notNull()) + .addColumn("encrypted_key_signature", "text", (col) => col.notNull()) + .addPrimaryKeyConstraint("client_master_encryption_key_pk", ["user_id", "client_id", "version"]) + .addForeignKeyConstraint( + "client_master_encryption_key_fk01", + ["user_id", "version"], + "master_encryption_key", + ["user_id", "version"], + ) + .execute(); + + // hsk.ts + await db.schema + .createTable("hmac_secret_key") + .addColumn("user_id", "integer", (col) => col.references("user.id").notNull()) + .addColumn("version", "integer", (col) => col.notNull()) + .addColumn("state", "text", (col) => col.notNull()) + .addColumn("master_encryption_key_version", "integer", (col) => col.notNull()) + .addColumn("encrypted_key", "text", (col) => col.unique().notNull()) + .addPrimaryKeyConstraint("hmac_secret_key_pk", ["user_id", "version"]) + .addForeignKeyConstraint( + "hmac_secret_key_fk01", + ["user_id", "master_encryption_key_version"], + "master_encryption_key", + ["user_id", "version"], + ) + .execute(); + await db.schema + .createTable("hmac_secret_key_log") + .addColumn("id", "integer", (col) => col.primaryKey().generatedAlwaysAsIdentity()) + .addColumn("user_id", "integer", (col) => col.references("user.id").notNull()) + .addColumn("hmac_secret_key_version", "integer", (col) => col.notNull()) + .addColumn("timestamp", "timestamp(3)", (col) => col.notNull()) + .addColumn("action", "text", (col) => col.notNull()) + .addColumn("action_by", "integer", (col) => col.references("client.id")) + .addForeignKeyConstraint( + "hmac_secret_key_log_fk01", + ["user_id", "hmac_secret_key_version"], + "hmac_secret_key", + ["user_id", "version"], + ) + .execute(); + + // file.ts + await db.schema + .createTable("directory") + .addColumn("id", "integer", (col) => col.primaryKey().generatedAlwaysAsIdentity()) + .addColumn("parent_id", "integer", (col) => col.references("directory.id")) + .addColumn("user_id", "integer", (col) => col.references("user.id").notNull()) + .addColumn("master_encryption_key_version", "integer", (col) => col.notNull()) + .addColumn("encrypted_data_encryption_key", "text", (col) => col.unique().notNull()) + .addColumn("data_encryption_key_version", "timestamp(3)", (col) => col.notNull()) + .addColumn("encrypted_name", "json", (col) => col.notNull()) + .addForeignKeyConstraint( + "directory_fk01", + ["user_id", "master_encryption_key_version"], + "master_encryption_key", + ["user_id", "version"], + ) + .execute(); + await db.schema + .createTable("directory_log") + .addColumn("id", "integer", (col) => col.primaryKey().generatedAlwaysAsIdentity()) + .addColumn("directory_id", "integer", (col) => + col.references("directory.id").onDelete("cascade").notNull(), + ) + .addColumn("timestamp", "timestamp(3)", (col) => col.notNull()) + .addColumn("action", "text", (col) => col.notNull()) + .addColumn("new_name", "json") + .execute(); + await db.schema + .createTable("file") + .addColumn("id", "integer", (col) => col.primaryKey().generatedAlwaysAsIdentity()) + .addColumn("parent_id", "integer", (col) => col.references("directory.id")) + .addColumn("user_id", "integer", (col) => col.references("user.id").notNull()) + .addColumn("path", "text", (col) => col.unique().notNull()) + .addColumn("master_encryption_key_version", "integer", (col) => col.notNull()) + .addColumn("encrypted_data_encryption_key", "text", (col) => col.unique().notNull()) + .addColumn("data_encryption_key_version", "timestamp(3)", (col) => col.notNull()) + .addColumn("hmac_secret_key_version", "integer") + .addColumn("content_hmac", "text") + .addColumn("content_type", "text", (col) => col.notNull()) + .addColumn("encrypted_content_iv", "text", (col) => col.notNull()) + .addColumn("encrypted_content_hash", "text", (col) => col.notNull()) + .addColumn("encrypted_name", "json", (col) => col.notNull()) + .addColumn("encrypted_created_at", "json") + .addColumn("encrypted_last_modified_at", "json", (col) => col.notNull()) + .addForeignKeyConstraint( + "file_fk01", + ["user_id", "master_encryption_key_version"], + "master_encryption_key", + ["user_id", "version"], + ) + .addForeignKeyConstraint( + "file_fk02", + ["user_id", "hmac_secret_key_version"], + "hmac_secret_key", + ["user_id", "version"], + ) + .execute(); + await db.schema + .createTable("file_log") + .addColumn("id", "integer", (col) => col.primaryKey().generatedAlwaysAsIdentity()) + .addColumn("file_id", "integer", (col) => + col.references("file.id").onDelete("cascade").notNull(), + ) + .addColumn("timestamp", "timestamp(3)", (col) => col.notNull()) + .addColumn("action", "text", (col) => col.notNull()) + .addColumn("new_name", "json") + .execute(); +}; + +export const down = async (db: Kysely) => { + await db.schema.dropTable("file_log").execute(); + await db.schema.dropTable("file").execute(); + await db.schema.dropTable("directory_log").execute(); + await db.schema.dropTable("directory").execute(); + await db.schema.dropTable("hmac_secret_key_log").execute(); + await db.schema.dropTable("hmac_secret_key").execute(); + await db.schema.dropTable("client_master_encryption_key").execute(); + await db.schema.dropTable("master_encryption_key_log").execute(); + await db.schema.dropTable("master_encryption_key").execute(); + await db.schema.dropTable("session_upgrade_challenge").execute(); + await db.schema.dropTable("session").execute(); + await db.schema.dropTable("user_client_challenge").execute(); + await db.schema.dropTable("user_client").execute(); + await db.schema.dropTable("client").execute(); + await db.schema.dropTable("user").execute(); +}; diff --git a/src/lib/server/db/schema/client.ts b/src/lib/server/db/schema/client.ts index e5642a6..d66e42b 100644 --- a/src/lib/server/db/schema/client.ts +++ b/src/lib/server/db/schema/client.ts @@ -1,65 +1,4 @@ -import { - sqliteTable, - text, - integer, - primaryKey, - foreignKey, - unique, -} from "drizzle-orm/sqlite-core"; import type { ColumnType, Generated } from "kysely"; -import { user } from "./user"; - -export const client = sqliteTable( - "client", - { - id: integer("id").primaryKey({ autoIncrement: true }), - encPubKey: text("encryption_public_key").notNull().unique(), // Base64 - sigPubKey: text("signature_public_key").notNull().unique(), // Base64 - }, - (t) => ({ - unq: unique().on(t.encPubKey, t.sigPubKey), - }), -); - -export const userClient = sqliteTable( - "user_client", - { - userId: integer("user_id") - .notNull() - .references(() => user.id), - clientId: integer("client_id") - .notNull() - .references(() => client.id), - state: text("state", { enum: ["challenging", "pending", "active"] }) - .notNull() - .default("challenging"), - }, - (t) => ({ - pk: primaryKey({ columns: [t.userId, t.clientId] }), - }), -); - -export const userClientChallenge = sqliteTable( - "user_client_challenge", - { - id: integer("id").primaryKey(), - userId: integer("user_id") - .notNull() - .references(() => user.id), - clientId: integer("client_id") - .notNull() - .references(() => client.id), - answer: text("answer").notNull().unique(), // Base64 - allowedIp: text("allowed_ip").notNull(), - expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(), - }, - (t) => ({ - ref: foreignKey({ - columns: [t.userId, t.clientId], - foreignColumns: [userClient.userId, userClient.clientId], - }), - }), -); interface ClientTable { id: Generated; diff --git a/src/lib/server/db/schema/file.ts b/src/lib/server/db/schema/file.ts index 60e3f22..6810057 100644 --- a/src/lib/server/db/schema/file.ts +++ b/src/lib/server/db/schema/file.ts @@ -1,92 +1,4 @@ -import { sqliteTable, text, integer, foreignKey } from "drizzle-orm/sqlite-core"; import type { ColumnType, Generated } from "kysely"; -import { hsk } from "./hsk"; -import { mek } from "./mek"; -import { user } from "./user"; - -const ciphertext = (name: string) => - text(name, { mode: "json" }).$type<{ - ciphertext: string; // Base64 - iv: string; // Base64 - }>(); - -export const directory = sqliteTable( - "directory", - { - id: integer("id").primaryKey({ autoIncrement: true }), - parentId: integer("parent_id"), - userId: integer("user_id") - .notNull() - .references(() => user.id), - mekVersion: integer("master_encryption_key_version").notNull(), - encDek: text("encrypted_data_encryption_key").notNull().unique(), // Base64 - dekVersion: integer("data_encryption_key_version", { mode: "timestamp_ms" }).notNull(), - encName: ciphertext("encrypted_name").notNull(), - }, - (t) => ({ - ref1: foreignKey({ - columns: [t.parentId], - foreignColumns: [t.id], - }), - ref2: foreignKey({ - columns: [t.userId, t.mekVersion], - foreignColumns: [mek.userId, mek.version], - }), - }), -); - -export const directoryLog = sqliteTable("directory_log", { - id: integer("id").primaryKey({ autoIncrement: true }), - directoryId: integer("directory_id") - .notNull() - .references(() => directory.id, { onDelete: "cascade" }), - timestamp: integer("timestamp", { mode: "timestamp_ms" }).notNull(), - action: text("action", { enum: ["create", "rename"] }).notNull(), - newName: ciphertext("new_name"), -}); - -export const file = sqliteTable( - "file", - { - id: integer("id").primaryKey({ autoIncrement: true }), - parentId: integer("parent_id").references(() => directory.id), - userId: integer("user_id") - .notNull() - .references(() => user.id), - path: text("path").notNull().unique(), - mekVersion: integer("master_encryption_key_version").notNull(), - encDek: text("encrypted_data_encryption_key").notNull().unique(), // Base64 - dekVersion: integer("data_encryption_key_version", { mode: "timestamp_ms" }).notNull(), - hskVersion: integer("hmac_secret_key_version"), - contentHmac: text("content_hmac"), // Base64 - contentType: text("content_type").notNull(), - encContentIv: text("encrypted_content_iv").notNull(), // Base64 - encContentHash: text("encrypted_content_hash").notNull(), // Base64 - encName: ciphertext("encrypted_name").notNull(), - encCreatedAt: ciphertext("encrypted_created_at"), - encLastModifiedAt: ciphertext("encrypted_last_modified_at").notNull(), - }, - (t) => ({ - ref1: foreignKey({ - columns: [t.userId, t.mekVersion], - foreignColumns: [mek.userId, mek.version], - }), - ref2: foreignKey({ - columns: [t.userId, t.hskVersion], - foreignColumns: [hsk.userId, hsk.version], - }), - }), -); - -export const fileLog = sqliteTable("file_log", { - id: integer("id").primaryKey({ autoIncrement: true }), - fileId: integer("file_id") - .notNull() - .references(() => file.id, { onDelete: "cascade" }), - timestamp: integer("timestamp", { mode: "timestamp_ms" }).notNull(), - action: text("action", { enum: ["create", "rename"] }).notNull(), - newName: ciphertext("new_name"), -}); export type Ciphertext = { ciphertext: string; // Base64 diff --git a/src/lib/server/db/schema/hsk.ts b/src/lib/server/db/schema/hsk.ts index aca5193..71457b0 100644 --- a/src/lib/server/db/schema/hsk.ts +++ b/src/lib/server/db/schema/hsk.ts @@ -1,48 +1,4 @@ -import { sqliteTable, text, integer, primaryKey, foreignKey } from "drizzle-orm/sqlite-core"; import type { ColumnType, Generated } from "kysely"; -import { client } from "./client"; -import { mek } from "./mek"; -import { user } from "./user"; - -export const hsk = sqliteTable( - "hmac_secret_key", - { - userId: integer("user_id") - .notNull() - .references(() => user.id), - version: integer("version").notNull(), - state: text("state", { enum: ["active"] }).notNull(), - mekVersion: integer("master_encryption_key_version").notNull(), - encHsk: text("encrypted_key").notNull().unique(), // Base64 - }, - (t) => ({ - pk: primaryKey({ columns: [t.userId, t.version] }), - ref: foreignKey({ - columns: [t.userId, t.mekVersion], - foreignColumns: [mek.userId, mek.version], - }), - }), -); - -export const hskLog = sqliteTable( - "hmac_secret_key_log", - { - id: integer("id").primaryKey({ autoIncrement: true }), - userId: integer("user_id") - .notNull() - .references(() => user.id), - hskVersion: integer("hmac_secret_key_version").notNull(), - timestamp: integer("timestamp", { mode: "timestamp_ms" }).notNull(), - action: text("action", { enum: ["create"] }).notNull(), - actionBy: integer("action_by").references(() => client.id), - }, - (t) => ({ - ref: foreignKey({ - columns: [t.userId, t.hskVersion], - foreignColumns: [hsk.userId, hsk.version], - }), - }), -); export type HskState = "active"; diff --git a/src/lib/server/db/schema/mek.ts b/src/lib/server/db/schema/mek.ts index ae454c1..d1b3c76 100644 --- a/src/lib/server/db/schema/mek.ts +++ b/src/lib/server/db/schema/mek.ts @@ -1,64 +1,4 @@ -import { sqliteTable, text, integer, primaryKey, foreignKey } from "drizzle-orm/sqlite-core"; import type { ColumnType, Generated } from "kysely"; -import { client } from "./client"; -import { user } from "./user"; - -export const mek = sqliteTable( - "master_encryption_key", - { - userId: integer("user_id") - .notNull() - .references(() => user.id), - version: integer("version").notNull(), - state: text("state", { enum: ["active", "retired", "dead"] }).notNull(), - retiredAt: integer("retired_at", { mode: "timestamp_ms" }), - }, - (t) => ({ - pk: primaryKey({ columns: [t.userId, t.version] }), - }), -); - -export const mekLog = sqliteTable( - "master_encryption_key_log", - { - id: integer("id").primaryKey({ autoIncrement: true }), - userId: integer("user_id") - .notNull() - .references(() => user.id), - mekVersion: integer("master_encryption_key_version").notNull(), - timestamp: integer("timestamp", { mode: "timestamp_ms" }).notNull(), - action: text("action", { enum: ["create"] }).notNull(), - actionBy: integer("action_by").references(() => client.id), - }, - (t) => ({ - ref: foreignKey({ - columns: [t.userId, t.mekVersion], - foreignColumns: [mek.userId, mek.version], - }), - }), -); - -export const clientMek = sqliteTable( - "client_master_encryption_key", - { - userId: integer("user_id") - .notNull() - .references(() => user.id), - clientId: integer("client_id") - .notNull() - .references(() => client.id), - mekVersion: integer("version").notNull(), - encMek: text("encrypted_key").notNull(), // Base64 - encMekSig: text("encrypted_key_signature").notNull(), // Base64 - }, - (t) => ({ - pk: primaryKey({ columns: [t.userId, t.clientId, t.mekVersion] }), - ref: foreignKey({ - columns: [t.userId, t.mekVersion], - foreignColumns: [mek.userId, mek.version], - }), - }), -); export type MekState = "active" | "retired" | "dead"; diff --git a/src/lib/server/db/schema/session.ts b/src/lib/server/db/schema/session.ts index d74f099..301a879 100644 --- a/src/lib/server/db/schema/session.ts +++ b/src/lib/server/db/schema/session.ts @@ -1,39 +1,4 @@ -import { sqliteTable, text, integer, unique } from "drizzle-orm/sqlite-core"; import type { ColumnType, Generated } from "kysely"; -import { client } from "./client"; -import { user } from "./user"; - -export const session = sqliteTable( - "session", - { - id: text("id").notNull().primaryKey(), - userId: integer("user_id") - .notNull() - .references(() => user.id), - clientId: integer("client_id").references(() => client.id), - createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(), - lastUsedAt: integer("last_used_at", { mode: "timestamp_ms" }).notNull(), - lastUsedByIp: text("last_used_by_ip"), - lastUsedByUserAgent: text("last_used_by_user_agent"), - }, - (t) => ({ - unq: unique().on(t.userId, t.clientId), - }), -); - -export const sessionUpgradeChallenge = sqliteTable("session_upgrade_challenge", { - id: integer("id").primaryKey(), - sessionId: text("session_id") - .notNull() - .references(() => session.id) - .unique(), - clientId: integer("client_id") - .notNull() - .references(() => client.id), - answer: text("answer").notNull().unique(), // Base64 - allowedIp: text("allowed_ip").notNull(), - expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(), -}); interface SessionTable { id: string; diff --git a/src/lib/server/db/schema/user.ts b/src/lib/server/db/schema/user.ts index 7a34988..a5f111f 100644 --- a/src/lib/server/db/schema/user.ts +++ b/src/lib/server/db/schema/user.ts @@ -1,13 +1,5 @@ -import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core"; import type { Generated } from "kysely"; -export const user = sqliteTable("user", { - id: integer("id").primaryKey({ autoIncrement: true }), - email: text("email").notNull().unique(), - password: text("password").notNull(), - nickname: text("nickname").notNull(), -}); - interface UserTable { id: Generated; email: string; diff --git a/src/lib/server/loadenv.ts b/src/lib/server/loadenv.ts index 0ebb110..baffdb0 100644 --- a/src/lib/server/loadenv.ts +++ b/src/lib/server/loadenv.ts @@ -9,8 +9,8 @@ if (!building) { export default { database: { - host: env.DATABASE_HOST || "localhost", - port: parseInt(env.DATABASE_PORT || "5432", 10), + host: env.DATABASE_HOST, + port: env.DATABASE_PORT ? parseInt(env.DATABASE_PORT, 10) : undefined, user: env.DATABASE_USER, password: env.DATABASE_PASSWORD!, name: env.DATABASE_NAME, From 803110606bde7eacda1db4c8103a8bdef96ae1df Mon Sep 17 00:00:00 2001 From: static Date: Mon, 20 Jan 2025 19:15:15 +0900 Subject: [PATCH 12/53] =?UTF-8?q?Production=20=ED=99=98=EA=B2=BD=EC=97=90?= =?UTF-8?q?=EC=84=9C=EC=9D=98=20DB=20=EC=9E=90=EB=8F=99=20Migration=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 15 ++++++------ docker-compose.yaml | 1 + src/hooks.server.ts | 6 ++--- src/lib/server/db/client.ts | 4 +-- src/lib/server/db/hsk.ts | 4 +-- src/lib/server/db/kysely.ts | 35 ++++++++++++++++++++++++--- src/lib/server/db/mek.ts | 4 +-- src/lib/server/db/migrations/index.ts | 5 ++++ src/lib/server/db/session.ts | 6 ++--- src/lib/server/loadenv.ts | 1 + 10 files changed, 58 insertions(+), 23 deletions(-) create mode 100644 src/lib/server/db/migrations/index.ts diff --git a/Dockerfile b/Dockerfile index eec42f6..f809fbc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,10 @@ FROM node:22-alpine AS base WORKDIR /app +RUN apk add --no-cache bash curl && \ + curl -o /usr/local/bin/wait-for-it https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh && \ + chmod +x /usr/local/bin/wait-for-it + RUN npm install -g pnpm@9 COPY pnpm-lock.yaml . @@ -10,10 +14,9 @@ FROM base AS build RUN pnpm fetch COPY . . -RUN pnpm install --offline -RUN pnpm build - -RUN sed -i "s/http\.createServer()/http.createServer({ requestTimeout: 0 })/g" ./build/index.js +RUN pnpm install --offline && \ + pnpm build && \ + sed -i "s/http\.createServer()/http.createServer({ requestTimeout: 0 })/g" ./build/index.js # Deploy Stage FROM base @@ -23,9 +26,7 @@ COPY package.json . RUN pnpm install --offline --prod COPY --from=build /app/build ./build -COPY drizzle ./drizzle EXPOSE 3000 ENV BODY_SIZE_LIMIT=Infinity - -CMD ["node", "./build/index.js"] +CMD ["bash", "-c", "wait-for-it ${DATABASE_HOST:-localhost}:${DATABASE_PORT:-5432} -- node ./build/index.js"] diff --git a/docker-compose.yaml b/docker-compose.yaml index b14f0df..dc7f392 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -27,6 +27,7 @@ services: database: image: postgres:17.2-alpine restart: on-failure + user: ${CONTAINER_UID:-0}:${CONTAINER_GID:-0} volumes: - ./data/database:/var/lib/postgresql/data environment: diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 9dac88c..6f94a7e 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -2,15 +2,15 @@ import type { ServerInit } from "@sveltejs/kit"; import { sequence } from "@sveltejs/kit/hooks"; import schedule from "node-schedule"; import { cleanupExpiredUserClientChallenges } from "$lib/server/db/client"; -import { migrateDB } from "$lib/server/db/drizzle"; +import { migrateDB } from "$lib/server/db/kysely"; import { cleanupExpiredSessions, cleanupExpiredSessionUpgradeChallenges, } from "$lib/server/db/session"; import { authenticate, setAgentInfo } from "$lib/server/middlewares"; -export const init: ServerInit = () => { - migrateDB(); +export const init: ServerInit = async () => { + await migrateDB(); schedule.scheduleJob("0 * * * *", () => { cleanupExpiredUserClientChallenges(); diff --git a/src/lib/server/db/client.ts b/src/lib/server/db/client.ts index 4fd23f3..08f2c98 100644 --- a/src/lib/server/db/client.ts +++ b/src/lib/server/db/client.ts @@ -1,4 +1,4 @@ -import { DatabaseError } from "pg"; +import pg from "pg"; import { IntegrityError } from "./error"; import db from "./kysely"; import type { UserClientState } from "./schema"; @@ -91,7 +91,7 @@ export const createUserClient = async (userId: number, clientId: number) => { try { await db.insertInto("user_client").values({ user_id: userId, client_id: clientId }).execute(); } catch (e) { - if (e instanceof DatabaseError && e.code === "23505") { + if (e instanceof pg.DatabaseError && e.code === "23505") { throw new IntegrityError("User client already exists"); } throw e; diff --git a/src/lib/server/db/hsk.ts b/src/lib/server/db/hsk.ts index a1bf66b..4673cae 100644 --- a/src/lib/server/db/hsk.ts +++ b/src/lib/server/db/hsk.ts @@ -1,4 +1,4 @@ -import { DatabaseError } from "pg"; +import pg from "pg"; import { IntegrityError } from "./error"; import db from "./kysely"; import type { HskState } from "./schema"; @@ -40,7 +40,7 @@ export const registerInitialHsk = async ( }) .execute(); } catch (e) { - if (e instanceof DatabaseError && e.code === "23505") { + if (e instanceof pg.DatabaseError && e.code === "23505") { throw new IntegrityError("HSK already registered"); } throw e; diff --git a/src/lib/server/db/kysely.ts b/src/lib/server/db/kysely.ts index 9665bb3..302049e 100644 --- a/src/lib/server/db/kysely.ts +++ b/src/lib/server/db/kysely.ts @@ -1,10 +1,11 @@ -import { Kysely, PostgresDialect } from "kysely"; -import { Pool } from "pg"; +import { Kysely, PostgresDialect, Migrator } from "kysely"; +import pg from "pg"; import env from "$lib/server/loadenv"; +import migrations from "./migrations"; import type { Database } from "./schema"; const dialect = new PostgresDialect({ - pool: new Pool({ + pool: new pg.Pool({ host: env.database.host, port: env.database.port, user: env.database.user, @@ -15,6 +16,32 @@ const dialect = new PostgresDialect({ const db = new Kysely({ dialect }); -// TODO: Migration +export const migrateDB = async () => { + if (env.nodeEnv !== "production") return; + + const migrator = new Migrator({ + db, + provider: { + async getMigrations() { + return migrations; + }, + }, + }); + const { error, results } = await migrator.migrateToLatest(); + if (error) { + const migration = results?.find(({ status }) => status === "Error"); + if (migration) { + console.error(`Migration "${migration.migrationName}" failed.`); + } + console.error(error); + process.exit(1); + } + + if (results?.length === 0) { + console.log("Database is up-to-date."); + } else { + console.log("Database migration completed."); + } +}; export default db; diff --git a/src/lib/server/db/mek.ts b/src/lib/server/db/mek.ts index 4d57013..ff3c999 100644 --- a/src/lib/server/db/mek.ts +++ b/src/lib/server/db/mek.ts @@ -1,4 +1,4 @@ -import { DatabaseError } from "pg"; +import pg from "pg"; import { IntegrityError } from "./error"; import db from "./kysely"; import type { MekState } from "./schema"; @@ -52,7 +52,7 @@ export const registerInitialMek = async ( }) .execute(); } catch (e) { - if (e instanceof DatabaseError && e.code === "23505") { + if (e instanceof pg.DatabaseError && e.code === "23505") { throw new IntegrityError("MEK already registered"); } throw e; diff --git a/src/lib/server/db/migrations/index.ts b/src/lib/server/db/migrations/index.ts new file mode 100644 index 0000000..6caca84 --- /dev/null +++ b/src/lib/server/db/migrations/index.ts @@ -0,0 +1,5 @@ +import * as Initial1737357000 from "./1737357000-Initial"; + +export default { + "1737357000-Initial": Initial1737357000, +}; diff --git a/src/lib/server/db/session.ts b/src/lib/server/db/session.ts index cd4d558..727f795 100644 --- a/src/lib/server/db/session.ts +++ b/src/lib/server/db/session.ts @@ -1,4 +1,4 @@ -import { DatabaseError } from "pg"; +import pg from "pg"; import env from "$lib/server/loadenv"; import { IntegrityError } from "./error"; import db from "./kysely"; @@ -25,7 +25,7 @@ export const createSession = async ( }) .execute(); } catch (e) { - if (e instanceof DatabaseError && e.code === "23505") { + if (e instanceof pg.DatabaseError && e.code === "23505") { throw new IntegrityError("Session already exists"); } throw e; @@ -105,7 +105,7 @@ export const registerSessionUpgradeChallenge = async ( }) .execute(); } catch (e) { - if (e instanceof DatabaseError && e.code === "23505") { + if (e instanceof pg.DatabaseError && e.code === "23505") { throw new IntegrityError("Challenge already registered"); } throw e; diff --git a/src/lib/server/loadenv.ts b/src/lib/server/loadenv.ts index baffdb0..d6f4675 100644 --- a/src/lib/server/loadenv.ts +++ b/src/lib/server/loadenv.ts @@ -8,6 +8,7 @@ if (!building) { } export default { + nodeEnv: env.NODE_ENV || "development", database: { host: env.DATABASE_HOST, port: env.DATABASE_PORT ? parseInt(env.DATABASE_PORT, 10) : undefined, From eed60bb4a14875e67d8f05980b575706a9200d22 Mon Sep 17 00:00:00 2001 From: static Date: Mon, 20 Jan 2025 19:40:38 +0900 Subject: [PATCH 13/53] =?UTF-8?q?=EC=BB=B4=ED=8C=8C=EC=9D=BC=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EB=93=B1=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/lib/server/db/mek.ts | 4 ++-- src/lib/server/db/migrations/1737357000-Initial.ts | 2 ++ src/lib/server/modules/mek.ts | 2 +- src/lib/server/services/client.ts | 2 +- src/lib/server/services/directory.ts | 10 +++++----- src/lib/server/services/file.ts | 14 +++++++------- src/lib/server/services/mek.ts | 10 +++++----- src/routes/api/directory/[id]/rename/+server.ts | 2 +- src/routes/api/directory/create/+server.ts | 3 +-- src/routes/api/file/[id]/rename/+server.ts | 2 +- src/routes/api/file/upload/+server.ts | 9 +++------ 12 files changed, 30 insertions(+), 32 deletions(-) diff --git a/package.json b/package.json index cfed32a..8185f79 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "dev": "vite dev", - "dev:db": "docker compose -f docker-compose.dev.yaml up -d", + "dev:db": "docker compose -f docker-compose.dev.yaml -p arkvault-dev up -d", "build": "vite build", "preview": "vite preview", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", diff --git a/src/lib/server/db/mek.ts b/src/lib/server/db/mek.ts index ff3c999..d6eecb0 100644 --- a/src/lib/server/db/mek.ts +++ b/src/lib/server/db/mek.ts @@ -82,8 +82,8 @@ export const getAllValidClientMeks = async (userId: number, clientId: number) => .onRef("client_master_encryption_key.version", "=", "master_encryption_key.version"), ) .selectAll() - .where("user_id", "=", userId) - .where("client_id", "=", clientId) + .where("client_master_encryption_key.user_id", "=", userId) + .where("client_master_encryption_key.client_id", "=", clientId) .where((eb) => eb.or([eb("state", "=", "active"), eb("state", "=", "retired")])) .execute(); return clientMeks.map( diff --git a/src/lib/server/db/migrations/1737357000-Initial.ts b/src/lib/server/db/migrations/1737357000-Initial.ts index 9dd987c..5caf503 100644 --- a/src/lib/server/db/migrations/1737357000-Initial.ts +++ b/src/lib/server/db/migrations/1737357000-Initial.ts @@ -1,5 +1,6 @@ import { Kysely } from "kysely"; +// eslint-disable-next-line @typescript-eslint/no-explicit-any export const up = async (db: Kysely) => { // user.ts await db.schema @@ -203,6 +204,7 @@ export const up = async (db: Kysely) => { .execute(); }; +// eslint-disable-next-line @typescript-eslint/no-explicit-any export const down = async (db: Kysely) => { await db.schema.dropTable("file_log").execute(); await db.schema.dropTable("file").execute(); diff --git a/src/lib/server/modules/mek.ts b/src/lib/server/modules/mek.ts index d65ef0a..1605d75 100644 --- a/src/lib/server/modules/mek.ts +++ b/src/lib/server/modules/mek.ts @@ -21,5 +21,5 @@ export const verifyClientEncMekSig = async ( } const data = JSON.stringify({ version, key: encMek }); - return verifySignature(Buffer.from(data), encMekSig, userClient.client.sigPubKey); + return verifySignature(Buffer.from(data), encMekSig, userClient.sigPubKey); }; diff --git a/src/lib/server/services/client.ts b/src/lib/server/services/client.ts index b5b0209..0d0b82d 100644 --- a/src/lib/server/services/client.ts +++ b/src/lib/server/services/client.ts @@ -63,7 +63,7 @@ export const registerUserClient = async ( } try { - const clientId = await createClient(encPubKey, sigPubKey, userId); + const { clientId } = await createClient(encPubKey, sigPubKey, userId); return { challenge: await createUserClientChallenge(ip, userId, clientId, encPubKey) }; } catch (e) { if (e instanceof IntegrityError && e.message === "Public key(s) already registered") { diff --git a/src/lib/server/services/directory.ts b/src/lib/server/services/directory.ts index 4dc14ce..be795b0 100644 --- a/src/lib/server/services/directory.ts +++ b/src/lib/server/services/directory.ts @@ -8,8 +8,9 @@ import { setDirectoryEncName, unregisterDirectory, getAllFilesByParent, - type NewDirectoryParams, + type NewDirectory, } from "$lib/server/db/file"; +import type { Ciphertext } from "$lib/server/db/schema"; export const getDirectoryInformation = async (userId: number, directoryId: "root" | number) => { const directory = directoryId !== "root" ? await getDirectory(userId, directoryId) : undefined; @@ -53,11 +54,10 @@ export const renameDirectory = async ( userId: number, directoryId: number, dekVersion: Date, - newEncName: string, - newEncNameIv: string, + newEncName: Ciphertext, ) => { try { - await setDirectoryEncName(userId, directoryId, dekVersion, newEncName, newEncNameIv); + await setDirectoryEncName(userId, directoryId, dekVersion, newEncName); } catch (e) { if (e instanceof IntegrityError) { if (e.message === "Directory not found") { @@ -70,7 +70,7 @@ export const renameDirectory = async ( } }; -export const createDirectory = async (params: NewDirectoryParams) => { +export const createDirectory = async (params: NewDirectory) => { const oneMinuteAgo = new Date(Date.now() - 60 * 1000); const oneMinuteLater = new Date(Date.now() + 60 * 1000); if (params.dekVersion <= oneMinuteAgo || params.dekVersion >= oneMinuteLater) { diff --git a/src/lib/server/services/file.ts b/src/lib/server/services/file.ts index ea01f16..0f2d371 100644 --- a/src/lib/server/services/file.ts +++ b/src/lib/server/services/file.ts @@ -13,8 +13,9 @@ import { getFile, setFileEncName, unregisterFile, - type NewFileParams, + type NewFile, } from "$lib/server/db/file"; +import type { Ciphertext } from "$lib/server/db/schema"; import env from "$lib/server/loadenv"; export const getFileInformation = async (userId: number, fileId: number) => { @@ -38,8 +39,8 @@ export const getFileInformation = async (userId: number, fileId: number) => { export const deleteFile = async (userId: number, fileId: number) => { try { - const filePath = await unregisterFile(userId, fileId); - unlink(filePath); // Intended + const { path } = await unregisterFile(userId, fileId); + unlink(path); // Intended } catch (e) { if (e instanceof IntegrityError && e.message === "File not found") { error(404, "Invalid file id"); @@ -65,11 +66,10 @@ export const renameFile = async ( userId: number, fileId: number, dekVersion: Date, - newEncName: string, - newEncNameIv: string, + newEncName: Ciphertext, ) => { try { - await setFileEncName(userId, fileId, dekVersion, newEncName, newEncNameIv); + await setFileEncName(userId, fileId, dekVersion, newEncName); } catch (e) { if (e instanceof IntegrityError) { if (e.message === "File not found") { @@ -96,7 +96,7 @@ const safeUnlink = async (path: string) => { }; export const uploadFile = async ( - params: Omit, + params: Omit, encContentStream: Readable, encContentHash: Promise, ) => { diff --git a/src/lib/server/services/mek.ts b/src/lib/server/services/mek.ts index e0deeb0..097906a 100644 --- a/src/lib/server/services/mek.ts +++ b/src/lib/server/services/mek.ts @@ -7,11 +7,11 @@ import { verifyClientEncMekSig } from "$lib/server/modules/mek"; export const getClientMekList = async (userId: number, clientId: number) => { const clientMeks = await getAllValidClientMeks(userId, clientId); return { - encMeks: clientMeks.map((clientMek) => ({ - version: clientMek.master_encryption_key.version, - state: clientMek.master_encryption_key.state as "active" | "retired", - encMek: clientMek.client_master_encryption_key.encMek, - encMekSig: clientMek.client_master_encryption_key.encMekSig, + encMeks: clientMeks.map(({ version, state, encMek, encMekSig }) => ({ + version, + state, + encMek, + encMekSig, })), }; }; diff --git a/src/routes/api/directory/[id]/rename/+server.ts b/src/routes/api/directory/[id]/rename/+server.ts index 0d95e13..cc50b2f 100644 --- a/src/routes/api/directory/[id]/rename/+server.ts +++ b/src/routes/api/directory/[id]/rename/+server.ts @@ -20,6 +20,6 @@ export const POST: RequestHandler = async ({ locals, params, request }) => { if (!bodyZodRes.success) error(400, "Invalid request body"); const { dekVersion, name, nameIv } = bodyZodRes.data; - await renameDirectory(userId, id, new Date(dekVersion), name, nameIv); + await renameDirectory(userId, id, new Date(dekVersion), { ciphertext: name, iv: nameIv }); return text("Directory renamed", { headers: { "Content-Type": "text/plain" } }); }; diff --git a/src/routes/api/directory/create/+server.ts b/src/routes/api/directory/create/+server.ts index 07711fc..7c65436 100644 --- a/src/routes/api/directory/create/+server.ts +++ b/src/routes/api/directory/create/+server.ts @@ -17,8 +17,7 @@ export const POST: RequestHandler = async ({ locals, request }) => { mekVersion, encDek: dek, dekVersion: new Date(dekVersion), - encName: name, - encNameIv: nameIv, + encName: { ciphertext: name, iv: nameIv }, }); return text("Directory created", { headers: { "Content-Type": "text/plain" } }); }; diff --git a/src/routes/api/file/[id]/rename/+server.ts b/src/routes/api/file/[id]/rename/+server.ts index c6748a0..343f146 100644 --- a/src/routes/api/file/[id]/rename/+server.ts +++ b/src/routes/api/file/[id]/rename/+server.ts @@ -20,6 +20,6 @@ export const POST: RequestHandler = async ({ locals, params, request }) => { if (!bodyZodRes.success) error(400, "Invalid request body"); const { dekVersion, name, nameIv } = bodyZodRes.data; - await renameFile(userId, id, new Date(dekVersion), name, nameIv); + await renameFile(userId, id, new Date(dekVersion), { ciphertext: name, iv: nameIv }); return text("File renamed", { headers: { "Content-Type": "text/plain" } }); }; diff --git a/src/routes/api/file/upload/+server.ts b/src/routes/api/file/upload/+server.ts index a69df0c..b54a542 100644 --- a/src/routes/api/file/upload/+server.ts +++ b/src/routes/api/file/upload/+server.ts @@ -40,12 +40,9 @@ const parseFileMetadata = (userId: number, json: string) => { contentHmac, contentType, encContentIv: contentIv, - encName: name, - encNameIv: nameIv, - encCreatedAt: createdAt ?? null, - encCreatedAtIv: createdAtIv ?? null, - encLastModifiedAt: lastModifiedAt, - encLastModifiedAtIv: lastModifiedAtIv, + encName: { ciphertext: name, iv: nameIv }, + encCreatedAt: createdAt && createdAtIv ? { ciphertext: createdAt, iv: createdAtIv } : null, + encLastModifiedAt: { ciphertext: lastModifiedAt, iv: lastModifiedAtIv }, } satisfies FileMetadata; }; From 9419e5e2b40f495d6127a2df949ab61ee614ef12 Mon Sep 17 00:00:00 2001 From: static Date: Mon, 20 Jan 2025 19:51:00 +0900 Subject: [PATCH 14/53] =?UTF-8?q?README.md=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 45f5c5a..ec1918c 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ vim .env # 아래를 참고하여 환경 변수를 설정해 주세요. docker compose up --build -d ``` -모든 데이터는 `./data` 디렉터리에 저장될 거예요. +모든 데이터는 `./data` 디렉터리에 아래에 저장될 거예요. ### Environment Variables @@ -31,7 +31,8 @@ docker compose up --build -d |이름|필수|기본값|설명| |:-|:-:|:-:|:-| -|`SESSION_SECRET`|Y||Session ID의 서명을 위해 사용돼요. 안전한 값으로 설정해 주세요.| +|`DATABASE_PASSWORD`|Y||데이터베이스에 접근하기 위해 필요한 비밀번호예요. 안전한 값으로 설정해 주세요.| +|`SESSION_SECRET`|Y||Session ID의 서명에 사용되는 비밀번호예요. 안전한 값으로 설정해 주세요.| |`SESSION_EXPIRES`||`14d`|Session의 유효 시간이에요. Session은 마지막으로 사용된 후 설정된 유효 시간이 지나면 자동으로 삭제돼요.| |`USER_CLIENT_CHALLENGE_EXPIRES`||`5m`|암호 키를 서버에 처음 등록할 때 사용되는 챌린지의 유효 시간이에요.| |`SESSION_UPGRADE_CHALLENGE_EXPIRES`||`5m`|암호 키와 함께 로그인할 때 사용되는 챌린지의 유효 시간이에요.| From 2a2d01b50e2120d7c2e887ab8288171f07cdba95 Mon Sep 17 00:00:00 2001 From: static Date: Tue, 21 Jan 2025 10:57:32 +0900 Subject: [PATCH 15/53] =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20DB=20=EC=8A=A4=ED=82=A4=EB=A7=88/=EC=BD=94?= =?UTF-8?q?=EB=93=9C=EB=A5=BC=20Kysely=20=EA=B8=B0=EB=B0=98=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/server/db/category.ts | 189 ++++++++++-------- src/lib/server/db/file.ts | 104 ++++++---- .../migrations/1737422340-AddFileCategory.ts | 65 ++++++ src/lib/server/db/migrations/index.ts | 2 + src/lib/server/db/schema/category.ts | 73 +++---- src/lib/server/db/schema/file.ts | 30 +-- src/lib/server/db/schema/index.ts | 1 + src/lib/server/db/schema/util.ts | 4 + 8 files changed, 281 insertions(+), 187 deletions(-) create mode 100644 src/lib/server/db/migrations/1737422340-AddFileCategory.ts create mode 100644 src/lib/server/db/schema/util.ts diff --git a/src/lib/server/db/category.ts b/src/lib/server/db/category.ts index db543ab..fb85322 100644 --- a/src/lib/server/db/category.ts +++ b/src/lib/server/db/category.ts @@ -1,111 +1,144 @@ -import { and, eq, isNull } from "drizzle-orm"; -import db from "./drizzle"; import { IntegrityError } from "./error"; -import { category, categoryLog, mek } from "./schema"; +import db from "./kysely"; +import type { Ciphertext } from "./schema"; type CategoryId = "root" | number; -export interface NewCategoryParams { - parentId: "root" | number; +interface Category { + id: number; + parentId: CategoryId; userId: number; mekVersion: number; encDek: string; dekVersion: Date; - encName: string; - encNameIv: string; + encName: Ciphertext; } -export const registerCategory = async (params: NewCategoryParams) => { - await db.transaction( - async (tx) => { - const meks = await tx - .select({ version: mek.version }) - .from(mek) - .where(and(eq(mek.userId, params.userId), eq(mek.state, "active"))) - .limit(1); - if (meks[0]?.version !== params.mekVersion) { - throw new IntegrityError("Inactive MEK version"); - } +export type NewCategory = Omit; - const newCategories = await tx - .insert(category) - .values({ - parentId: params.parentId === "root" ? null : params.parentId, - userId: params.userId, - mekVersion: params.mekVersion, - encDek: params.encDek, - dekVersion: params.dekVersion, - encName: { ciphertext: params.encName, iv: params.encNameIv }, - }) - .returning({ id: category.id }); - const { id: categoryId } = newCategories[0]!; - await tx.insert(categoryLog).values({ - categoryId, +export const registerCategory = async (params: NewCategory) => { + await db.transaction().execute(async (trx) => { + const mek = await trx + .selectFrom("master_encryption_key") + .select("version") + .where("user_id", "=", params.userId) + .where("state", "=", "active") + .limit(1) + .forUpdate() + .executeTakeFirst(); + if (mek?.version !== params.mekVersion) { + throw new IntegrityError("Inactive MEK version"); + } + + const { categoryId } = await trx + .insertInto("category") + .values({ + parent_id: params.parentId !== "root" ? params.parentId : null, + user_id: params.userId, + master_encryption_key_version: params.mekVersion, + encrypted_data_encryption_key: params.encDek, + data_encryption_key_version: params.dekVersion, + encrypted_name: params.encName, + }) + .returning("id as categoryId") + .executeTakeFirstOrThrow(); + await trx + .insertInto("category_log") + .values({ + category_id: categoryId, timestamp: new Date(), action: "create", - newName: { ciphertext: params.encName, iv: params.encNameIv }, - }); - }, - { behavior: "exclusive" }, - ); + new_name: params.encName, + }) + .execute(); + }); }; export const getAllCategoriesByParent = async (userId: number, parentId: CategoryId) => { - return await db - .select() - .from(category) - .where( - and( - eq(category.userId, userId), - parentId === "root" ? isNull(category.parentId) : eq(category.parentId, parentId), - ), - ); + let query = db.selectFrom("category").selectAll().where("user_id", "=", userId); + query = + parentId === "root" + ? query.where("parent_id", "is", null) + : query.where("parent_id", "=", parentId); + const categories = await query.execute(); + return categories.map( + (category) => + ({ + id: category.id, + parentId: category.parent_id ?? "root", + userId: category.user_id, + mekVersion: category.master_encryption_key_version, + encDek: category.encrypted_data_encryption_key, + dekVersion: category.data_encryption_key_version, + encName: category.encrypted_name, + }) satisfies Category, + ); }; export const getCategory = async (userId: number, categoryId: number) => { - const res = await db - .select() - .from(category) - .where(and(eq(category.userId, userId), eq(category.id, categoryId))) - .limit(1); - return res[0] ?? null; + const category = await db + .selectFrom("category") + .selectAll() + .where("id", "=", categoryId) + .where("user_id", "=", userId) + .limit(1) + .executeTakeFirst(); + return category + ? ({ + id: category.id, + parentId: category.parent_id ?? "root", + userId: category.user_id, + mekVersion: category.master_encryption_key_version, + encDek: category.encrypted_data_encryption_key, + dekVersion: category.data_encryption_key_version, + encName: category.encrypted_name, + } satisfies Category) + : null; }; export const setCategoryEncName = async ( userId: number, categoryId: number, dekVersion: Date, - encName: string, - encNameIv: string, + encName: Ciphertext, ) => { - await db.transaction( - async (tx) => { - const categories = await tx - .select({ version: category.dekVersion }) - .from(category) - .where(and(eq(category.userId, userId), eq(category.id, categoryId))) - .limit(1); - if (!categories[0]) { - throw new IntegrityError("Category not found"); - } else if (categories[0].version.getTime() !== dekVersion.getTime()) { - throw new IntegrityError("Invalid DEK version"); - } + await db.transaction().execute(async (trx) => { + const category = await trx + .selectFrom("category") + .select("data_encryption_key_version") + .where("id", "=", categoryId) + .where("user_id", "=", userId) + .limit(1) + .forUpdate() + .executeTakeFirst(); + if (!category) { + throw new IntegrityError("Category not found"); + } else if (category.data_encryption_key_version.getTime() !== dekVersion.getTime()) { + throw new IntegrityError("Invalid DEK version"); + } - await tx - .update(category) - .set({ encName: { ciphertext: encName, iv: encNameIv } }) - .where(and(eq(category.userId, userId), eq(category.id, categoryId))); - await tx.insert(categoryLog).values({ - categoryId, + await trx + .updateTable("category") + .set({ encrypted_name: encName }) + .where("id", "=", categoryId) + .where("user_id", "=", userId) + .execute(); + await trx + .insertInto("category_log") + .values({ + category_id: categoryId, timestamp: new Date(), action: "rename", - newName: { ciphertext: encName, iv: encNameIv }, - }); - }, - { behavior: "exclusive" }, - ); + new_name: encName, + }) + .execute(); + }); }; export const unregisterCategory = async (userId: number, categoryId: number) => { - await db.delete(category).where(and(eq(category.userId, userId), eq(category.id, categoryId))); + await db + .deleteFrom("category") + .where("id", "=", categoryId) + .where("user_id", "=", userId) + .execute(); }; diff --git a/src/lib/server/db/file.ts b/src/lib/server/db/file.ts index b372557..789c3b3 100644 --- a/src/lib/server/db/file.ts +++ b/src/lib/server/db/file.ts @@ -1,3 +1,4 @@ +import pg from "pg"; import { IntegrityError } from "./error"; import db from "./kysely"; import type { Ciphertext } from "./schema"; @@ -290,11 +291,33 @@ export const getAllFilesByParent = async (userId: number, parentId: DirectoryId) }; export const getAllFilesByCategory = async (userId: number, categoryId: number) => { - return await db - .select() - .from(file) - .innerJoin(fileCategory, eq(file.id, fileCategory.fileId)) - .where(and(eq(file.userId, userId), eq(fileCategory.categoryId, categoryId))); + const files = await db + .selectFrom("file") + .innerJoin("file_category", "file.id", "file_category.file_id") + .selectAll("file") + .where("user_id", "=", userId) + .where("category_id", "=", categoryId) + .execute(); + return files.map( + (file) => + ({ + id: file.id, + parentId: file.parent_id ?? "root", + userId: file.user_id, + path: file.path, + mekVersion: file.master_encryption_key_version, + encDek: file.encrypted_data_encryption_key, + dekVersion: file.data_encryption_key_version, + hskVersion: file.hmac_secret_key_version, + contentHmac: file.content_hmac, + contentType: file.content_type, + encContentIv: file.encrypted_content_iv, + encContentHash: file.encrypted_content_hash, + encName: file.encrypted_name, + encCreatedAt: file.encrypted_created_at, + encLastModifiedAt: file.encrypted_last_modified_at, + }) satisfies File, + ); }; export const getAllFileIdsByContentHmac = async ( @@ -394,44 +417,49 @@ export const unregisterFile = async (userId: number, fileId: number) => { }; export const addFileToCategory = async (fileId: number, categoryId: number) => { - await db.transaction( - async (tx) => { - try { - await tx.insert(fileCategory).values({ fileId, categoryId }); - await tx.insert(fileLog).values({ - fileId, + await db.transaction().execute(async (trx) => { + try { + await trx + .insertInto("file_category") + .values({ file_id: fileId, category_id: categoryId }) + .execute(); + await trx + .insertInto("file_log") + .values({ + file_id: fileId, timestamp: new Date(), - action: "addToCategory", - categoryId, - }); - } catch (e) { - if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_PRIMARYKEY") { - throw new IntegrityError("File already added to category"); - } - throw e; + action: "add-to-category", + category_id: categoryId, + }) + .execute(); + } catch (e) { + if (e instanceof pg.DatabaseError && e.code === "23505") { + throw new IntegrityError("File already added to category"); } - }, - { behavior: "exclusive" }, - ); + throw e; + } + }); }; export const removeFileFromCategory = async (fileId: number, categoryId: number) => { - await db.transaction( - async (tx) => { - const res = await tx - .delete(fileCategory) - .where(and(eq(fileCategory.fileId, fileId), eq(fileCategory.categoryId, categoryId))); - if (res.changes === 0) { - throw new IntegrityError("File not found in category"); - } + await db.transaction().execute(async (trx) => { + const res = await trx + .deleteFrom("file_category") + .where("file_id", "=", fileId) + .where("category_id", "=", categoryId) + .executeTakeFirst(); + if (res.numDeletedRows === 0n) { + throw new IntegrityError("File not found in category"); + } - await tx.insert(fileLog).values({ - fileId, + await trx + .insertInto("file_log") + .values({ + file_id: fileId, timestamp: new Date(), - action: "removeFromCategory", - categoryId, - }); - }, - { behavior: "exclusive" }, - ); + action: "remove-from-category", + category_id: categoryId, + }) + .execute(); + }); }; diff --git a/src/lib/server/db/migrations/1737422340-AddFileCategory.ts b/src/lib/server/db/migrations/1737422340-AddFileCategory.ts new file mode 100644 index 0000000..ff811e1 --- /dev/null +++ b/src/lib/server/db/migrations/1737422340-AddFileCategory.ts @@ -0,0 +1,65 @@ +import { Kysely } from "kysely"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const up = async (db: Kysely) => { + // category.ts + await db.schema + .createTable("category") + .addColumn("id", "integer", (col) => col.primaryKey().generatedAlwaysAsIdentity()) + .addColumn("parent_id", "integer", (col) => col.references("category.id").onDelete("cascade")) + .addColumn("user_id", "integer", (col) => col.references("user.id").notNull()) + .addColumn("master_encryption_key_version", "integer", (col) => col.notNull()) + .addColumn("encrypted_data_encryption_key", "text", (col) => col.unique().notNull()) + .addColumn("data_encryption_key_version", "timestamp(3)", (col) => col.notNull()) + .addColumn("encrypted_name", "json", (col) => col.notNull()) + .addForeignKeyConstraint( + "category_fk01", + ["user_id", "master_encryption_key_version"], + "master_encryption_key", + ["user_id", "version"], + ) + .execute(); + await db.schema + .createTable("category_log") + .addColumn("id", "integer", (col) => col.primaryKey().generatedAlwaysAsIdentity()) + .addColumn("category_id", "integer", (col) => + col.references("category.id").onDelete("cascade").notNull(), + ) + .addColumn("timestamp", "timestamp(3)", (col) => col.notNull()) + .addColumn("action", "text", (col) => col.notNull()) + .addColumn("new_name", "json") + .execute(); + + // file.ts + await db.schema + .alterTable("file_log") + .addColumn("category_id", "integer", (col) => + col.references("category.id").onDelete("set null"), + ) + .execute(); + await db.schema + .createTable("file_category") + .addColumn("file_id", "integer", (col) => + col.references("file.id").onDelete("cascade").notNull(), + ) + .addColumn("category_id", "integer", (col) => + col.references("category.id").onDelete("cascade").notNull(), + ) + .addPrimaryKeyConstraint("file_category_pk", ["file_id", "category_id"]) + .execute(); +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const down = async (db: Kysely) => { + await db + .deleteFrom("file_log") + .where((eb) => + eb.or([eb("action", "=", "add-to-category"), eb("action", "=", "remove-from-category")]), + ) + .execute(); + + await db.schema.dropTable("file_category").execute(); + await db.schema.alterTable("file_log").dropColumn("category_id").execute(); + await db.schema.dropTable("category_log").execute(); + await db.schema.dropTable("category").execute(); +}; diff --git a/src/lib/server/db/migrations/index.ts b/src/lib/server/db/migrations/index.ts index 6caca84..aa6ee13 100644 --- a/src/lib/server/db/migrations/index.ts +++ b/src/lib/server/db/migrations/index.ts @@ -1,5 +1,7 @@ import * as Initial1737357000 from "./1737357000-Initial"; +import * as AddFileCategory1737422340 from "./1737422340-AddFileCategory"; export default { "1737357000-Initial": Initial1737357000, + "1737422340-AddFileCategory": AddFileCategory1737422340, }; diff --git a/src/lib/server/db/schema/category.ts b/src/lib/server/db/schema/category.ts index f8e1621..2304264 100644 --- a/src/lib/server/db/schema/category.ts +++ b/src/lib/server/db/schema/category.ts @@ -1,52 +1,27 @@ -import { - sqliteTable, - text, - integer, - foreignKey, - type AnySQLiteColumn, -} from "drizzle-orm/sqlite-core"; -import { mek } from "./mek"; -import { user } from "./user"; +import type { Generated } from "kysely"; +import type { Ciphertext } from "./util"; -const ciphertext = (name: string) => - text(name, { mode: "json" }).$type<{ - ciphertext: string; // Base64 - iv: string; // Base64 - }>(); +interface CategoryTable { + id: Generated; + parent_id: number | null; + user_id: number; + master_encryption_key_version: number; + encrypted_data_encryption_key: string; // Base64 + data_encryption_key_version: Date; + encrypted_name: Ciphertext; +} -export const category = sqliteTable( - "category", - { - id: integer("id").primaryKey({ autoIncrement: true }), - parentId: integer("parent_id").references((): AnySQLiteColumn => category.id, { - onDelete: "cascade", - }), - userId: integer("user_id") - .notNull() - .references(() => user.id), - mekVersion: integer("master_encryption_key_version").notNull(), - encDek: text("encrypted_data_encryption_key").notNull().unique(), // Base64 - dekVersion: integer("data_encryption_key_version", { mode: "timestamp_ms" }).notNull(), - encName: ciphertext("encrypted_name").notNull(), - }, - (t) => ({ - ref1: foreignKey({ - columns: [t.parentId], - foreignColumns: [t.id], - }), - ref2: foreignKey({ - columns: [t.userId, t.mekVersion], - foreignColumns: [mek.userId, mek.version], - }), - }), -); +interface CategoryLogTable { + id: Generated; + category_id: number; + timestamp: Date; + action: "create" | "rename"; + new_name: Ciphertext | null; +} -export const categoryLog = sqliteTable("category_log", { - id: integer("id").primaryKey({ autoIncrement: true }), - categoryId: integer("category_id") - .notNull() - .references(() => category.id, { onDelete: "cascade" }), - timestamp: integer("timestamp", { mode: "timestamp_ms" }).notNull(), - action: text("action", { enum: ["create", "rename"] }).notNull(), - newName: ciphertext("new_name"), -}); +declare module "./index" { + interface Database { + category: CategoryTable; + category_log: CategoryLogTable; + } +} diff --git a/src/lib/server/db/schema/file.ts b/src/lib/server/db/schema/file.ts index 2e13b4c..a1bf9bd 100644 --- a/src/lib/server/db/schema/file.ts +++ b/src/lib/server/db/schema/file.ts @@ -1,9 +1,5 @@ import type { ColumnType, Generated } from "kysely"; - -export type Ciphertext = { - ciphertext: string; // Base64 - iv: string; // Base64 -}; +import type { Ciphertext } from "./util"; interface DirectoryTable { id: Generated; @@ -45,26 +41,15 @@ interface FileLogTable { id: Generated; file_id: number; timestamp: ColumnType; - action: "create" | "rename"; + action: "create" | "rename" | "add-to-category" | "remove-from-category"; new_name: Ciphertext | null; + category_id: number | null; } -export const fileCategory = sqliteTable( - "file_category", - { - fileId: integer("file_id") - .notNull() - .references(() => file.id, { onDelete: "cascade" }), - categoryId: integer("category_id") - .notNull() - .references(() => category.id, { onDelete: "cascade" }), - }, - (t) => ({ - pk: primaryKey({ - columns: [t.fileId, t.categoryId], - }), - }), -); +interface FileCategoryTable { + file_id: number; + category_id: number; +} declare module "./index" { interface Database { @@ -72,5 +57,6 @@ declare module "./index" { directory_log: DirectoryLogTable; file: FileTable; file_log: FileLogTable; + file_category: FileCategoryTable; } } diff --git a/src/lib/server/db/schema/index.ts b/src/lib/server/db/schema/index.ts index 00c72a7..d3dd9b1 100644 --- a/src/lib/server/db/schema/index.ts +++ b/src/lib/server/db/schema/index.ts @@ -5,6 +5,7 @@ export * from "./hsk"; export * from "./mek"; export * from "./session"; export * from "./user"; +export * from "./util"; // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface Database {} diff --git a/src/lib/server/db/schema/util.ts b/src/lib/server/db/schema/util.ts new file mode 100644 index 0000000..d7f7350 --- /dev/null +++ b/src/lib/server/db/schema/util.ts @@ -0,0 +1,4 @@ +export type Ciphertext = { + ciphertext: string; // Base64 + iv: string; // Base64 +}; From f66421a5dcf3d766a91903fff329c865bc5c1150 Mon Sep 17 00:00:00 2001 From: static Date: Tue, 21 Jan 2025 11:10:16 +0900 Subject: [PATCH 16/53] =?UTF-8?q?@types/better-sqlite3=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .prettierignore | 3 --- package.json | 1 - pnpm-lock.yaml | 10 ---------- 3 files changed, 14 deletions(-) diff --git a/.prettierignore b/.prettierignore index 0f54b15..0c65201 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,8 +3,5 @@ package-lock.json pnpm-lock.yaml yarn.lock -# Output -/drizzle - # Documents *.md diff --git a/package.json b/package.json index 8185f79..93b2eed 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,6 @@ "@sveltejs/adapter-node": "^5.2.11", "@sveltejs/kit": "^2.15.2", "@sveltejs/vite-plugin-svelte": "^4.0.4", - "@types/better-sqlite3": "^7.6.12", "@types/file-saver": "^2.0.7", "@types/ms": "^0.7.34", "@types/node-schedule": "^2.1.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5092a35..b8fafd8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,9 +48,6 @@ importers: '@sveltejs/vite-plugin-svelte': specifier: ^4.0.4 version: 4.0.4(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5)) - '@types/better-sqlite3': - specifier: ^7.6.12 - version: 7.6.12 '@types/file-saver': specifier: ^2.0.7 version: 2.0.7 @@ -717,9 +714,6 @@ packages: svelte: ^5.0.0-next.96 || ^5.0.0 vite: ^5.0.0 - '@types/better-sqlite3@7.6.12': - resolution: {integrity: sha512-fnQmj8lELIj7BSrZQAdBMHEHX8OZLYIHXqAKT1O7tDfLxaINzf00PMjw22r3N/xXh0w/sGHlO6SVaCQ2mj78lg==} - '@types/cookie@0.6.0': resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} @@ -2627,10 +2621,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@types/better-sqlite3@7.6.12': - dependencies: - '@types/node': 22.10.5 - '@types/cookie@0.6.0': {} '@types/estree@1.0.6': {} From 2993593770b7cb7ac7d82cd2ce76f1b4e892590e Mon Sep 17 00:00:00 2001 From: static Date: Tue, 21 Jan 2025 14:35:34 +0900 Subject: [PATCH 17/53] =?UTF-8?q?/api/category/[id],=20/api/category/creat?= =?UTF-8?q?e=20Endpoint=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/server/db/category.ts | 2 +- src/lib/server/db/file.ts | 2 +- src/lib/server/schemas/category.ts | 28 ++++++++++++++ src/lib/server/schemas/directory.ts | 6 ++- src/lib/server/schemas/file.ts | 5 ++- src/lib/server/schemas/index.ts | 1 + src/lib/server/services/category.ts | 45 +++++++++++++++++++++++ src/lib/server/services/directory.ts | 3 +- src/routes/api/category/[id]/+server.ts | 33 +++++++++++++++++ src/routes/api/category/create/+server.ts | 23 ++++++++++++ 10 files changed, 141 insertions(+), 7 deletions(-) create mode 100644 src/lib/server/schemas/category.ts create mode 100644 src/lib/server/services/category.ts create mode 100644 src/routes/api/category/[id]/+server.ts create mode 100644 src/routes/api/category/create/+server.ts diff --git a/src/lib/server/db/category.ts b/src/lib/server/db/category.ts index fb85322..d710b34 100644 --- a/src/lib/server/db/category.ts +++ b/src/lib/server/db/category.ts @@ -2,7 +2,7 @@ import { IntegrityError } from "./error"; import db from "./kysely"; import type { Ciphertext } from "./schema"; -type CategoryId = "root" | number; +export type CategoryId = "root" | number; interface Category { id: number; diff --git a/src/lib/server/db/file.ts b/src/lib/server/db/file.ts index 789c3b3..8b7819b 100644 --- a/src/lib/server/db/file.ts +++ b/src/lib/server/db/file.ts @@ -3,7 +3,7 @@ import { IntegrityError } from "./error"; import db from "./kysely"; import type { Ciphertext } from "./schema"; -type DirectoryId = "root" | number; +export type DirectoryId = "root" | number; interface Directory { id: number; diff --git a/src/lib/server/schemas/category.ts b/src/lib/server/schemas/category.ts new file mode 100644 index 0000000..13032b3 --- /dev/null +++ b/src/lib/server/schemas/category.ts @@ -0,0 +1,28 @@ +import { z } from "zod"; + +export const categoryIdSchema = z.union([z.enum(["root"]), z.number().int().positive()]); + +export const categoryInfoResponse = z.object({ + metadata: z + .object({ + parent: categoryIdSchema, + mekVersion: z.number().int().positive(), + dek: z.string().base64().nonempty(), + dekVersion: z.string().datetime(), + name: z.string().base64().nonempty(), + nameIv: z.string().base64().nonempty(), + }) + .optional(), + subCategories: z.number().int().positive().array(), +}); +export type CategoryInfoResponse = z.infer; + +export const categoryCreateRequest = z.object({ + parent: categoryIdSchema, + mekVersion: z.number().int().positive(), + dek: z.string().base64().nonempty(), + dekVersion: z.string().datetime(), + name: z.string().base64().nonempty(), + nameIv: z.string().base64().nonempty(), +}); +export type CategoryCreateRequest = z.infer; diff --git a/src/lib/server/schemas/directory.ts b/src/lib/server/schemas/directory.ts index 15a5886..473d696 100644 --- a/src/lib/server/schemas/directory.ts +++ b/src/lib/server/schemas/directory.ts @@ -1,9 +1,11 @@ import { z } from "zod"; +export const directoryIdSchema = z.union([z.enum(["root"]), z.number().int().positive()]); + export const directoryInfoResponse = z.object({ metadata: z .object({ - parent: z.union([z.enum(["root"]), z.number().int().positive()]), + parent: directoryIdSchema, mekVersion: z.number().int().positive(), dek: z.string().base64().nonempty(), dekVersion: z.string().datetime(), @@ -29,7 +31,7 @@ export const directoryRenameRequest = z.object({ export type DirectoryRenameRequest = z.infer; export const directoryCreateRequest = z.object({ - parent: z.union([z.enum(["root"]), z.number().int().positive()]), + parent: directoryIdSchema, mekVersion: z.number().int().positive(), dek: z.string().base64().nonempty(), dekVersion: z.string().datetime(), diff --git a/src/lib/server/schemas/file.ts b/src/lib/server/schemas/file.ts index 781baf2..8f552f7 100644 --- a/src/lib/server/schemas/file.ts +++ b/src/lib/server/schemas/file.ts @@ -1,8 +1,9 @@ import mime from "mime"; import { z } from "zod"; +import { directoryIdSchema } from "./directory"; export const fileInfoResponse = z.object({ - parent: z.union([z.enum(["root"]), z.number().int().positive()]), + parent: directoryIdSchema, mekVersion: z.number().int().positive(), dek: z.string().base64().nonempty(), dekVersion: z.string().datetime(), @@ -39,7 +40,7 @@ export const duplicateFileScanResponse = z.object({ export type DuplicateFileScanResponse = z.infer; export const fileUploadRequest = z.object({ - parent: z.union([z.enum(["root"]), z.number().int().positive()]), + parent: directoryIdSchema, mekVersion: z.number().int().positive(), dek: z.string().base64().nonempty(), dekVersion: z.string().datetime(), diff --git a/src/lib/server/schemas/index.ts b/src/lib/server/schemas/index.ts index 6f8270b..1fed0d0 100644 --- a/src/lib/server/schemas/index.ts +++ b/src/lib/server/schemas/index.ts @@ -1,4 +1,5 @@ export * from "./auth"; +export * from "./category"; export * from "./client"; export * from "./directory"; export * from "./file"; diff --git a/src/lib/server/services/category.ts b/src/lib/server/services/category.ts new file mode 100644 index 0000000..5dd6c65 --- /dev/null +++ b/src/lib/server/services/category.ts @@ -0,0 +1,45 @@ +import { error } from "@sveltejs/kit"; +import { + registerCategory, + getAllCategoriesByParent, + getCategory, + type CategoryId, + type NewCategory, +} from "$lib/server/db/category"; +import { IntegrityError } from "$lib/server/db/error"; + +export const getCategoryInformation = async (userId: number, categoryId: CategoryId) => { + const category = categoryId !== "root" ? await getCategory(userId, categoryId) : undefined; + if (category === null) { + error(404, "Invalid category id"); + } + + const categories = await getAllCategoriesByParent(userId, categoryId); + return { + metadata: category && { + parentId: category.parentId ?? ("root" as const), + mekVersion: category.mekVersion, + encDek: category.encDek, + dekVersion: category.dekVersion, + encName: category.encName, + }, + categories: categories.map(({ id }) => id), + }; +}; + +export const createCategory = async (params: NewCategory) => { + const oneMinuteAgo = new Date(Date.now() - 60 * 1000); + const oneMinuteLater = new Date(Date.now() + 60 * 1000); + if (params.dekVersion <= oneMinuteAgo || params.dekVersion >= oneMinuteLater) { + error(400, "Invalid DEK version"); + } + + try { + await registerCategory(params); + } catch (e) { + if (e instanceof IntegrityError && e.message === "Inactive MEK version") { + error(400, "Inactive MEK version"); + } + throw e; + } +}; diff --git a/src/lib/server/services/directory.ts b/src/lib/server/services/directory.ts index be795b0..2525069 100644 --- a/src/lib/server/services/directory.ts +++ b/src/lib/server/services/directory.ts @@ -8,11 +8,12 @@ import { setDirectoryEncName, unregisterDirectory, getAllFilesByParent, + type DirectoryId, type NewDirectory, } from "$lib/server/db/file"; import type { Ciphertext } from "$lib/server/db/schema"; -export const getDirectoryInformation = async (userId: number, directoryId: "root" | number) => { +export const getDirectoryInformation = async (userId: number, directoryId: DirectoryId) => { const directory = directoryId !== "root" ? await getDirectory(userId, directoryId) : undefined; if (directory === null) { error(404, "Invalid directory id"); diff --git a/src/routes/api/category/[id]/+server.ts b/src/routes/api/category/[id]/+server.ts new file mode 100644 index 0000000..4a486fa --- /dev/null +++ b/src/routes/api/category/[id]/+server.ts @@ -0,0 +1,33 @@ +import { error, json } from "@sveltejs/kit"; +import { z } from "zod"; +import { authorize } from "$lib/server/modules/auth"; +import { categoryInfoResponse, type CategoryInfoResponse } from "$lib/server/schemas"; +import { getCategoryInformation } from "$lib/server/services/category"; +import type { RequestHandler } from "./$types"; + +export const GET: RequestHandler = async ({ locals, params }) => { + const { userId } = await authorize(locals, "activeClient"); + + const zodRes = z + .object({ + id: z.union([z.enum(["root"]), z.coerce.number().int().positive()]), + }) + .safeParse(params); + if (!zodRes.success) error(400, "Invalid path parameters"); + const { id } = zodRes.data; + + const { metadata, categories } = await getCategoryInformation(userId, id); + return json( + categoryInfoResponse.parse({ + metadata: metadata && { + parent: metadata.parentId, + mekVersion: metadata.mekVersion, + dek: metadata.encDek, + dekVersion: metadata.dekVersion.toISOString(), + name: metadata.encName.ciphertext, + nameIv: metadata.encName.iv, + }, + subCategories: categories, + } satisfies CategoryInfoResponse), + ); +}; diff --git a/src/routes/api/category/create/+server.ts b/src/routes/api/category/create/+server.ts new file mode 100644 index 0000000..216d850 --- /dev/null +++ b/src/routes/api/category/create/+server.ts @@ -0,0 +1,23 @@ +import { error, text } from "@sveltejs/kit"; +import { authorize } from "$lib/server/modules/auth"; +import { categoryCreateRequest } from "$lib/server/schemas"; +import { createCategory } from "$lib/server/services/category"; +import type { RequestHandler } from "./$types"; + +export const POST: RequestHandler = async ({ locals, request }) => { + const { userId } = await authorize(locals, "activeClient"); + + const zodRes = categoryCreateRequest.safeParse(await request.json()); + if (!zodRes.success) error(400, "Invalid request body"); + const { parent, mekVersion, dek, dekVersion, name, nameIv } = zodRes.data; + + await createCategory({ + userId, + parentId: parent, + mekVersion, + encDek: dek, + dekVersion: new Date(dekVersion), + encName: { ciphertext: name, iv: nameIv }, + }); + return text("Category created", { headers: { "Content-Type": "text/plain" } }); +}; From efe2782db05764a6be17915e0cbb57a2a449b2e5 Mon Sep 17 00:00:00 2001 From: static Date: Tue, 21 Jan 2025 16:07:23 +0900 Subject: [PATCH 18/53] =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B5=AC=ED=98=84=20(WiP)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 아직은 하위 카테고리의 목록만 볼 수 있습니다. --- src/lib/components/TopBar.svelte | 8 +- src/lib/modules/filesystem.ts | 81 ++++++++++++++++++- src/routes/(main)/category/+page.svelte | 3 - .../(main)/category/[[id]]/+page.svelte | 61 ++++++++++++++ src/routes/(main)/category/[[id]]/+page.ts | 17 ++++ .../[[id]]/CreateCategoryModal.svelte | 30 +++++++ .../category/[[id]]/SubCategories.svelte | 41 ++++++++++ .../(main)/category/[[id]]/SubCategory.svelte | 61 ++++++++++++++ src/routes/(main)/category/[[id]]/service.ts | 28 +++++++ 9 files changed, 322 insertions(+), 8 deletions(-) delete mode 100644 src/routes/(main)/category/+page.svelte create mode 100644 src/routes/(main)/category/[[id]]/+page.svelte create mode 100644 src/routes/(main)/category/[[id]]/+page.ts create mode 100644 src/routes/(main)/category/[[id]]/CreateCategoryModal.svelte create mode 100644 src/routes/(main)/category/[[id]]/SubCategories.svelte create mode 100644 src/routes/(main)/category/[[id]]/SubCategory.svelte create mode 100644 src/routes/(main)/category/[[id]]/service.ts diff --git a/src/lib/components/TopBar.svelte b/src/lib/components/TopBar.svelte index 6691feb..9d1893f 100644 --- a/src/lib/components/TopBar.svelte +++ b/src/lib/components/TopBar.svelte @@ -7,16 +7,20 @@ children?: Snippet; onback?: () => void; title?: string; + xPadding?: boolean; } - let { children, onback, title }: Props = $props(); + let { children, onback, title, xPadding = false }: Props = $props(); const back = $derived(() => { setTimeout(onback || (() => history.back()), 100); }); -
+
diff --git a/src/lib/modules/filesystem.ts b/src/lib/modules/filesystem.ts index 1a1ff0f..54b5c10 100644 --- a/src/lib/modules/filesystem.ts +++ b/src/lib/modules/filesystem.ts @@ -12,7 +12,11 @@ import { type DirectoryId, } from "$lib/indexedDB"; import { unwrapDataKey, decryptString } from "$lib/modules/crypto"; -import type { DirectoryInfoResponse, FileInfoResponse } from "$lib/server/schemas"; +import type { + CategoryInfoResponse, + DirectoryInfoResponse, + FileInfoResponse, +} from "$lib/server/schemas"; export type DirectoryInfo = | { @@ -43,8 +47,27 @@ export interface FileInfo { lastModifiedAt: Date; } +type CategoryId = "root" | number; + +export type CategoryInfo = + | { + id: "root"; + dataKey?: undefined; + dataKeyVersion?: undefined; + name?: undefined; + subCategoryIds: number[]; + } + | { + id: number; + dataKey?: CryptoKey; + dataKeyVersion?: Date; + name: string; + subCategoryIds: number[]; + }; + const directoryInfoStore = new Map>(); const fileInfoStore = new Map>(); +const categoryInfoStore = new Map>(); const fetchDirectoryInfoFromIndexedDB = async ( id: DirectoryId, @@ -124,7 +147,7 @@ export const getDirectoryInfo = (id: DirectoryId, masterKey: CryptoKey) => { directoryInfoStore.set(id, info); } - fetchDirectoryInfo(id, info, masterKey); + fetchDirectoryInfo(id, info, masterKey); // Intended return info; }; @@ -203,6 +226,58 @@ export const getFileInfo = (fileId: number, masterKey: CryptoKey) => { fileInfoStore.set(fileId, info); } - fetchFileInfo(fileId, info, masterKey); + fetchFileInfo(fileId, info, masterKey); // Intended + return info; +}; + +const fetchCategoryInfoFromServer = async ( + id: CategoryId, + info: Writable, + masterKey: CryptoKey, +) => { + const res = await callGetApi(`/api/category/${id}`); + if (res.status === 404) { + info.set(null); + return; + } else if (!res.ok) { + throw new Error("Failed to fetch category information"); + } + + const { metadata, subCategories }: CategoryInfoResponse = await res.json(); + + if (id === "root") { + info.set({ id, subCategoryIds: subCategories }); + } else { + const { dataKey } = await unwrapDataKey(metadata!.dek, masterKey); + const name = await decryptString(metadata!.name, metadata!.nameIv, dataKey); + + info.set({ + id, + dataKey, + dataKeyVersion: new Date(metadata!.dekVersion), + name, + subCategoryIds: subCategories, + }); + } +}; + +const fetchCategoryInfo = async ( + id: CategoryId, + info: Writable, + masterKey: CryptoKey, +) => { + await fetchCategoryInfoFromServer(id, info, masterKey); +}; + +export const getCategoryInfo = (categoryId: CategoryId, masterKey: CryptoKey) => { + // TODO: MEK rotation + + let info = categoryInfoStore.get(categoryId); + if (!info) { + info = writable(null); + categoryInfoStore.set(categoryId, info); + } + + fetchCategoryInfo(categoryId, info, masterKey); // Intended return info; }; diff --git a/src/routes/(main)/category/+page.svelte b/src/routes/(main)/category/+page.svelte deleted file mode 100644 index 73d68b7..0000000 --- a/src/routes/(main)/category/+page.svelte +++ /dev/null @@ -1,3 +0,0 @@ -
-

아직 개발 중이에요.

-
diff --git a/src/routes/(main)/category/[[id]]/+page.svelte b/src/routes/(main)/category/[[id]]/+page.svelte new file mode 100644 index 0000000..91e027b --- /dev/null +++ b/src/routes/(main)/category/[[id]]/+page.svelte @@ -0,0 +1,61 @@ + + + + 카테고리 + + +
+ {#if data.id !== "root"} + + {/if} + {#if $info} +
+
+ {#if data.id !== "root"} +

하위 카테고리

+ {/if} + {#key $info} + goto(`/category/${id}`)} + onCategoryCreateClick={() => { + isCreateCategoryModalOpen = true; + }} + /> + {/key} +
+ {#if data.id !== "root"} +
+

파일

+
+ {/if} +
+ {/if} +
+ + diff --git a/src/routes/(main)/category/[[id]]/+page.ts b/src/routes/(main)/category/[[id]]/+page.ts new file mode 100644 index 0000000..cfa37f8 --- /dev/null +++ b/src/routes/(main)/category/[[id]]/+page.ts @@ -0,0 +1,17 @@ +import { error } from "@sveltejs/kit"; +import { z } from "zod"; +import type { PageLoad } from "./$types"; + +export const load: PageLoad = async ({ params }) => { + const zodRes = z + .object({ + id: z.coerce.number().int().positive().optional(), + }) + .safeParse(params); + if (!zodRes.success) error(404, "Not found"); + const { id } = zodRes.data; + + return { + id: id ? id : ("root" as const), + }; +}; diff --git a/src/routes/(main)/category/[[id]]/CreateCategoryModal.svelte b/src/routes/(main)/category/[[id]]/CreateCategoryModal.svelte new file mode 100644 index 0000000..37f868a --- /dev/null +++ b/src/routes/(main)/category/[[id]]/CreateCategoryModal.svelte @@ -0,0 +1,30 @@ + + + +

새 카테고리

+
+ +
+
+ + +
+
diff --git a/src/routes/(main)/category/[[id]]/SubCategories.svelte b/src/routes/(main)/category/[[id]]/SubCategories.svelte new file mode 100644 index 0000000..1f47fc8 --- /dev/null +++ b/src/routes/(main)/category/[[id]]/SubCategories.svelte @@ -0,0 +1,41 @@ + + +
+ {#each subCategories as subCategory} + + {/each} + +
+ +

카테고리 추가하기

+
+
+
diff --git a/src/routes/(main)/category/[[id]]/SubCategory.svelte b/src/routes/(main)/category/[[id]]/SubCategory.svelte new file mode 100644 index 0000000..212b591 --- /dev/null +++ b/src/routes/(main)/category/[[id]]/SubCategory.svelte @@ -0,0 +1,61 @@ + + +{#if $info} + + +
+
+
+ +
+

+ {$info.name} +

+ +
+
+{/if} + + diff --git a/src/routes/(main)/category/[[id]]/service.ts b/src/routes/(main)/category/[[id]]/service.ts new file mode 100644 index 0000000..a5d354a --- /dev/null +++ b/src/routes/(main)/category/[[id]]/service.ts @@ -0,0 +1,28 @@ +import { callPostApi } from "$lib/hooks"; +import { generateDataKey, wrapDataKey, encryptString } from "$lib/modules/crypto"; +import type { CategoryCreateRequest } from "$lib/server/schemas"; +import type { MasterKey } from "$lib/stores"; + +export interface SelectedSubCategory { + id: number; + dataKey: CryptoKey; + dataKeyVersion: Date; + name: string; +} + +export const requestCategoryCreation = async ( + name: string, + parentId: "root" | number, + masterKey: MasterKey, +) => { + const { dataKey, dataKeyVersion } = await generateDataKey(); + const nameEncrypted = await encryptString(name, dataKey); + await callPostApi("/api/category/create", { + parent: parentId, + mekVersion: masterKey.version, + dek: await wrapDataKey(dataKey, masterKey.key), + dekVersion: dataKeyVersion.toISOString(), + name: nameEncrypted.ciphertext, + nameIv: nameEncrypted.iv, + }); +}; From 88d4757cf714958170ea8166068ab5f39431b26e Mon Sep 17 00:00:00 2001 From: static Date: Tue, 21 Jan 2025 17:32:08 +0900 Subject: [PATCH 19/53] =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=EC=97=90=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EB=B6=80=EB=B6=84=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/modules/filesystem.ts | 13 +++- src/lib/server/schemas/category.ts | 10 +++ src/lib/server/services/category.ts | 30 +++++++++ .../(main)/category/[[id]]/+page.svelte | 6 +- src/routes/(main)/category/[[id]]/File.svelte | 61 +++++++++++++++++++ .../(main)/category/[[id]]/Files.svelte | 34 +++++++++++ src/routes/(main)/category/[[id]]/service.ts | 7 +++ .../api/category/[id]/file/add/+server.ts | 25 ++++++++ .../api/category/[id]/file/list/+server.ts | 17 ++++++ 9 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 src/routes/(main)/category/[[id]]/File.svelte create mode 100644 src/routes/(main)/category/[[id]]/Files.svelte create mode 100644 src/routes/api/category/[id]/file/add/+server.ts create mode 100644 src/routes/api/category/[id]/file/list/+server.ts diff --git a/src/lib/modules/filesystem.ts b/src/lib/modules/filesystem.ts index 54b5c10..cfafbbe 100644 --- a/src/lib/modules/filesystem.ts +++ b/src/lib/modules/filesystem.ts @@ -13,6 +13,7 @@ import { } from "$lib/indexedDB"; import { unwrapDataKey, decryptString } from "$lib/modules/crypto"; import type { + CategoryFileListResponse, CategoryInfoResponse, DirectoryInfoResponse, FileInfoResponse, @@ -56,6 +57,7 @@ export type CategoryInfo = dataKeyVersion?: undefined; name?: undefined; subCategoryIds: number[]; + files?: undefined; } | { id: number; @@ -63,6 +65,7 @@ export type CategoryInfo = dataKeyVersion?: Date; name: string; subCategoryIds: number[]; + files: number[]; }; const directoryInfoStore = new Map>(); @@ -235,7 +238,7 @@ const fetchCategoryInfoFromServer = async ( info: Writable, masterKey: CryptoKey, ) => { - const res = await callGetApi(`/api/category/${id}`); + let res = await callGetApi(`/api/category/${id}`); if (res.status === 404) { info.set(null); return; @@ -251,12 +254,20 @@ const fetchCategoryInfoFromServer = async ( const { dataKey } = await unwrapDataKey(metadata!.dek, masterKey); const name = await decryptString(metadata!.name, metadata!.nameIv, dataKey); + res = await callGetApi(`/api/category/${id}/file/list`); + if (!res.ok) { + throw new Error("Failed to fetch category files"); + } + + const { files }: CategoryFileListResponse = await res.json(); + info.set({ id, dataKey, dataKeyVersion: new Date(metadata!.dekVersion), name, subCategoryIds: subCategories, + files, }); } }; diff --git a/src/lib/server/schemas/category.ts b/src/lib/server/schemas/category.ts index 13032b3..a693bee 100644 --- a/src/lib/server/schemas/category.ts +++ b/src/lib/server/schemas/category.ts @@ -17,6 +17,16 @@ export const categoryInfoResponse = z.object({ }); export type CategoryInfoResponse = z.infer; +export const categoryFileAddRequest = z.object({ + file: z.number().int().positive(), +}); +export type CategoryFileAddRequest = z.infer; + +export const categoryFileListResponse = z.object({ + files: z.number().int().positive().array(), +}); +export type CategoryFileListResponse = z.infer; + export const categoryCreateRequest = z.object({ parent: categoryIdSchema, mekVersion: z.number().int().positive(), diff --git a/src/lib/server/services/category.ts b/src/lib/server/services/category.ts index 5dd6c65..e81e458 100644 --- a/src/lib/server/services/category.ts +++ b/src/lib/server/services/category.ts @@ -7,6 +7,7 @@ import { type NewCategory, } from "$lib/server/db/category"; import { IntegrityError } from "$lib/server/db/error"; +import { getAllFilesByCategory, getFile, addFileToCategory } from "$lib/server/db/file"; export const getCategoryInformation = async (userId: number, categoryId: CategoryId) => { const category = categoryId !== "root" ? await getCategory(userId, categoryId) : undefined; @@ -27,6 +28,35 @@ export const getCategoryInformation = async (userId: number, categoryId: Categor }; }; +export const addCategoryFile = async (userId: number, categoryId: number, fileId: number) => { + const category = await getCategory(userId, categoryId); + const file = await getFile(userId, fileId); + if (!category) { + error(404, "Invalid category id"); + } else if (!file) { + error(404, "Invalid file id"); + } + + try { + await addFileToCategory(fileId, categoryId); + } catch (e) { + if (e instanceof IntegrityError && e.message === "File already added to category") { + error(400, "File already added"); + } + throw e; + } +}; + +export const getCategoryFiles = async (userId: number, categoryId: number) => { + const category = await getCategory(userId, categoryId); + if (!category) { + error(404, "Invalid category id"); + } + + const files = await getAllFilesByCategory(userId, categoryId); + return { files: files.map(({ id }) => id) }; +}; + export const createCategory = async (params: NewCategory) => { const oneMinuteAgo = new Date(Date.now() - 60 * 1000); const oneMinuteLater = new Date(Date.now() + 60 * 1000); diff --git a/src/routes/(main)/category/[[id]]/+page.svelte b/src/routes/(main)/category/[[id]]/+page.svelte index 91e027b..33ac0a7 100644 --- a/src/routes/(main)/category/[[id]]/+page.svelte +++ b/src/routes/(main)/category/[[id]]/+page.svelte @@ -5,6 +5,7 @@ import { getCategoryInfo, type CategoryInfo } from "$lib/modules/filesystem"; import { masterKeyStore } from "$lib/stores"; import CreateCategoryModal from "./CreateCategoryModal.svelte"; + import Files from "./Files.svelte"; import SubCategories from "./SubCategories.svelte"; import { requestCategoryCreation } from "./service"; @@ -34,7 +35,7 @@ {/if} {#if $info} -
+
{#if data.id !== "root"}

하위 카테고리

@@ -52,6 +53,9 @@ {#if data.id !== "root"}

파일

+ {#key $info} + goto(`/file/${id}`)} /> + {/key}
{/if}
diff --git a/src/routes/(main)/category/[[id]]/File.svelte b/src/routes/(main)/category/[[id]]/File.svelte new file mode 100644 index 0000000..c3b9b97 --- /dev/null +++ b/src/routes/(main)/category/[[id]]/File.svelte @@ -0,0 +1,61 @@ + + +{#if $info} + + +
+
+
+ +
+

+ {$info.name} +

+ +
+
+{/if} + + diff --git a/src/routes/(main)/category/[[id]]/Files.svelte b/src/routes/(main)/category/[[id]]/Files.svelte new file mode 100644 index 0000000..6679931 --- /dev/null +++ b/src/routes/(main)/category/[[id]]/Files.svelte @@ -0,0 +1,34 @@ + + +
+ {#each files as file} + + {:else} +

이 카테고리에 추가된 파일이 없어요.

+ {/each} +
diff --git a/src/routes/(main)/category/[[id]]/service.ts b/src/routes/(main)/category/[[id]]/service.ts index a5d354a..6af11e3 100644 --- a/src/routes/(main)/category/[[id]]/service.ts +++ b/src/routes/(main)/category/[[id]]/service.ts @@ -10,6 +10,13 @@ export interface SelectedSubCategory { name: string; } +export interface SelectedFile { + id: number; + dataKey: CryptoKey; + dataKeyVersion: Date; + name: string; +} + export const requestCategoryCreation = async ( name: string, parentId: "root" | number, diff --git a/src/routes/api/category/[id]/file/add/+server.ts b/src/routes/api/category/[id]/file/add/+server.ts new file mode 100644 index 0000000..2eaf2f2 --- /dev/null +++ b/src/routes/api/category/[id]/file/add/+server.ts @@ -0,0 +1,25 @@ +import { error, text } from "@sveltejs/kit"; +import { z } from "zod"; +import { authorize } from "$lib/server/modules/auth"; +import { categoryFileAddRequest } from "$lib/server/schemas"; +import { addCategoryFile } from "$lib/server/services/category"; +import type { RequestHandler } from "./$types"; + +export const POST: RequestHandler = async ({ locals, params, request }) => { + const { userId } = await authorize(locals, "activeClient"); + + const paramsZodRes = z + .object({ + id: z.coerce.number().int().positive(), + }) + .safeParse(params); + if (!paramsZodRes.success) error(400, "Invalid path parameters"); + const { id } = paramsZodRes.data; + + const bodyZodRes = categoryFileAddRequest.safeParse(await request.json()); + if (!bodyZodRes.success) error(400, "Invalid request body"); + const { file } = bodyZodRes.data; + + await addCategoryFile(userId, id, file); + return text("File added", { headers: { "Content-Type": "text/plain" } }); +}; diff --git a/src/routes/api/category/[id]/file/list/+server.ts b/src/routes/api/category/[id]/file/list/+server.ts new file mode 100644 index 0000000..e2aa7af --- /dev/null +++ b/src/routes/api/category/[id]/file/list/+server.ts @@ -0,0 +1,17 @@ +import { error, json } from "@sveltejs/kit"; +import { z } from "zod"; +import { authorize } from "$lib/server/modules/auth"; +import { categoryFileListResponse, type CategoryFileListResponse } from "$lib/server/schemas"; +import { getCategoryFiles } from "$lib/server/services/category"; +import type { RequestHandler } from "./$types"; + +export const GET: RequestHandler = async ({ locals, params }) => { + const { userId } = await authorize(locals, "activeClient"); + + const zodRes = z.object({ id: z.coerce.number().int().positive() }).safeParse(params); + if (!zodRes.success) error(400, "Invalid path parameters"); + const { id } = zodRes.data; + + const { files } = await getCategoryFiles(userId, id); + return json(categoryFileListResponse.parse({ files }) as CategoryFileListResponse); +}; From dbe2262d07c3aff9e685b1a9c04aae6ef989c8d3 Mon Sep 17 00:00:00 2001 From: static Date: Wed, 22 Jan 2025 11:28:13 +0900 Subject: [PATCH 20/53] =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=EC=9D=98=20=EC=A3=BC=EC=9A=94=20?= =?UTF-8?q?=EC=9A=94=EC=86=8C=EB=A5=BC=20=EB=B3=84=EB=8F=84=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 +- src/lib/organisms/Category/Category.svelte | 72 +++++++++++++++++++ .../organisms/Category}/File.svelte | 0 .../organisms/Category}/SubCategory.svelte | 0 src/lib/organisms/Category/index.ts | 2 + src/lib/organisms/Category/service.ts | 13 ++++ .../(main)/category/[[id]]/+page.svelte | 39 +++------- .../(main)/category/[[id]]/Files.svelte | 34 --------- .../category/[[id]]/SubCategories.svelte | 41 ----------- src/routes/(main)/category/[[id]]/service.ts | 14 ---- 10 files changed, 100 insertions(+), 118 deletions(-) create mode 100644 src/lib/organisms/Category/Category.svelte rename src/{routes/(main)/category/[[id]] => lib/organisms/Category}/File.svelte (100%) rename src/{routes/(main)/category/[[id]] => lib/organisms/Category}/SubCategory.svelte (100%) create mode 100644 src/lib/organisms/Category/index.ts create mode 100644 src/lib/organisms/Category/service.ts delete mode 100644 src/routes/(main)/category/[[id]]/Files.svelte delete mode 100644 src/routes/(main)/category/[[id]]/SubCategories.svelte diff --git a/package.json b/package.json index 93b2eed..1830e27 100644 --- a/package.json +++ b/package.json @@ -5,13 +5,14 @@ "type": "module", "scripts": { "dev": "vite dev", - "dev:db": "docker compose -f docker-compose.dev.yaml -p arkvault-dev up -d", "build": "vite build", "preview": "vite preview", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "format": "prettier --write .", "lint": "prettier --check . && eslint .", + "db:up": "docker compose -f docker-compose.dev.yaml -p arkvault-dev up -d", + "db:down": "docker compose -f docker-compose.dev.yaml -p arkvault-dev down", "db:migrate": "kysely migrate" }, "devDependencies": { diff --git a/src/lib/organisms/Category/Category.svelte b/src/lib/organisms/Category/Category.svelte new file mode 100644 index 0000000..cfe1809 --- /dev/null +++ b/src/lib/organisms/Category/Category.svelte @@ -0,0 +1,72 @@ + + +
+
+ {#if info.id !== "root"} +

하위 카테고리

+ {/if} +
+ {#key info} + {#each subCategories as subCategory} + + {/each} + {/key} + +
+ +

카테고리 추가하기

+
+
+
+
+ {#if info.id !== "root"} +
+

파일

+
+ {#key info} + {#each files as file} + + {:else} +

이 카테고리에 추가된 파일이 없어요.

+ {/each} + {/key} +
+
+ {/if} +
diff --git a/src/routes/(main)/category/[[id]]/File.svelte b/src/lib/organisms/Category/File.svelte similarity index 100% rename from src/routes/(main)/category/[[id]]/File.svelte rename to src/lib/organisms/Category/File.svelte diff --git a/src/routes/(main)/category/[[id]]/SubCategory.svelte b/src/lib/organisms/Category/SubCategory.svelte similarity index 100% rename from src/routes/(main)/category/[[id]]/SubCategory.svelte rename to src/lib/organisms/Category/SubCategory.svelte diff --git a/src/lib/organisms/Category/index.ts b/src/lib/organisms/Category/index.ts new file mode 100644 index 0000000..51e0a58 --- /dev/null +++ b/src/lib/organisms/Category/index.ts @@ -0,0 +1,2 @@ +export { default } from "./Category.svelte"; +export * from "./service"; diff --git a/src/lib/organisms/Category/service.ts b/src/lib/organisms/Category/service.ts new file mode 100644 index 0000000..8499c88 --- /dev/null +++ b/src/lib/organisms/Category/service.ts @@ -0,0 +1,13 @@ +export interface SelectedSubCategory { + id: number; + dataKey: CryptoKey; + dataKeyVersion: Date; + name: string; +} + +export interface SelectedFile { + id: number; + dataKey: CryptoKey; + dataKeyVersion: Date; + name: string; +} diff --git a/src/routes/(main)/category/[[id]]/+page.svelte b/src/routes/(main)/category/[[id]]/+page.svelte index 33ac0a7..aa551f1 100644 --- a/src/routes/(main)/category/[[id]]/+page.svelte +++ b/src/routes/(main)/category/[[id]]/+page.svelte @@ -3,10 +3,9 @@ import { goto } from "$app/navigation"; import { TopBar } from "$lib/components"; import { getCategoryInfo, type CategoryInfo } from "$lib/modules/filesystem"; + import Category from "$lib/organisms/Category"; import { masterKeyStore } from "$lib/stores"; import CreateCategoryModal from "./CreateCategoryModal.svelte"; - import Files from "./Files.svelte"; - import SubCategories from "./SubCategories.svelte"; import { requestCategoryCreation } from "./service"; let { data } = $props(); @@ -34,32 +33,16 @@ {#if data.id !== "root"} {/if} - {#if $info} -
-
- {#if data.id !== "root"} -

하위 카테고리

- {/if} - {#key $info} - goto(`/category/${id}`)} - onCategoryCreateClick={() => { - isCreateCategoryModalOpen = true; - }} - /> - {/key} -
- {#if data.id !== "root"} -
-

파일

- {#key $info} - goto(`/file/${id}`)} /> - {/key} -
- {/if} -
- {/if} +
+ {#if $info} + goto(`/category/${id}`)} + onSubCategoryCreateClick={() => (isCreateCategoryModalOpen = true)} + onFileClick={({ id }) => goto(`/file/${id}`)} + /> + {/if} +
diff --git a/src/routes/(main)/category/[[id]]/Files.svelte b/src/routes/(main)/category/[[id]]/Files.svelte deleted file mode 100644 index 6679931..0000000 --- a/src/routes/(main)/category/[[id]]/Files.svelte +++ /dev/null @@ -1,34 +0,0 @@ - - -
- {#each files as file} - - {:else} -

이 카테고리에 추가된 파일이 없어요.

- {/each} -
diff --git a/src/routes/(main)/category/[[id]]/SubCategories.svelte b/src/routes/(main)/category/[[id]]/SubCategories.svelte deleted file mode 100644 index 1f47fc8..0000000 --- a/src/routes/(main)/category/[[id]]/SubCategories.svelte +++ /dev/null @@ -1,41 +0,0 @@ - - -
- {#each subCategories as subCategory} - - {/each} - -
- -

카테고리 추가하기

-
-
-
diff --git a/src/routes/(main)/category/[[id]]/service.ts b/src/routes/(main)/category/[[id]]/service.ts index 6af11e3..c2018ed 100644 --- a/src/routes/(main)/category/[[id]]/service.ts +++ b/src/routes/(main)/category/[[id]]/service.ts @@ -3,20 +3,6 @@ import { generateDataKey, wrapDataKey, encryptString } from "$lib/modules/crypto import type { CategoryCreateRequest } from "$lib/server/schemas"; import type { MasterKey } from "$lib/stores"; -export interface SelectedSubCategory { - id: number; - dataKey: CryptoKey; - dataKeyVersion: Date; - name: string; -} - -export interface SelectedFile { - id: number; - dataKey: CryptoKey; - dataKeyVersion: Date; - name: string; -} - export const requestCategoryCreation = async ( name: string, parentId: "root" | number, From a2402f37a07764c36fb44a5a4131faeaab60ecc3 Mon Sep 17 00:00:00 2001 From: static Date: Wed, 22 Jan 2025 13:22:16 +0900 Subject: [PATCH 21/53] =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC?= =?UTF-8?q?=EC=97=90=20=ED=8C=8C=EC=9D=BC=EC=9D=84=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8A=94=20BottomSheet=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(WiP)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- pnpm-lock.yaml | 82 +++++++++---------- src/lib/components/BottomSheet.svelte | 2 +- .../molecules/Categories/Categories.svelte | 19 +++++ .../Categories/Category.svelte} | 4 +- src/lib/molecules/Categories/index.ts | 2 + src/lib/molecules/Categories/service.ts | 6 ++ src/lib/molecules/SubCategories.svelte | 57 +++++++++++++ src/lib/organisms/Category/Category.svelte | 24 ++---- src/lib/organisms/Category/service.ts | 7 -- .../(fullscreen)/file/[id]/+page.svelte | 15 +++- .../file/[id]/AddToCategoryBottomSheet.svelte | 42 ++++++++++ src/routes/(fullscreen)/file/[id]/service.ts | 9 ++ 13 files changed, 199 insertions(+), 72 deletions(-) create mode 100644 src/lib/molecules/Categories/Categories.svelte rename src/lib/{organisms/Category/SubCategory.svelte => molecules/Categories/Category.svelte} (93%) create mode 100644 src/lib/molecules/Categories/index.ts create mode 100644 src/lib/molecules/Categories/service.ts create mode 100644 src/lib/molecules/SubCategories.svelte create mode 100644 src/routes/(fullscreen)/file/[id]/AddToCategoryBottomSheet.svelte diff --git a/package.json b/package.json index 1830e27..9e31c9d 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "prettier": "^3.4.2", "prettier-plugin-svelte": "^3.3.2", "prettier-plugin-tailwindcss": "^0.6.9", - "svelte": "^5.17.1", + "svelte": "^5.19.1", "svelte-check": "^4.1.3", "tailwindcss": "^3.4.17", "typescript": "^5.7.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b8fafd8..9ed4442 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,13 +41,13 @@ importers: version: 1.2.12 '@sveltejs/adapter-node': specifier: ^5.2.11 - version: 5.2.11(@sveltejs/kit@2.15.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5)))(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5))) + version: 5.2.11(@sveltejs/kit@2.15.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5)))(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5))) '@sveltejs/kit': specifier: ^2.15.2 - version: 2.15.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5)))(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5)) + version: 2.15.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5)))(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5)) '@sveltejs/vite-plugin-svelte': specifier: ^4.0.4 - version: 4.0.4(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5)) + version: 4.0.4(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5)) '@types/file-saver': specifier: ^2.0.7 version: 2.0.7 @@ -77,7 +77,7 @@ importers: version: 9.1.0(eslint@9.17.0(jiti@2.4.2)) eslint-plugin-svelte: specifier: ^2.46.1 - version: 2.46.1(eslint@9.17.0(jiti@2.4.2))(svelte@5.17.1) + version: 2.46.1(eslint@9.17.0(jiti@2.4.2))(svelte@5.19.1) eslint-plugin-tailwindcss: specifier: ^3.17.5 version: 3.17.5(tailwindcss@3.4.17) @@ -107,16 +107,16 @@ importers: version: 3.4.2 prettier-plugin-svelte: specifier: ^3.3.2 - version: 3.3.2(prettier@3.4.2)(svelte@5.17.1) + version: 3.3.2(prettier@3.4.2)(svelte@5.19.1) prettier-plugin-tailwindcss: specifier: ^0.6.9 - version: 0.6.9(prettier-plugin-svelte@3.3.2(prettier@3.4.2)(svelte@5.17.1))(prettier@3.4.2) + version: 0.6.9(prettier-plugin-svelte@3.3.2(prettier@3.4.2)(svelte@5.19.1))(prettier@3.4.2) svelte: - specifier: ^5.17.1 - version: 5.17.1 + specifier: ^5.19.1 + version: 5.19.1 svelte-check: specifier: ^4.1.3 - version: 4.1.3(picomatch@4.0.2)(svelte@5.17.1)(typescript@5.7.3) + version: 4.1.3(picomatch@4.0.2)(svelte@5.19.1)(typescript@5.7.3) tailwindcss: specifier: ^3.4.17 version: 3.4.17 @@ -128,7 +128,7 @@ importers: version: 8.19.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3) unplugin-icons: specifier: ^0.22.0 - version: 0.22.0(svelte@5.17.1) + version: 0.22.0(svelte@5.19.1) vite: specifier: ^5.4.11 version: 5.4.11(@types/node@22.10.5) @@ -1114,8 +1114,8 @@ packages: resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} engines: {node: '>=0.10'} - esrap@1.3.2: - resolution: {integrity: sha512-C4PXusxYhFT98GjLSmb20k9PREuUdporer50dhzGuJu9IJXktbMddVCMLAERl5dAHyAi73GWWCE4FVHGP1794g==} + esrap@1.4.3: + resolution: {integrity: sha512-Xddc1RsoFJ4z9nR7W7BFaEPIp4UXoeQ0+077UdWLxbafMQFyU79sQJMk7kxNgRwQ9/aVgaKacCHC2pUACGwmYw==} esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} @@ -1992,8 +1992,8 @@ packages: svelte: optional: true - svelte@5.17.1: - resolution: {integrity: sha512-HitqD0XhU9OEytPuux/XYzxle4+7D8+fIb1tHbwMzOtBzDZZO+ESEuwMbahJ/3JoklfmRPB/Gzp74L87Qrxfpw==} + svelte@5.19.1: + resolution: {integrity: sha512-H/Vs2O51bwILZbaNUSdr4P1NbLpOGsxl4jJAjd88ELjzRgeRi1BHqexcVGannDr7D1pmTYWWajzHOM7bMbtB9Q==} engines: {node: '>=18'} tailwindcss@3.4.17: @@ -2573,17 +2573,17 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.30.1': optional: true - '@sveltejs/adapter-node@5.2.11(@sveltejs/kit@2.15.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5)))(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5)))': + '@sveltejs/adapter-node@5.2.11(@sveltejs/kit@2.15.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5)))(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5)))': dependencies: '@rollup/plugin-commonjs': 28.0.2(rollup@4.30.1) '@rollup/plugin-json': 6.1.0(rollup@4.30.1) '@rollup/plugin-node-resolve': 16.0.0(rollup@4.30.1) - '@sveltejs/kit': 2.15.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5)))(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5)) + '@sveltejs/kit': 2.15.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5)))(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5)) rollup: 4.30.1 - '@sveltejs/kit@2.15.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5)))(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5))': + '@sveltejs/kit@2.15.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5)))(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5))': dependencies: - '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5)) + '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5)) '@types/cookie': 0.6.0 cookie: 0.6.0 devalue: 5.1.1 @@ -2595,27 +2595,27 @@ snapshots: sade: 1.8.1 set-cookie-parser: 2.7.1 sirv: 3.0.0 - svelte: 5.17.1 + svelte: 5.19.1 tiny-glob: 0.2.9 vite: 5.4.11(@types/node@22.10.5) - '@sveltejs/vite-plugin-svelte-inspector@3.0.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5)))(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5))': + '@sveltejs/vite-plugin-svelte-inspector@3.0.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5)))(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5))': dependencies: - '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5)) + '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5)) debug: 4.4.0 - svelte: 5.17.1 + svelte: 5.19.1 vite: 5.4.11(@types/node@22.10.5) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5))': + '@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 3.0.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5)))(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5)) + '@sveltejs/vite-plugin-svelte-inspector': 3.0.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5)))(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5)) debug: 4.4.0 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.17 - svelte: 5.17.1 + svelte: 5.19.1 vite: 5.4.11(@types/node@22.10.5) vitefu: 1.0.5(vite@5.4.11(@types/node@22.10.5)) transitivePeerDependencies: @@ -3001,7 +3001,7 @@ snapshots: dependencies: eslint: 9.17.0(jiti@2.4.2) - eslint-plugin-svelte@2.46.1(eslint@9.17.0(jiti@2.4.2))(svelte@5.17.1): + eslint-plugin-svelte@2.46.1(eslint@9.17.0(jiti@2.4.2))(svelte@5.19.1): dependencies: '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@2.4.2)) '@jridgewell/sourcemap-codec': 1.5.0 @@ -3014,9 +3014,9 @@ snapshots: postcss-safe-parser: 6.0.0(postcss@8.4.49) postcss-selector-parser: 6.1.2 semver: 7.6.3 - svelte-eslint-parser: 0.43.0(svelte@5.17.1) + svelte-eslint-parser: 0.43.0(svelte@5.19.1) optionalDependencies: - svelte: 5.17.1 + svelte: 5.19.1 transitivePeerDependencies: - ts-node @@ -3099,7 +3099,7 @@ snapshots: dependencies: estraverse: 5.3.0 - esrap@1.3.2: + esrap@1.4.3: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 @@ -3683,16 +3683,16 @@ snapshots: prelude-ls@1.2.1: {} - prettier-plugin-svelte@3.3.2(prettier@3.4.2)(svelte@5.17.1): + prettier-plugin-svelte@3.3.2(prettier@3.4.2)(svelte@5.19.1): dependencies: prettier: 3.4.2 - svelte: 5.17.1 + svelte: 5.19.1 - prettier-plugin-tailwindcss@0.6.9(prettier-plugin-svelte@3.3.2(prettier@3.4.2)(svelte@5.17.1))(prettier@3.4.2): + prettier-plugin-tailwindcss@0.6.9(prettier-plugin-svelte@3.3.2(prettier@3.4.2)(svelte@5.19.1))(prettier@3.4.2): dependencies: prettier: 3.4.2 optionalDependencies: - prettier-plugin-svelte: 3.3.2(prettier@3.4.2)(svelte@5.17.1) + prettier-plugin-svelte: 3.3.2(prettier@3.4.2)(svelte@5.19.1) prettier@3.4.2: {} @@ -3828,19 +3828,19 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-check@4.1.3(picomatch@4.0.2)(svelte@5.17.1)(typescript@5.7.3): + svelte-check@4.1.3(picomatch@4.0.2)(svelte@5.19.1)(typescript@5.7.3): dependencies: '@jridgewell/trace-mapping': 0.3.25 chokidar: 4.0.3 fdir: 6.4.2(picomatch@4.0.2) picocolors: 1.1.1 sade: 1.8.1 - svelte: 5.17.1 + svelte: 5.19.1 typescript: 5.7.3 transitivePeerDependencies: - picomatch - svelte-eslint-parser@0.43.0(svelte@5.17.1): + svelte-eslint-parser@0.43.0(svelte@5.19.1): dependencies: eslint-scope: 7.2.2 eslint-visitor-keys: 3.4.3 @@ -3848,9 +3848,9 @@ snapshots: postcss: 8.4.49 postcss-scss: 4.0.9(postcss@8.4.49) optionalDependencies: - svelte: 5.17.1 + svelte: 5.19.1 - svelte@5.17.1: + svelte@5.19.1: dependencies: '@ampproject/remapping': 2.3.0 '@jridgewell/sourcemap-codec': 1.5.0 @@ -3861,7 +3861,7 @@ snapshots: axobject-query: 4.1.0 clsx: 2.1.1 esm-env: 1.2.2 - esrap: 1.3.2 + esrap: 1.4.3 is-reference: 3.0.3 locate-character: 3.0.0 magic-string: 0.30.17 @@ -3957,7 +3957,7 @@ snapshots: undici-types@6.20.0: {} - unplugin-icons@0.22.0(svelte@5.17.1): + unplugin-icons@0.22.0(svelte@5.19.1): dependencies: '@antfu/install-pkg': 0.5.0 '@antfu/utils': 0.7.10 @@ -3967,7 +3967,7 @@ snapshots: local-pkg: 0.5.1 unplugin: 2.1.2 optionalDependencies: - svelte: 5.17.1 + svelte: 5.19.1 transitivePeerDependencies: - supports-color diff --git a/src/lib/components/BottomSheet.svelte b/src/lib/components/BottomSheet.svelte index 3d3fbd6..a283957 100644 --- a/src/lib/components/BottomSheet.svelte +++ b/src/lib/components/BottomSheet.svelte @@ -28,7 +28,7 @@
e.stopPropagation()} - class="flex max-h-[70vh] min-h-[30vh] rounded-t-2xl bg-white px-4" + class="flex max-h-[70vh] min-h-[30vh] overflow-y-auto rounded-t-2xl bg-white px-4" transition:fly={{ y: 100, duration: 200 }} > {@render children?.()} diff --git a/src/lib/molecules/Categories/Categories.svelte b/src/lib/molecules/Categories/Categories.svelte new file mode 100644 index 0000000..2fdbfad --- /dev/null +++ b/src/lib/molecules/Categories/Categories.svelte @@ -0,0 +1,19 @@ + + +
+ {#each categories as category} + + {/each} +
diff --git a/src/lib/organisms/Category/SubCategory.svelte b/src/lib/molecules/Categories/Category.svelte similarity index 93% rename from src/lib/organisms/Category/SubCategory.svelte rename to src/lib/molecules/Categories/Category.svelte index 212b591..18d6a83 100644 --- a/src/lib/organisms/Category/SubCategory.svelte +++ b/src/lib/molecules/Categories/Category.svelte @@ -1,14 +1,14 @@ + +
+ {#snippet subCategoryCreate()} + +
+ +

카테고리 추가하기

+
+
+ {/snippet} + + {#if subCategoryCreatePosition === "top"} + {@render subCategoryCreate()} + {/if} + {#key info} + + {/key} + {#if subCategoryCreatePosition === "bottom"} + {@render subCategoryCreate()} + {/if} +
diff --git a/src/lib/organisms/Category/Category.svelte b/src/lib/organisms/Category/Category.svelte index cfe1809..b3cbd59 100644 --- a/src/lib/organisms/Category/Category.svelte +++ b/src/lib/organisms/Category/Category.svelte @@ -1,23 +1,21 @@ + + +
+ {#if $category} + + (category = getCategoryInfo(id, $masterKeyStore?.get(1)?.key!))} + subCategoryCreatePosition="top" + /> + {#if $category.id !== "root"} + + + + {/if} + {/if} +
+
diff --git a/src/routes/(fullscreen)/file/[id]/service.ts b/src/routes/(fullscreen)/file/[id]/service.ts index fcc5ce7..f48c16e 100644 --- a/src/routes/(fullscreen)/file/[id]/service.ts +++ b/src/routes/(fullscreen)/file/[id]/service.ts @@ -1,4 +1,6 @@ +import { callPostApi } from "$lib/hooks"; import { getFileCache, storeFileCache, downloadFile } from "$lib/modules/file"; +import type { CategoryFileAddRequest } from "$lib/server/schemas"; export const requestFileDownload = async ( fileId: number, @@ -12,3 +14,10 @@ export const requestFileDownload = async ( storeFileCache(fileId, fileBuffer); // Intended return fileBuffer; }; + +export const requestFileAdditionToCategory = async (fileId: number, categoryId: number) => { + const res = await callPostApi(`/api/category/${categoryId}/file/add`, { + file: fileId, + }); + return res.ok; +}; From 4c0d668cc1fcb8a11ce63364a547358e83fc0f24 Mon Sep 17 00:00:00 2001 From: static Date: Wed, 22 Jan 2025 13:50:36 +0900 Subject: [PATCH 22/53] =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=EC=97=90=20=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EB=B0=8F=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=EC=97=90=20=EC=B6=94=EA=B0=80=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/modules/filesystem.ts | 4 +- .../molecules/Categories/Categories.svelte | 12 +-- src/lib/server/db/file.ts | 9 +++ src/lib/server/schemas/file.ts | 1 + src/lib/server/services/file.ts | 3 + .../(fullscreen)/file/[id]/+page.svelte | 77 +++++++++++++------ src/routes/api/file/[id]/+server.ts | 2 + 7 files changed, 80 insertions(+), 28 deletions(-) diff --git a/src/lib/modules/filesystem.ts b/src/lib/modules/filesystem.ts index cfafbbe..f098456 100644 --- a/src/lib/modules/filesystem.ts +++ b/src/lib/modules/filesystem.ts @@ -46,6 +46,7 @@ export interface FileInfo { name: string; createdAt?: Date; lastModifiedAt: Date; + categoryIds: number[]; } type CategoryId = "root" | number; @@ -160,7 +161,7 @@ const fetchFileInfoFromIndexedDB = async (id: number, info: Writable { @@ -204,6 +205,7 @@ const fetchFileInfoFromServer = async ( name, createdAt, lastModifiedAt, + categoryIds: metadata.categories, }); await storeFileInfo({ id, diff --git a/src/lib/molecules/Categories/Categories.svelte b/src/lib/molecules/Categories/Categories.svelte index 2fdbfad..ef21e0e 100644 --- a/src/lib/molecules/Categories/Categories.svelte +++ b/src/lib/molecules/Categories/Categories.svelte @@ -12,8 +12,10 @@ let { categories, onCategoryClick }: Props = $props(); -
- {#each categories as category} - - {/each} -
+{#if categories.length > 0} +
+ {#each categories as category} + + {/each} +
+{/if} diff --git a/src/lib/server/db/file.ts b/src/lib/server/db/file.ts index 8b7819b..46c8d66 100644 --- a/src/lib/server/db/file.ts +++ b/src/lib/server/db/file.ts @@ -441,6 +441,15 @@ export const addFileToCategory = async (fileId: number, categoryId: number) => { }); }; +export const getAllFileCategories = async (fileId: number) => { + const categories = await db + .selectFrom("file_category") + .select("category_id") + .where("file_id", "=", fileId) + .execute(); + return categories.map(({ category_id }) => ({ id: category_id })); +}; + export const removeFileFromCategory = async (fileId: number, categoryId: number) => { await db.transaction().execute(async (trx) => { const res = await trx diff --git a/src/lib/server/schemas/file.ts b/src/lib/server/schemas/file.ts index 8f552f7..ed0af94 100644 --- a/src/lib/server/schemas/file.ts +++ b/src/lib/server/schemas/file.ts @@ -18,6 +18,7 @@ export const fileInfoResponse = z.object({ createdAtIv: z.string().base64().nonempty().optional(), lastModifiedAt: z.string().base64().nonempty(), lastModifiedAtIv: z.string().base64().nonempty(), + categories: z.number().int().positive().array(), }); export type FileInfoResponse = z.infer; diff --git a/src/lib/server/services/file.ts b/src/lib/server/services/file.ts index 0f2d371..519bdfd 100644 --- a/src/lib/server/services/file.ts +++ b/src/lib/server/services/file.ts @@ -13,6 +13,7 @@ import { getFile, setFileEncName, unregisterFile, + getAllFileCategories, type NewFile, } from "$lib/server/db/file"; import type { Ciphertext } from "$lib/server/db/schema"; @@ -24,6 +25,7 @@ export const getFileInformation = async (userId: number, fileId: number) => { error(404, "Invalid file id"); } + const categories = await getAllFileCategories(fileId); return { parentId: file.parentId ?? ("root" as const), mekVersion: file.mekVersion, @@ -34,6 +36,7 @@ export const getFileInformation = async (userId: number, fileId: number) => { encName: file.encName, encCreatedAt: file.encCreatedAt, encLastModifiedAt: file.encLastModifiedAt, + categories: categories.map(({ id }) => id), }; }; diff --git a/src/routes/(fullscreen)/file/[id]/+page.svelte b/src/routes/(fullscreen)/file/[id]/+page.svelte index 2b27d89..89dc04d 100644 --- a/src/routes/(fullscreen)/file/[id]/+page.svelte +++ b/src/routes/(fullscreen)/file/[id]/+page.svelte @@ -2,18 +2,29 @@ import FileSaver from "file-saver"; import { untrack } from "svelte"; import { get, type Writable } from "svelte/store"; + import { goto } from "$app/navigation"; import { TopBar } from "$lib/components"; - import { getFileInfo, type FileInfo } from "$lib/modules/filesystem"; + import { EntryButton } from "$lib/components/buttons"; + import { + getFileInfo, + getCategoryInfo, + type FileInfo, + type CategoryInfo, + } from "$lib/modules/filesystem"; + import Categories from "$lib/molecules/Categories"; import { fileDownloadStatusStore, isFileDownloading, masterKeyStore } from "$lib/stores"; import AddToCategoryBottomSheet from "./AddToCategoryBottomSheet.svelte"; import DownloadStatus from "./DownloadStatus.svelte"; import { requestFileDownload, requestFileAdditionToCategory } from "./service"; + import IconAddCircle from "~icons/material-symbols/add-circle"; + let { data } = $props(); let info: Writable | undefined = $state(); + let categories: Writable[] = $state([]); - let isAddToCategoryBottomSheetOpen = $state(true); + let isAddToCategoryBottomSheetOpen = $state(false); const downloadStatus = $derived( $fileDownloadStatusStore.find((statusStore) => { @@ -50,6 +61,7 @@ const addToCategory = async (categoryId: number) => { await requestFileAdditionToCategory(data.id, categoryId); isAddToCategoryBottomSheetOpen = false; + info = getFileInfo(data.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME }; $effect(() => { @@ -58,6 +70,13 @@ viewerType = undefined; }); + $effect(() => { + categories = + $info?.categoryIds.map((id) => getCategoryInfo(id, $masterKeyStore?.get(1)?.key!)) ?? []; + + // TODO: Sorting + }); + $effect(() => { if ($info && $info.dataKey && $info.contentIv) { untrack(() => { @@ -89,28 +108,42 @@
- -
- {#snippet viewerLoading(message: string)} -
-

{message}

-
- {/snippet} +
+ + {#if $info && viewerType} +
+ {#snippet viewerLoading(message: string)} +

{message}

+ {/snippet} - {#if $info && viewerType === "image"} - {#if fileBlobUrl} - {$info.name} - {:else} - {@render viewerLoading("이미지를 불러오고 있어요.")} - {/if} - {:else if viewerType === "video"} - {#if fileBlobUrl} - - - {:else} - {@render viewerLoading("비디오를 불러오고 있어요.")} - {/if} + {#if viewerType === "image"} + {#if fileBlobUrl} + {$info.name} + {:else} + {@render viewerLoading("이미지를 불러오고 있어요.")} + {/if} + {:else if viewerType === "video"} + {#if fileBlobUrl} + + + {:else} + {@render viewerLoading("비디오를 불러오고 있어요.")} + {/if} + {/if} +
{/if} +
+

카테고리

+
+ goto(`/category/${id}`)} /> + (isAddToCategoryBottomSheetOpen = true)}> +
+ +

카테고리에 추가하기

+
+
+
+
diff --git a/src/routes/api/file/[id]/+server.ts b/src/routes/api/file/[id]/+server.ts index 892f62b..23e9385 100644 --- a/src/routes/api/file/[id]/+server.ts +++ b/src/routes/api/file/[id]/+server.ts @@ -26,6 +26,7 @@ export const GET: RequestHandler = async ({ locals, params }) => { encName, encCreatedAt, encLastModifiedAt, + categories, } = await getFileInformation(userId, id); return json( fileInfoResponse.parse({ @@ -41,6 +42,7 @@ export const GET: RequestHandler = async ({ locals, params }) => { createdAtIv: encCreatedAt?.iv, lastModifiedAt: encLastModifiedAt.ciphertext, lastModifiedAtIv: encLastModifiedAt.iv, + categories, } satisfies FileInfoResponse), ); }; From 368868910d808ba01421420ec4171d71fd96c81e Mon Sep 17 00:00:00 2001 From: static Date: Wed, 22 Jan 2025 15:39:48 +0900 Subject: [PATCH 23/53] =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C,=20=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=ED=8C=8C=EC=9D=BC=20=EC=82=AD=EC=A0=9C=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../molecules/Categories/Categories.svelte | 13 +++- src/lib/molecules/Categories/Category.svelte | 30 ++++++---- src/lib/molecules/SubCategories.svelte | 14 ++++- src/lib/organisms/Category/Category.svelte | 30 ++++++---- .../organisms}/CreateCategoryModal.svelte | 0 src/lib/server/db/category.ts | 7 ++- src/lib/server/schemas/category.ts | 12 ++++ src/lib/server/services/category.ts | 60 ++++++++++++++++++- src/lib/services/category.ts | 21 +++++++ .../(fullscreen)/file/[id]/+page.svelte | 19 +++++- .../file/[id]/AddToCategoryBottomSheet.svelte | 22 ++++++- src/routes/(fullscreen)/file/[id]/service.ts | 12 +++- .../(main)/category/[[id]]/+page.svelte | 57 +++++++++++++++++- .../[[id]]/CategoryMenuBottomSheet.svelte | 57 ++++++++++++++++++ .../[[id]]/DeleteCategoryModal.svelte | 55 +++++++++++++++++ .../[[id]]/RenameCategoryModal.svelte | 47 +++++++++++++++ src/routes/(main)/category/[[id]]/service.ts | 35 +++++------ .../api/category/[id]/delete/+server.ts | 20 +++++++ .../api/category/[id]/file/remove/+server.ts | 25 ++++++++ .../api/category/[id]/rename/+server.ts | 25 ++++++++ 20 files changed, 507 insertions(+), 54 deletions(-) rename src/{routes/(main)/category/[[id]] => lib/organisms}/CreateCategoryModal.svelte (100%) create mode 100644 src/lib/services/category.ts create mode 100644 src/routes/(main)/category/[[id]]/CategoryMenuBottomSheet.svelte create mode 100644 src/routes/(main)/category/[[id]]/DeleteCategoryModal.svelte create mode 100644 src/routes/(main)/category/[[id]]/RenameCategoryModal.svelte create mode 100644 src/routes/api/category/[id]/delete/+server.ts create mode 100644 src/routes/api/category/[id]/file/remove/+server.ts create mode 100644 src/routes/api/category/[id]/rename/+server.ts diff --git a/src/lib/molecules/Categories/Categories.svelte b/src/lib/molecules/Categories/Categories.svelte index ef21e0e..0c07e2b 100644 --- a/src/lib/molecules/Categories/Categories.svelte +++ b/src/lib/molecules/Categories/Categories.svelte @@ -1,4 +1,6 @@ {#if categories.length > 0}
{#each categories as category} - + {/each}
{/if} diff --git a/src/lib/molecules/Categories/Category.svelte b/src/lib/molecules/Categories/Category.svelte index 18d6a83..ea2c392 100644 --- a/src/lib/molecules/Categories/Category.svelte +++ b/src/lib/molecules/Categories/Category.svelte @@ -1,17 +1,20 @@ @@ -40,13 +48,15 @@

{$info.name}

- + {#if MenuIcon && onMenuClick} + + {/if}
{/if} diff --git a/src/lib/molecules/SubCategories.svelte b/src/lib/molecules/SubCategories.svelte index 945e29c..7b08629 100644 --- a/src/lib/molecules/SubCategories.svelte +++ b/src/lib/molecules/SubCategories.svelte @@ -1,5 +1,6 @@ @@ -30,6 +43,7 @@ info={$category} onSubCategoryClick={({ id }) => (category = getCategoryInfo(id, $masterKeyStore?.get(1)?.key!))} + onSubCategoryCreateClick={() => (isCreateCategoryModalOpen = true)} subCategoryCreatePosition="top" /> {#if $category.id !== "root"} @@ -40,3 +54,5 @@ {/if}
+ + diff --git a/src/routes/(fullscreen)/file/[id]/service.ts b/src/routes/(fullscreen)/file/[id]/service.ts index f48c16e..e45a108 100644 --- a/src/routes/(fullscreen)/file/[id]/service.ts +++ b/src/routes/(fullscreen)/file/[id]/service.ts @@ -1,6 +1,8 @@ import { callPostApi } from "$lib/hooks"; import { getFileCache, storeFileCache, downloadFile } from "$lib/modules/file"; -import type { CategoryFileAddRequest } from "$lib/server/schemas"; +import type { CategoryFileAddRequest, CategoryFileRemoveRequest } from "$lib/server/schemas"; + +export { requestCategoryCreation } from "$lib/services/category"; export const requestFileDownload = async ( fileId: number, @@ -21,3 +23,11 @@ export const requestFileAdditionToCategory = async (fileId: number, categoryId: }); return res.ok; }; + +export const requestFileRemovalFromCategory = async (fileId: number, categoryId: number) => { + const res = await callPostApi( + `/api/category/${categoryId}/file/remove`, + { file: fileId }, + ); + return res.ok; +}; diff --git a/src/routes/(main)/category/[[id]]/+page.svelte b/src/routes/(main)/category/[[id]]/+page.svelte index aa551f1..cbd0e2e 100644 --- a/src/routes/(main)/category/[[id]]/+page.svelte +++ b/src/routes/(main)/category/[[id]]/+page.svelte @@ -3,16 +3,28 @@ import { goto } from "$app/navigation"; import { TopBar } from "$lib/components"; import { getCategoryInfo, type CategoryInfo } from "$lib/modules/filesystem"; + import type { SelectedCategory } from "$lib/molecules/Categories"; import Category from "$lib/organisms/Category"; + import CreateCategoryModal from "$lib/organisms/CreateCategoryModal.svelte"; import { masterKeyStore } from "$lib/stores"; - import CreateCategoryModal from "./CreateCategoryModal.svelte"; - import { requestCategoryCreation } from "./service"; + import CategoryMenuBottomSheet from "./CategoryMenuBottomSheet.svelte"; + import DeleteCategoryModal from "./DeleteCategoryModal.svelte"; + import RenameCategoryModal from "./RenameCategoryModal.svelte"; + import { + requestCategoryCreation, + requestCategoryRename, + requestCategoryDeletion, + } from "./service"; let { data } = $props(); let info: Writable | undefined = $state(); + let selectedSubCategory: SelectedCategory | undefined = $state(); let isCreateCategoryModalOpen = $state(false); + let isSubCategoryMenuBottomSheetOpen = $state(false); + let isRenameCategoryModalOpen = $state(false); + let isDeleteCategoryModalOpen = $state(false); const createCategory = async (name: string) => { await requestCategoryCreation(name, data.id, $masterKeyStore?.get(1)!); @@ -39,6 +51,10 @@ info={$info} onSubCategoryClick={({ id }) => goto(`/category/${id}`)} onSubCategoryCreateClick={() => (isCreateCategoryModalOpen = true)} + onSubCategoryMenuClick={(subCategory) => { + selectedSubCategory = subCategory; + isSubCategoryMenuBottomSheetOpen = true; + }} onFileClick={({ id }) => goto(`/file/${id}`)} /> {/if} @@ -46,3 +62,40 @@
+ + { + isSubCategoryMenuBottomSheetOpen = false; + isRenameCategoryModalOpen = true; + }} + onDeleteClick={() => { + isSubCategoryMenuBottomSheetOpen = false; + isDeleteCategoryModalOpen = true; + }} +/> + { + if (selectedSubCategory) { + await requestCategoryRename(selectedSubCategory, newName); + info = getCategoryInfo(data.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME + return true; + } + return false; + }} +/> + { + if (selectedSubCategory) { + await requestCategoryDeletion(selectedSubCategory); + info = getCategoryInfo(data.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME + return true; + } + return false; + }} +/> diff --git a/src/routes/(main)/category/[[id]]/CategoryMenuBottomSheet.svelte b/src/routes/(main)/category/[[id]]/CategoryMenuBottomSheet.svelte new file mode 100644 index 0000000..200501e --- /dev/null +++ b/src/routes/(main)/category/[[id]]/CategoryMenuBottomSheet.svelte @@ -0,0 +1,57 @@ + + + +
+ {#if selectedCategory} + {@const { name } = selectedCategory} +
+
+ +
+

+ {name} +

+
+
+ {/if} + +
+ +

이름 바꾸기

+
+
+ +
+ +

삭제하기

+
+
+
+
diff --git a/src/routes/(main)/category/[[id]]/DeleteCategoryModal.svelte b/src/routes/(main)/category/[[id]]/DeleteCategoryModal.svelte new file mode 100644 index 0000000..880cbda --- /dev/null +++ b/src/routes/(main)/category/[[id]]/DeleteCategoryModal.svelte @@ -0,0 +1,55 @@ + + + + {#if selectedCategory} + {@const { name } = selectedCategory} + {@const nameShort = name.length > 20 ? `${name.slice(0, 20)}...` : name} +
+
+

+ '{nameShort}' 카테고리를 삭제할까요? +

+

+ 모든 하위 카테고리도 함께 삭제돼요.
+ 하지만 카테고리에 추가된 파일들은 삭제되지 않아요. +

+
+
+ + +
+
+ {/if} +
diff --git a/src/routes/(main)/category/[[id]]/RenameCategoryModal.svelte b/src/routes/(main)/category/[[id]]/RenameCategoryModal.svelte new file mode 100644 index 0000000..dbb13c6 --- /dev/null +++ b/src/routes/(main)/category/[[id]]/RenameCategoryModal.svelte @@ -0,0 +1,47 @@ + + + +

이름 바꾸기

+
+ +
+
+ + +
+
diff --git a/src/routes/(main)/category/[[id]]/service.ts b/src/routes/(main)/category/[[id]]/service.ts index c2018ed..8a5d9f8 100644 --- a/src/routes/(main)/category/[[id]]/service.ts +++ b/src/routes/(main)/category/[[id]]/service.ts @@ -1,21 +1,22 @@ import { callPostApi } from "$lib/hooks"; -import { generateDataKey, wrapDataKey, encryptString } from "$lib/modules/crypto"; -import type { CategoryCreateRequest } from "$lib/server/schemas"; -import type { MasterKey } from "$lib/stores"; +import { encryptString } from "$lib/modules/crypto"; +import type { SelectedCategory } from "$lib/molecules/Categories"; +import type { CategoryRenameRequest } from "$lib/server/schemas"; -export const requestCategoryCreation = async ( - name: string, - parentId: "root" | number, - masterKey: MasterKey, -) => { - const { dataKey, dataKeyVersion } = await generateDataKey(); - const nameEncrypted = await encryptString(name, dataKey); - await callPostApi("/api/category/create", { - parent: parentId, - mekVersion: masterKey.version, - dek: await wrapDataKey(dataKey, masterKey.key), - dekVersion: dataKeyVersion.toISOString(), - name: nameEncrypted.ciphertext, - nameIv: nameEncrypted.iv, +export { requestCategoryCreation } from "$lib/services/category"; + +export const requestCategoryRename = async (category: SelectedCategory, newName: string) => { + const newNameEncrypted = await encryptString(newName, category.dataKey); + + const res = await callPostApi(`/api/category/${category.id}/rename`, { + dekVersion: category.dataKeyVersion.toISOString(), + name: newNameEncrypted.ciphertext, + nameIv: newNameEncrypted.iv, }); + return res.ok; +}; + +export const requestCategoryDeletion = async (category: SelectedCategory) => { + const res = await callPostApi(`/api/category/${category.id}/delete`); + return res.ok; }; diff --git a/src/routes/api/category/[id]/delete/+server.ts b/src/routes/api/category/[id]/delete/+server.ts new file mode 100644 index 0000000..cbbe356 --- /dev/null +++ b/src/routes/api/category/[id]/delete/+server.ts @@ -0,0 +1,20 @@ +import { error, text } from "@sveltejs/kit"; +import { z } from "zod"; +import { authorize } from "$lib/server/modules/auth"; +import { deleteCategory } from "$lib/server/services/category"; +import type { RequestHandler } from "./$types"; + +export const POST: RequestHandler = async ({ locals, params }) => { + const { userId } = await authorize(locals, "activeClient"); + + const zodRes = z + .object({ + id: z.coerce.number().int().positive(), + }) + .safeParse(params); + if (!zodRes.success) error(400, "Invalid path parameters"); + const { id } = zodRes.data; + + await deleteCategory(userId, id); + return text("Category deleted", { headers: { "Content-Type": "text/plain" } }); +}; diff --git a/src/routes/api/category/[id]/file/remove/+server.ts b/src/routes/api/category/[id]/file/remove/+server.ts new file mode 100644 index 0000000..6fdcccf --- /dev/null +++ b/src/routes/api/category/[id]/file/remove/+server.ts @@ -0,0 +1,25 @@ +import { error, text } from "@sveltejs/kit"; +import { z } from "zod"; +import { authorize } from "$lib/server/modules/auth"; +import { categoryFileRemoveRequest } from "$lib/server/schemas"; +import { removeCategoryFile } from "$lib/server/services/category"; +import type { RequestHandler } from "./$types"; + +export const POST: RequestHandler = async ({ locals, params, request }) => { + const { userId } = await authorize(locals, "activeClient"); + + const paramsZodRes = z + .object({ + id: z.coerce.number().int().positive(), + }) + .safeParse(params); + if (!paramsZodRes.success) error(400, "Invalid path parameters"); + const { id } = paramsZodRes.data; + + const bodyZodRes = categoryFileRemoveRequest.safeParse(await request.json()); + if (!bodyZodRes.success) error(400, "Invalid request body"); + const { file } = bodyZodRes.data; + + await removeCategoryFile(userId, id, file); + return text("File removed", { headers: { "Content-Type": "text/plain" } }); +}; diff --git a/src/routes/api/category/[id]/rename/+server.ts b/src/routes/api/category/[id]/rename/+server.ts new file mode 100644 index 0000000..5351544 --- /dev/null +++ b/src/routes/api/category/[id]/rename/+server.ts @@ -0,0 +1,25 @@ +import { error, text } from "@sveltejs/kit"; +import { z } from "zod"; +import { authorize } from "$lib/server/modules/auth"; +import { categoryRenameRequest } from "$lib/server/schemas"; +import { renameCategory } from "$lib/server/services/category"; +import type { RequestHandler } from "./$types"; + +export const POST: RequestHandler = async ({ locals, params, request }) => { + const { userId } = await authorize(locals, "activeClient"); + + const paramsZodRes = z + .object({ + id: z.coerce.number().int().positive(), + }) + .safeParse(params); + if (!paramsZodRes.success) error(400, "Invalid path parameters"); + const { id } = paramsZodRes.data; + + const bodyZodRes = categoryRenameRequest.safeParse(await request.json()); + if (!bodyZodRes.success) error(400, "Invalid request body"); + const { dekVersion, name, nameIv } = bodyZodRes.data; + + await renameCategory(userId, id, new Date(dekVersion), { ciphertext: name, iv: nameIv }); + return text("Category renamed", { headers: { "Content-Type": "text/plain" } }); +}; From 8f8bad6d10b20690a9aac4ed274ce10625e6e3fd Mon Sep 17 00:00:00 2001 From: static Date: Wed, 22 Jan 2025 15:48:46 +0900 Subject: [PATCH 24/53] =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=EC=97=90=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20=EB=B2=84=ED=8A=BC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/organisms/Category/Category.svelte | 4 +++- src/lib/organisms/Category/File.svelte | 24 ++++++++++++------- src/lib/services/category.ts | 10 +++++++- .../(fullscreen)/file/[id]/+page.svelte | 2 +- src/routes/(fullscreen)/file/[id]/service.ts | 12 ++-------- .../(main)/category/[[id]]/+page.svelte | 7 +++++- src/routes/(main)/category/[[id]]/service.ts | 2 +- 7 files changed, 37 insertions(+), 24 deletions(-) diff --git a/src/lib/organisms/Category/Category.svelte b/src/lib/organisms/Category/Category.svelte index 075a01a..d70a6d3 100644 --- a/src/lib/organisms/Category/Category.svelte +++ b/src/lib/organisms/Category/Category.svelte @@ -12,6 +12,7 @@ interface Props { info: CategoryInfo; onFileClick: (file: SelectedFile) => void; + onFileRemoveClick: (file: SelectedFile) => void; onSubCategoryClick: (subCategory: SelectedCategory) => void; onSubCategoryCreateClick: () => void; onSubCategoryMenuClick: (subCategory: SelectedCategory) => void; @@ -20,6 +21,7 @@ let { info, onFileClick, + onFileRemoveClick, onSubCategoryClick, onSubCategoryCreateClick, onSubCategoryMenuClick, @@ -53,7 +55,7 @@
{#key info} {#each files as file} - + {:else}

이 카테고리에 추가된 파일이 없어요.

{/each} diff --git a/src/lib/organisms/Category/File.svelte b/src/lib/organisms/Category/File.svelte index c3b9b97..f15f9b0 100644 --- a/src/lib/organisms/Category/File.svelte +++ b/src/lib/organisms/Category/File.svelte @@ -4,14 +4,15 @@ import type { SelectedFile } from "./service"; import IconDraft from "~icons/material-symbols/draft"; - import IconMoreVert from "~icons/material-symbols/more-vert"; + import IconClose from "~icons/material-symbols/close"; interface Props { info: Writable; onclick: (selectedFile: SelectedFile) => void; + onRemoveClick: (selectedFile: SelectedFile) => void; } - let { info, onclick }: Props = $props(); + let { info, onclick, onRemoveClick }: Props = $props(); const openFile = () => { const { id, dataKey, dataKeyVersion, name } = $info as FileInfo; @@ -22,10 +23,15 @@ }, 100); }; - const openMenu = (e: Event) => { + const removeFile = (e: Event) => { e.stopPropagation(); - // TODO + const { id, dataKey, dataKeyVersion, name } = $info as FileInfo; + if (!dataKey || !dataKeyVersion) return; // TODO: Error handling + + setTimeout(() => { + onRemoveClick({ id, dataKey, dataKeyVersion, name }); + }, 100); }; @@ -41,21 +47,21 @@ {$info.name}

{/if} diff --git a/src/lib/services/category.ts b/src/lib/services/category.ts index c2018ed..ab574f5 100644 --- a/src/lib/services/category.ts +++ b/src/lib/services/category.ts @@ -1,6 +1,6 @@ import { callPostApi } from "$lib/hooks"; import { generateDataKey, wrapDataKey, encryptString } from "$lib/modules/crypto"; -import type { CategoryCreateRequest } from "$lib/server/schemas"; +import type { CategoryCreateRequest, CategoryFileRemoveRequest } from "$lib/server/schemas"; import type { MasterKey } from "$lib/stores"; export const requestCategoryCreation = async ( @@ -19,3 +19,11 @@ export const requestCategoryCreation = async ( nameIv: nameEncrypted.iv, }); }; + +export const requestFileRemovalFromCategory = async (fileId: number, categoryId: number) => { + const res = await callPostApi( + `/api/category/${categoryId}/file/remove`, + { file: fileId }, + ); + return res.ok; +}; diff --git a/src/routes/(fullscreen)/file/[id]/+page.svelte b/src/routes/(fullscreen)/file/[id]/+page.svelte index 7097078..902d09e 100644 --- a/src/routes/(fullscreen)/file/[id]/+page.svelte +++ b/src/routes/(fullscreen)/file/[id]/+page.svelte @@ -16,9 +16,9 @@ import AddToCategoryBottomSheet from "./AddToCategoryBottomSheet.svelte"; import DownloadStatus from "./DownloadStatus.svelte"; import { + requestFileRemovalFromCategory, requestFileDownload, requestFileAdditionToCategory, - requestFileRemovalFromCategory, } from "./service"; import IconClose from "~icons/material-symbols/close"; diff --git a/src/routes/(fullscreen)/file/[id]/service.ts b/src/routes/(fullscreen)/file/[id]/service.ts index e45a108..43f0134 100644 --- a/src/routes/(fullscreen)/file/[id]/service.ts +++ b/src/routes/(fullscreen)/file/[id]/service.ts @@ -1,8 +1,8 @@ import { callPostApi } from "$lib/hooks"; import { getFileCache, storeFileCache, downloadFile } from "$lib/modules/file"; -import type { CategoryFileAddRequest, CategoryFileRemoveRequest } from "$lib/server/schemas"; +import type { CategoryFileAddRequest } from "$lib/server/schemas"; -export { requestCategoryCreation } from "$lib/services/category"; +export { requestCategoryCreation, requestFileRemovalFromCategory } from "$lib/services/category"; export const requestFileDownload = async ( fileId: number, @@ -23,11 +23,3 @@ export const requestFileAdditionToCategory = async (fileId: number, categoryId: }); return res.ok; }; - -export const requestFileRemovalFromCategory = async (fileId: number, categoryId: number) => { - const res = await callPostApi( - `/api/category/${categoryId}/file/remove`, - { file: fileId }, - ); - return res.ok; -}; diff --git a/src/routes/(main)/category/[[id]]/+page.svelte b/src/routes/(main)/category/[[id]]/+page.svelte index cbd0e2e..163b890 100644 --- a/src/routes/(main)/category/[[id]]/+page.svelte +++ b/src/routes/(main)/category/[[id]]/+page.svelte @@ -12,6 +12,7 @@ import RenameCategoryModal from "./RenameCategoryModal.svelte"; import { requestCategoryCreation, + requestFileRemovalFromCategory, requestCategoryRename, requestCategoryDeletion, } from "./service"; @@ -49,13 +50,17 @@ {#if $info} goto(`/file/${id}`)} + onFileRemoveClick={({ id }) => { + requestFileRemovalFromCategory(id, data.id as number); + info = getCategoryInfo(data.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME + }} onSubCategoryClick={({ id }) => goto(`/category/${id}`)} onSubCategoryCreateClick={() => (isCreateCategoryModalOpen = true)} onSubCategoryMenuClick={(subCategory) => { selectedSubCategory = subCategory; isSubCategoryMenuBottomSheetOpen = true; }} - onFileClick={({ id }) => goto(`/file/${id}`)} /> {/if}
diff --git a/src/routes/(main)/category/[[id]]/service.ts b/src/routes/(main)/category/[[id]]/service.ts index 8a5d9f8..a4ebe57 100644 --- a/src/routes/(main)/category/[[id]]/service.ts +++ b/src/routes/(main)/category/[[id]]/service.ts @@ -3,7 +3,7 @@ import { encryptString } from "$lib/modules/crypto"; import type { SelectedCategory } from "$lib/molecules/Categories"; import type { CategoryRenameRequest } from "$lib/server/schemas"; -export { requestCategoryCreation } from "$lib/services/category"; +export { requestCategoryCreation, requestFileRemovalFromCategory } from "$lib/services/category"; export const requestCategoryRename = async (category: SelectedCategory, newName: string) => { const newNameEncrypted = await encryptString(newName, category.dataKey); From f34764ffe02edd03d70967b29aa232bdc834dd69 Mon Sep 17 00:00:00 2001 From: static Date: Wed, 22 Jan 2025 22:24:44 +0900 Subject: [PATCH 25/53] =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=EC=97=90=EC=84=9C=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=EC=9D=84=20=EC=9E=AC=EA=B7=80=EC=A0=81?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=ED=91=9C=EC=8B=9C=ED=95=A0=20=EC=88=98=20?= =?UTF-8?q?=EC=9E=88=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/components/inputs/CheckBox.svelte | 23 ++++++++ src/lib/components/inputs/index.ts | 1 + src/lib/modules/filesystem.ts | 6 +- src/lib/organisms/Category/Category.svelte | 15 ++++- src/lib/server/db/file.ts | 56 ++++++++++--------- src/lib/server/schemas/category.ts | 7 ++- src/lib/server/services/category.ts | 6 +- .../(main)/category/[[id]]/+page.svelte | 3 + .../api/category/[id]/file/list/+server.ts | 22 ++++++-- 9 files changed, 98 insertions(+), 41 deletions(-) create mode 100644 src/lib/components/inputs/CheckBox.svelte diff --git a/src/lib/components/inputs/CheckBox.svelte b/src/lib/components/inputs/CheckBox.svelte new file mode 100644 index 0000000..813a7e0 --- /dev/null +++ b/src/lib/components/inputs/CheckBox.svelte @@ -0,0 +1,23 @@ + + + diff --git a/src/lib/components/inputs/index.ts b/src/lib/components/inputs/index.ts index c2c534d..47cb929 100644 --- a/src/lib/components/inputs/index.ts +++ b/src/lib/components/inputs/index.ts @@ -1 +1,2 @@ +export { default as CheckBox } from "./CheckBox.svelte"; export { default as TextInput } from "./TextInput.svelte"; diff --git a/src/lib/modules/filesystem.ts b/src/lib/modules/filesystem.ts index f098456..0e786ce 100644 --- a/src/lib/modules/filesystem.ts +++ b/src/lib/modules/filesystem.ts @@ -66,7 +66,7 @@ export type CategoryInfo = dataKeyVersion?: Date; name: string; subCategoryIds: number[]; - files: number[]; + files: { id: number; isRecursive: boolean }[]; }; const directoryInfoStore = new Map>(); @@ -256,7 +256,7 @@ const fetchCategoryInfoFromServer = async ( const { dataKey } = await unwrapDataKey(metadata!.dek, masterKey); const name = await decryptString(metadata!.name, metadata!.nameIv, dataKey); - res = await callGetApi(`/api/category/${id}/file/list`); + res = await callGetApi(`/api/category/${id}/file/list?recursive=true`); if (!res.ok) { throw new Error("Failed to fetch category files"); } @@ -269,7 +269,7 @@ const fetchCategoryInfoFromServer = async ( dataKeyVersion: new Date(metadata!.dekVersion), name, subCategoryIds: subCategories, - files, + files: files.map(({ file, isRecursive }) => ({ id: file, isRecursive })), }); } }; diff --git a/src/lib/organisms/Category/Category.svelte b/src/lib/organisms/Category/Category.svelte index d70a6d3..563293d 100644 --- a/src/lib/organisms/Category/Category.svelte +++ b/src/lib/organisms/Category/Category.svelte @@ -1,5 +1,6 @@ @@ -46,13 +46,15 @@

{$info.name}

- + {#if onRemoveClick} + + {/if}
{/if} From 606609d468ae67e9b3fbff79b7efa0d36b034b7e Mon Sep 17 00:00:00 2001 From: static Date: Thu, 23 Jan 2025 00:28:30 +0900 Subject: [PATCH 29/53] =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=9D=B4=EB=A6=84=20=EA=B8=B0=EB=B0=98=20?= =?UTF-8?q?=EC=A0=95=EB=A0=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/modules/util.ts | 29 ++++++++++++ .../molecules/Categories/Categories.svelte | 45 ++++++++++++++++--- src/lib/organisms/Category/Category.svelte | 38 +++++++++++++--- .../DirectoryEntries/DirectoryEntries.svelte | 2 +- .../[[id]]/DirectoryEntries/index.ts | 1 - .../[[id]]/DirectoryEntries/service.ts | 31 ------------- 6 files changed, 100 insertions(+), 46 deletions(-) delete mode 100644 src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts diff --git a/src/lib/modules/util.ts b/src/lib/modules/util.ts index 67e1b3b..0048e9e 100644 --- a/src/lib/modules/util.ts +++ b/src/lib/modules/util.ts @@ -27,3 +27,32 @@ export const formatNetworkSpeed = (speed: number) => { if (speed < 1000 * 1000 * 1000) return `${(speed / 1000 / 1000).toFixed(1)} Mbps`; return `${(speed / 1000 / 1000 / 1000).toFixed(1)} Gbps`; }; + +export enum SortBy { + NAME_ASC, + NAME_DESC, +} + +type SortFunc = (a?: string, b?: string) => number; + +const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" }); + +const sortByNameAsc: SortFunc = (a, b) => { + if (a && b) return collator.compare(a, b); + if (a) return -1; + if (b) return 1; + return 0; +}; + +const sortByNameDesc: SortFunc = (a, b) => -sortByNameAsc(a, b); + +export const sortEntries = (entries: T[], sortBy: SortBy) => { + let sortFunc: SortFunc; + if (sortBy === SortBy.NAME_ASC) { + sortFunc = sortByNameAsc; + } else { + sortFunc = sortByNameDesc; + } + + entries.sort((a, b) => sortFunc(a.name, b.name)); +}; diff --git a/src/lib/molecules/Categories/Categories.svelte b/src/lib/molecules/Categories/Categories.svelte index 0c07e2b..a11313e 100644 --- a/src/lib/molecules/Categories/Categories.svelte +++ b/src/lib/molecules/Categories/Categories.svelte @@ -1,8 +1,9 @@ -{#if categories.length > 0} +{#if categoriesWithName.length > 0}
- {#each categories as category} + {#each categoriesWithName as { info }} - import type { Writable } from "svelte/store"; + import { untrack } from "svelte"; + import { get, type Writable } from "svelte/store"; import { CheckBox } from "$lib/components/inputs"; import { getFileInfo, type FileInfo, type CategoryInfo } from "$lib/modules/filesystem"; + import { SortBy, sortEntries } from "$lib/modules/util"; import type { SelectedCategory } from "$lib/molecules/Categories"; import SubCategories from "$lib/molecules/SubCategories.svelte"; import { masterKeyStore } from "$lib/stores"; @@ -17,6 +19,7 @@ onSubCategoryClick: (subCategory: SelectedCategory) => void; onSubCategoryCreateClick: () => void; onSubCategoryMenuClick: (subCategory: SelectedCategory) => void; + sortBy?: SortBy; isFileRecursive: boolean; } @@ -27,21 +30,42 @@ onSubCategoryClick, onSubCategoryCreateClick, onSubCategoryMenuClick, + sortBy = SortBy.NAME_ASC, isFileRecursive = $bindable(), }: Props = $props(); - let files: { info: Writable; isRecursive: boolean }[] = $state([]); + let files: { name?: string; info: Writable; isRecursive: boolean }[] = $state( + [], + ); $effect(() => { files = info.files ?.filter(({ isRecursive }) => isFileRecursive || !isRecursive) - .map(({ id, isRecursive }) => ({ - info: getFileInfo(id, $masterKeyStore?.get(1)?.key!), - isRecursive, - })) ?? []; + .map(({ id, isRecursive }) => { + const info = getFileInfo(id, $masterKeyStore?.get(1)?.key!); + return { + name: get(info)?.name, + info, + isRecursive, + }; + }) ?? []; - // TODO: Sorting + const sort = () => { + sortEntries(files, sortBy); + }; + return untrack(() => { + sort(); + + const unsubscribes = files.map((file) => + file.info.subscribe((value) => { + if (file.name === value?.name) return; + file.name = value?.name; + sort(); + }), + ); + return () => unsubscribes.forEach((unsubscribe) => unsubscribe()); + }); }); diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/DirectoryEntries.svelte b/src/routes/(main)/directory/[[id]]/DirectoryEntries/DirectoryEntries.svelte index e482e38..baa9760 100644 --- a/src/routes/(main)/directory/[[id]]/DirectoryEntries/DirectoryEntries.svelte +++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/DirectoryEntries.svelte @@ -7,6 +7,7 @@ type DirectoryInfo, type FileInfo, } from "$lib/modules/filesystem"; + import { SortBy, sortEntries } from "$lib/modules/util"; import { fileUploadStatusStore, isFileUploading, @@ -15,7 +16,6 @@ } from "$lib/stores"; import File from "./File.svelte"; import SubDirectory from "./SubDirectory.svelte"; - import { SortBy, sortEntries } from "./service"; import UploadingFile from "./UploadingFile.svelte"; import type { SelectedDirectoryEntry } from "../service"; diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/index.ts b/src/routes/(main)/directory/[[id]]/DirectoryEntries/index.ts index 72ab278..075644e 100644 --- a/src/routes/(main)/directory/[[id]]/DirectoryEntries/index.ts +++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/index.ts @@ -1,2 +1 @@ export { default } from "./DirectoryEntries.svelte"; -export * from "./service"; diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts b/src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts deleted file mode 100644 index b797727..0000000 --- a/src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts +++ /dev/null @@ -1,31 +0,0 @@ -export enum SortBy { - NAME_ASC, - NAME_DESC, -} - -type SortFunc = (a?: string, b?: string) => number; - -const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" }); - -const sortByNameAsc: SortFunc = (a, b) => { - if (a && b) return collator.compare(a, b); - if (a) return -1; - if (b) return 1; - return 0; -}; - -const sortByNameDesc: SortFunc = (a, b) => -sortByNameAsc(a, b); - -export const sortEntries = ( - entries: T[], - sortBy: SortBy = SortBy.NAME_ASC, -) => { - let sortFunc: SortFunc; - if (sortBy === SortBy.NAME_ASC) { - sortFunc = sortByNameAsc; - } else { - sortFunc = sortByNameDesc; - } - - entries.sort((a, b) => sortFunc(a.name, b.name)); -}; From b8b87877d2a3dc5ccf2e44a0bbe9b3e8be857756 Mon Sep 17 00:00:00 2001 From: static Date: Thu, 23 Jan 2025 00:32:44 +0900 Subject: [PATCH 30/53] =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=EC=97=90=EC=84=9C=20=EB=B7=B0=EC=96=B4=20=EB=A1=9C?= =?UTF-8?q?=EB=94=A9=20=EB=A9=94=EC=84=B8=EC=A7=80=EB=A5=BC=20=EB=8D=94=20?= =?UTF-8?q?=EB=B9=A0=EB=A5=B4=EA=B2=8C=20=ED=91=9C=EC=8B=9C=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(fullscreen)/file/[id]/+page.svelte | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/routes/(fullscreen)/file/[id]/+page.svelte b/src/routes/(fullscreen)/file/[id]/+page.svelte index 902d09e..b86cc52 100644 --- a/src/routes/(fullscreen)/file/[id]/+page.svelte +++ b/src/routes/(fullscreen)/file/[id]/+page.svelte @@ -42,14 +42,7 @@ let viewerType: "image" | "video" | undefined = $state(); let fileBlobUrl: string | undefined = $state(); - const updateViewer = async (info: FileInfo, buffer: ArrayBuffer) => { - const contentType = info.contentType; - if (contentType.startsWith("image")) { - viewerType = "image"; - } else if (contentType.startsWith("video")) { - viewerType = "video"; - } - + const updateViewer = async (buffer: ArrayBuffer, contentType: string) => { const fileBlob = new Blob([buffer], { type: contentType }); if (contentType === "image/heic") { const { default: heic2any } = await import("heic2any"); @@ -89,11 +82,18 @@ $effect(() => { if ($info && $info.dataKey && $info.contentIv) { + const contentType = $info.contentType; + if (contentType.startsWith("image")) { + viewerType = "image"; + } else if (contentType.startsWith("video")) { + viewerType = "video"; + } + untrack(() => { if (!downloadStatus && !isDownloadRequested) { isDownloadRequested = true; requestFileDownload(data.id, $info.contentIv!, $info.dataKey!).then(async (buffer) => { - const blob = await updateViewer($info, buffer); + const blob = await updateViewer(buffer, contentType); if (!viewerType) { FileSaver.saveAs(blob, $info.name); } @@ -105,7 +105,9 @@ $effect(() => { if ($info && $downloadStatus?.status === "decrypted") { - untrack(() => !isDownloadRequested && updateViewer($info, $downloadStatus.result!)); + untrack( + () => !isDownloadRequested && updateViewer($downloadStatus.result!, $info.contentType), + ); } }); From ca67f5a81c48bc2d143905e45eb72eb26298be4f Mon Sep 17 00:00:00 2001 From: static Date: Thu, 23 Jan 2025 12:57:37 +0900 Subject: [PATCH 31/53] =?UTF-8?q?/api/category/[id]/file/list=20Endpoint?= =?UTF-8?q?=EC=97=90=EC=84=9C,=20recursive=20=EC=BF=BC=EB=A6=AC=20?= =?UTF-8?q?=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=EC=9D=98=20=EA=B0=92?= =?UTF-8?q?=EC=9D=84=20false=EB=A1=9C=20=EC=84=A4=EC=A0=95=ED=95=B4?= =?UTF-8?q?=EB=8F=84=20=EC=9E=AC=EA=B7=80=EC=A0=81=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=EB=90=98=EB=8D=98=20=EB=B2=84=EA=B7=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/modules/filesystem.ts | 2 +- src/lib/server/db/file.ts | 4 ++-- src/lib/server/services/category.ts | 4 ++-- src/routes/api/category/[id]/file/list/+server.ts | 13 +++++++++---- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/lib/modules/filesystem.ts b/src/lib/modules/filesystem.ts index dc1208e..ce2c35a 100644 --- a/src/lib/modules/filesystem.ts +++ b/src/lib/modules/filesystem.ts @@ -281,7 +281,7 @@ const fetchCategoryInfoFromServer = async ( const { dataKey } = await unwrapDataKey(metadata!.dek, masterKey); const name = await decryptString(metadata!.name, metadata!.nameIv, dataKey); - res = await callGetApi(`/api/category/${id}/file/list?recursive=true`); + res = await callGetApi(`/api/category/${id}/file/list?recurse=true`); if (!res.ok) { throw new Error("Failed to fetch category files"); } diff --git a/src/lib/server/db/file.ts b/src/lib/server/db/file.ts index 6a53c5b..219dc42 100644 --- a/src/lib/server/db/file.ts +++ b/src/lib/server/db/file.ts @@ -294,7 +294,7 @@ export const getAllFilesByParent = async (userId: number, parentId: DirectoryId) export const getAllFilesByCategory = async ( userId: number, categoryId: number, - recursive: boolean, + recurse: boolean, ) => { const files = await db .withRecursive("cte", (db) => @@ -304,7 +304,7 @@ export const getAllFilesByCategory = async ( .select(["id", "parent_id", "user_id", "file_category.file_id"]) .select(sql`0`.as("depth")) .where("id", "=", categoryId) - .$if(recursive, (qb) => + .$if(recurse, (qb) => qb.unionAll((db) => db .selectFrom("category") diff --git a/src/lib/server/services/category.ts b/src/lib/server/services/category.ts index 0d03696..cb3db7a 100644 --- a/src/lib/server/services/category.ts +++ b/src/lib/server/services/category.ts @@ -66,13 +66,13 @@ export const addCategoryFile = async (userId: number, categoryId: number, fileId } }; -export const getCategoryFiles = async (userId: number, categoryId: number, recursive: boolean) => { +export const getCategoryFiles = async (userId: number, categoryId: number, recurse: boolean) => { const category = await getCategory(userId, categoryId); if (!category) { error(404, "Invalid category id"); } - const files = await getAllFilesByCategory(userId, categoryId, recursive); + const files = await getAllFilesByCategory(userId, categoryId, recurse); return { files }; }; diff --git a/src/routes/api/category/[id]/file/list/+server.ts b/src/routes/api/category/[id]/file/list/+server.ts index db55ba5..5d80474 100644 --- a/src/routes/api/category/[id]/file/list/+server.ts +++ b/src/routes/api/category/[id]/file/list/+server.ts @@ -13,12 +13,17 @@ export const GET: RequestHandler = async ({ locals, url, params }) => { const { id } = paramsZodRes.data; const queryZodRes = z - .object({ recursive: z.coerce.boolean().nullable() }) - .safeParse({ recursive: url.searchParams.get("recursive") }); + .object({ + recurse: z + .enum(["true", "false"]) + .transform((value) => value === "true") + .nullable(), + }) + .safeParse({ recurse: url.searchParams.get("recurse") }); if (!queryZodRes.success) error(400, "Invalid query parameters"); - const { recursive } = queryZodRes.data; + const { recurse } = queryZodRes.data; - const { files } = await getCategoryFiles(userId, id, recursive ?? false); + const { files } = await getCategoryFiles(userId, id, recurse ?? false); return json( categoryFileListResponse.parse({ files: files.map(({ id, isRecursive }) => ({ file: id, isRecursive })), From fd10f13a4d0e7a2c5891189e493a13c95c2a08d1 Mon Sep 17 00:00:00 2001 From: static Date: Thu, 23 Jan 2025 13:21:34 +0900 Subject: [PATCH 32/53] =?UTF-8?q?=EC=82=AC=EC=86=8C=ED=95=9C=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/components/inputs/CheckBox.svelte | 2 +- src/lib/modules/filesystem.ts | 2 +- src/lib/molecules/SubCategories.svelte | 2 -- src/routes/(fullscreen)/file/[id]/+page.svelte | 2 -- .../(main)/category/[[id]]/DeleteCategoryModal.svelte | 9 +-------- .../directory/[[id]]/DeleteDirectoryEntryModal.svelte | 9 +-------- src/routes/api/category/[id]/file/list/+server.ts | 8 ++++++-- 7 files changed, 10 insertions(+), 24 deletions(-) diff --git a/src/lib/components/inputs/CheckBox.svelte b/src/lib/components/inputs/CheckBox.svelte index 813a7e0..6875040 100644 --- a/src/lib/components/inputs/CheckBox.svelte +++ b/src/lib/components/inputs/CheckBox.svelte @@ -12,7 +12,7 @@ let { children, checked = $bindable(false) }: Props = $props(); -
- +
diff --git a/src/routes/(main)/directory/[[id]]/DeleteDirectoryEntryModal.svelte b/src/routes/(main)/directory/[[id]]/DeleteDirectoryEntryModal.svelte index 07fb6dd..16dce02 100644 --- a/src/routes/(main)/directory/[[id]]/DeleteDirectoryEntryModal.svelte +++ b/src/routes/(main)/directory/[[id]]/DeleteDirectoryEntryModal.svelte @@ -48,14 +48,7 @@

- +
diff --git a/src/routes/api/category/[id]/file/list/+server.ts b/src/routes/api/category/[id]/file/list/+server.ts index 5d80474..c93c963 100644 --- a/src/routes/api/category/[id]/file/list/+server.ts +++ b/src/routes/api/category/[id]/file/list/+server.ts @@ -8,7 +8,11 @@ import type { RequestHandler } from "./$types"; export const GET: RequestHandler = async ({ locals, url, params }) => { const { userId } = await authorize(locals, "activeClient"); - const paramsZodRes = z.object({ id: z.coerce.number().int().positive() }).safeParse(params); + const paramsZodRes = z + .object({ + id: z.coerce.number().int().positive(), + }) + .safeParse(params); if (!paramsZodRes.success) error(400, "Invalid path parameters"); const { id } = paramsZodRes.data; @@ -27,6 +31,6 @@ export const GET: RequestHandler = async ({ locals, url, params }) => { return json( categoryFileListResponse.parse({ files: files.map(({ id, isRecursive }) => ({ file: id, isRecursive })), - }) as CategoryFileListResponse, + }) satisfies CategoryFileListResponse, ); }; From 3ee98166c6d8efb87b6d1c10161dd9512f531800 Mon Sep 17 00:00:00 2001 From: static Date: Thu, 23 Jan 2025 13:30:50 +0900 Subject: [PATCH 33/53] =?UTF-8?q?=EC=A2=85=EC=A2=85=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=EC=97=90=EC=84=9C=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=EC=9D=84=20=EC=82=AD=EC=A0=9C=ED=96=88=EC=9D=8C=EC=97=90?= =?UTF-8?q?=EB=8F=84=20=EC=82=AD=EC=A0=9C=EB=90=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EC=9D=80=20=EA=B2=83=EC=9C=BC=EB=A1=9C=20=ED=91=9C=EC=8B=9C?= =?UTF-8?q?=EB=90=98=EB=8D=98=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes/(main)/category/[[id]]/+page.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/(main)/category/[[id]]/+page.svelte b/src/routes/(main)/category/[[id]]/+page.svelte index d83ded2..587390e 100644 --- a/src/routes/(main)/category/[[id]]/+page.svelte +++ b/src/routes/(main)/category/[[id]]/+page.svelte @@ -54,8 +54,8 @@ bind:isFileRecursive info={$info} onFileClick={({ id }) => goto(`/file/${id}`)} - onFileRemoveClick={({ id }) => { - requestFileRemovalFromCategory(id, data.id as number); + onFileRemoveClick={async ({ id }) => { + await requestFileRemovalFromCategory(id, data.id as number); info = getCategoryInfo(data.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME }} onSubCategoryClick={({ id }) => goto(`/category/${id}`)} From 1c09d93b41ff92b68d0ba77f6feb89858fd9118c Mon Sep 17 00:00:00 2001 From: static Date: Fri, 24 Jan 2025 16:39:09 +0900 Subject: [PATCH 34/53] =?UTF-8?q?Button=20=EB=B0=8F=20Input=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=EB=A5=BC=20atoms=20=EB=94=94?= =?UTF-8?q?=EB=A0=89=ED=84=B0=EB=A6=AC=EB=A1=9C=20=EC=9D=B4=EB=8F=99=20?= =?UTF-8?q?=EB=B0=8F=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/atoms/buttons/Button.svelte | 35 ++++++++++++++++++ .../atoms/buttons/EntryButton.svelte | 28 ++++++++++++++ .../{ => atoms}/buttons/FloatingButton.svelte | 14 ++----- .../{ => atoms}/buttons/TextButton.svelte | 10 ++--- .../components/{ => atoms}/buttons/index.ts | 0 src/lib/components/atoms/index.ts | 2 + .../{ => atoms}/inputs/CheckBox.svelte | 8 ++-- .../{ => atoms}/inputs/TextInput.svelte | 2 +- .../components/{ => atoms}/inputs/index.ts | 0 src/lib/components/buttons/Button.svelte | 37 ------------------- src/lib/components/buttons/EntryButton.svelte | 30 --------------- src/lib/molecules/SubCategories.svelte | 4 +- src/lib/organisms/Category/Category.svelte | 2 +- src/lib/organisms/CreateCategoryModal.svelte | 9 ++--- .../auth/changePassword/+page.svelte | 5 +-- .../(fullscreen)/auth/login/+page.svelte | 5 +-- .../(fullscreen)/file/[id]/+page.svelte | 4 +- .../file/[id]/AddToCategoryBottomSheet.svelte | 6 ++- .../(fullscreen)/key/export/+page.svelte | 12 ++---- .../export/BeforeContinueBottomSheet.svelte | 8 ++-- .../key/export/BeforeContinueModal.svelte | 15 ++------ .../(fullscreen)/key/generate/+page.svelte | 4 +- .../[[id]]/CategoryMenuBottomSheet.svelte | 6 +-- .../[[id]]/DeleteCategoryModal.svelte | 8 ++-- .../[[id]]/RenameCategoryModal.svelte | 11 +++--- .../(main)/directory/[[id]]/+page.svelte | 2 +- .../directory/[[id]]/CreateBottomSheet.svelte | 6 +-- .../[[id]]/CreateDirectoryModal.svelte | 9 ++--- .../[[id]]/DeleteDirectoryEntryModal.svelte | 8 ++-- .../DirectoryEntryMenuBottomSheet.svelte | 6 +-- .../[[id]]/DuplicateFileModal.svelte | 8 ++-- .../[[id]]/RenameDirectoryEntryModal.svelte | 9 ++--- src/routes/(main)/menu/MenuEntryButton.svelte | 4 +- 33 files changed, 145 insertions(+), 172 deletions(-) create mode 100644 src/lib/components/atoms/buttons/Button.svelte create mode 100644 src/lib/components/atoms/buttons/EntryButton.svelte rename src/lib/components/{ => atoms}/buttons/FloatingButton.svelte (76%) rename src/lib/components/{ => atoms}/buttons/TextButton.svelte (67%) rename src/lib/components/{ => atoms}/buttons/index.ts (100%) create mode 100644 src/lib/components/atoms/index.ts rename src/lib/components/{ => atoms}/inputs/CheckBox.svelte (79%) rename src/lib/components/{ => atoms}/inputs/TextInput.svelte (83%) rename src/lib/components/{ => atoms}/inputs/index.ts (100%) delete mode 100644 src/lib/components/buttons/Button.svelte delete mode 100644 src/lib/components/buttons/EntryButton.svelte diff --git a/src/lib/components/atoms/buttons/Button.svelte b/src/lib/components/atoms/buttons/Button.svelte new file mode 100644 index 0000000..a1b079c --- /dev/null +++ b/src/lib/components/atoms/buttons/Button.svelte @@ -0,0 +1,35 @@ + + + diff --git a/src/lib/components/atoms/buttons/EntryButton.svelte b/src/lib/components/atoms/buttons/EntryButton.svelte new file mode 100644 index 0000000..22e8a16 --- /dev/null +++ b/src/lib/components/atoms/buttons/EntryButton.svelte @@ -0,0 +1,28 @@ + + + diff --git a/src/lib/components/buttons/FloatingButton.svelte b/src/lib/components/atoms/buttons/FloatingButton.svelte similarity index 76% rename from src/lib/components/buttons/FloatingButton.svelte rename to src/lib/components/atoms/buttons/FloatingButton.svelte index 51870c7..499a043 100644 --- a/src/lib/components/buttons/FloatingButton.svelte +++ b/src/lib/components/atoms/buttons/FloatingButton.svelte @@ -1,30 +1,24 @@
-
+
diff --git a/src/lib/components/buttons/index.ts b/src/lib/components/atoms/buttons/index.ts similarity index 100% rename from src/lib/components/buttons/index.ts rename to src/lib/components/atoms/buttons/index.ts diff --git a/src/lib/components/atoms/index.ts b/src/lib/components/atoms/index.ts new file mode 100644 index 0000000..5c96a2a --- /dev/null +++ b/src/lib/components/atoms/index.ts @@ -0,0 +1,2 @@ +export * from "./buttons"; +export * from "./inputs"; diff --git a/src/lib/components/inputs/CheckBox.svelte b/src/lib/components/atoms/inputs/CheckBox.svelte similarity index 79% rename from src/lib/components/inputs/CheckBox.svelte rename to src/lib/components/atoms/inputs/CheckBox.svelte index 6875040..753892f 100644 --- a/src/lib/components/inputs/CheckBox.svelte +++ b/src/lib/components/atoms/inputs/CheckBox.svelte @@ -5,16 +5,18 @@ import IconCheckCircleOutline from "~icons/material-symbols/check-circle-outline"; interface Props { - children: Snippet; checked?: boolean; + children?: Snippet; } - let { children, checked = $bindable(false) }: Props = $props(); + let { checked = $bindable(false), children }: Props = $props();