From ad0f3ff9504743ca1136645c0526d054cebfa62b Mon Sep 17 00:00:00 2001 From: static Date: Fri, 31 Jan 2025 00:37:23 +0900 Subject: [PATCH 01/38] =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80/=EB=B9=84?= =?UTF-8?q?=EB=94=94=EC=98=A4=20=EC=8D=B8=EB=84=A4=EC=9D=BC=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=ED=95=A8=EC=88=98=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/thumbnail.ts | 76 ++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 src/lib/modules/thumbnail.ts diff --git a/src/lib/modules/thumbnail.ts b/src/lib/modules/thumbnail.ts new file mode 100644 index 0000000..30e931e --- /dev/null +++ b/src/lib/modules/thumbnail.ts @@ -0,0 +1,76 @@ +const scaleSize = (width: number, height: number, targetSize: number) => { + if (width <= targetSize || height <= targetSize) { + return { width, height }; + } + + const scale = targetSize / Math.min(width, height); + return { + width: Math.round(width * scale), + height: Math.round(height * scale), + }; +}; + +export const generateImageThumbnail = (imageUrl: string) => { + return new Promise((resolve, reject) => { + const image = new Image(); + image.onload = () => { + const canvas = document.createElement("canvas"); + const { width, height } = scaleSize(image.width, image.height, 250); + + canvas.width = width; + canvas.height = height; + + const context = canvas.getContext("2d"); + if (!context) { + return reject(new Error("Failed to generate thumbnail")); + } + + context.drawImage(image, 0, 0, width, height); + canvas.toBlob((blob) => { + if (blob) { + resolve(blob); + } else { + reject(new Error("Failed to generate thumbnail")); + } + }, "image/webp"); + }; + image.onerror = reject; + + image.src = imageUrl; + }); +}; + +export const generateVideoThumbnail = (videoUrl: string, time = 0) => { + return new Promise((resolve, reject) => { + const video = document.createElement("video"); + video.onloadeddata = () => { + video.currentTime = time; + }; + video.onseeked = () => { + const canvas = document.createElement("canvas"); + const { width, height } = scaleSize(video.videoWidth, video.videoHeight, 250); + + canvas.width = width; + canvas.height = height; + + const context = canvas.getContext("2d"); + if (!context) { + return reject(new Error("Failed to generate thumbnail")); + } + + context.drawImage(video, 0, 0, width, height); + canvas.toBlob((blob) => { + if (blob) { + resolve(blob); + } else { + reject(new Error("Failed to generate thumbnail")); + } + }, "image/webp"); + }; + video.onerror = reject; + + video.muted = true; + video.playsInline = true; + video.src = videoUrl; + }); +}; From 2105b66cc3c274ecc026a49838b0403d8f7de120 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 1 Feb 2025 20:33:41 +0900 Subject: [PATCH 02/38] =?UTF-8?q?DB=EC=97=90=20thumbnail=20=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EB=B8=94=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../db/migrations/1738409340-AddThumbnail.ts | 31 +++++++++++++++++++ src/lib/server/db/migrations/index.ts | 2 ++ src/lib/server/db/schema/index.ts | 1 + src/lib/server/db/schema/media.ts | 17 ++++++++++ 4 files changed, 51 insertions(+) create mode 100644 src/lib/server/db/migrations/1738409340-AddThumbnail.ts create mode 100644 src/lib/server/db/schema/media.ts diff --git a/src/lib/server/db/migrations/1738409340-AddThumbnail.ts b/src/lib/server/db/migrations/1738409340-AddThumbnail.ts new file mode 100644 index 0000000..0e38647 --- /dev/null +++ b/src/lib/server/db/migrations/1738409340-AddThumbnail.ts @@ -0,0 +1,31 @@ +import { Kysely, sql } from "kysely"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const up = async (db: Kysely) => { + // media.ts + await db.schema + .createTable("thumbnail") + .addColumn("id", "integer", (col) => col.primaryKey().generatedAlwaysAsIdentity()) + .addColumn("directory_id", "integer", (col) => + col.references("directory.id").onDelete("cascade").unique(), + ) + .addColumn("file_id", "integer", (col) => + col.references("file.id").onDelete("cascade").unique(), + ) + .addColumn("category_id", "integer", (col) => + col.references("category.id").onDelete("cascade").unique(), + ) + .addColumn("path", "text", (col) => col.unique().notNull()) + .addColumn("created_at", "timestamp(3)", (col) => col.notNull()) + .addColumn("encrypted_content_iv", "text", (col) => col.notNull()) + .addCheckConstraint( + "thumbnail_ck01", + sql`(file_id IS NOT NULL)::integer + (directory_id IS NOT NULL)::integer + (category_id IS NOT NULL)::integer = 1`, + ) + .execute(); +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const down = async (db: Kysely) => { + await db.schema.dropTable("thumbnail").execute(); +}; diff --git a/src/lib/server/db/migrations/index.ts b/src/lib/server/db/migrations/index.ts index aa6ee13..f58c2d0 100644 --- a/src/lib/server/db/migrations/index.ts +++ b/src/lib/server/db/migrations/index.ts @@ -1,7 +1,9 @@ import * as Initial1737357000 from "./1737357000-Initial"; import * as AddFileCategory1737422340 from "./1737422340-AddFileCategory"; +import * as AddThumbnail1738409340 from "./1738409340-AddThumbnail"; export default { "1737357000-Initial": Initial1737357000, "1737422340-AddFileCategory": AddFileCategory1737422340, + "1738409340-AddThumbnail": AddThumbnail1738409340, }; diff --git a/src/lib/server/db/schema/index.ts b/src/lib/server/db/schema/index.ts index d3dd9b1..4e427fb 100644 --- a/src/lib/server/db/schema/index.ts +++ b/src/lib/server/db/schema/index.ts @@ -2,6 +2,7 @@ export * from "./category"; export * from "./client"; export * from "./file"; export * from "./hsk"; +export * from "./media"; export * from "./mek"; export * from "./session"; export * from "./user"; diff --git a/src/lib/server/db/schema/media.ts b/src/lib/server/db/schema/media.ts new file mode 100644 index 0000000..9eeccf7 --- /dev/null +++ b/src/lib/server/db/schema/media.ts @@ -0,0 +1,17 @@ +import type { ColumnType, Generated } from "kysely"; + +interface ThumbnailTable { + id: Generated; + directory_id: number | null; + file_id: number | null; + category_id: number | null; + path: string; + created_at: ColumnType; + encrypted_content_iv: string; // Base64 +} + +declare module "./index" { + interface Database { + thumbnail: ThumbnailTable; + } +} From 451dd3c1296dd571e8eb87d378369d9d2a1450bc Mon Sep 17 00:00:00 2001 From: static Date: Wed, 28 May 2025 18:00:17 +0900 Subject: [PATCH 03/38] =?UTF-8?q?=EB=8D=B0=EB=AA=A8=EC=9A=A9=20=EC=9E=84?= =?UTF-8?q?=EC=8B=9C=20=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=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 --- src/lib/server/db/user.ts | 9 ++++ src/lib/server/middlewares/authenticate.ts | 2 +- src/lib/server/schemas/auth.ts | 7 ++++ src/lib/server/services/auth.ts | 23 +++++++++- .../(fullscreen)/auth/login/+page.svelte | 42 ++++++++++++++++++- .../auth/login/NicknameModal.svelte | 18 ++++++++ src/routes/(fullscreen)/auth/login/service.ts | 11 ++++- src/routes/api/auth/register/+server.ts | 27 ++++++++++++ 8 files changed, 134 insertions(+), 5 deletions(-) create mode 100644 src/routes/(fullscreen)/auth/login/NicknameModal.svelte create mode 100644 src/routes/api/auth/register/+server.ts diff --git a/src/lib/server/db/user.ts b/src/lib/server/db/user.ts index 3964a94..1804144 100644 --- a/src/lib/server/db/user.ts +++ b/src/lib/server/db/user.ts @@ -7,6 +7,15 @@ interface User { password: string; } +export const createUser = async (email: string, nickname: string, password: string) => { + const { id } = await db + .insertInto("user") + .values({ email, nickname, password }) + .returning("id") + .executeTakeFirstOrThrow(); + return { id, email, nickname, password } satisfies User; +}; + export const getUser = async (userId: number) => { const user = await db .selectFrom("user") diff --git a/src/lib/server/middlewares/authenticate.ts b/src/lib/server/middlewares/authenticate.ts index 8880f1a..37ce672 100644 --- a/src/lib/server/middlewares/authenticate.ts +++ b/src/lib/server/middlewares/authenticate.ts @@ -3,7 +3,7 @@ import { authenticate, AuthenticationError } from "$lib/server/modules/auth"; export const authenticateMiddleware: Handle = async ({ event, resolve }) => { const { pathname, search } = event.url; - if (pathname === "/api/auth/login") { + if (pathname === "/api/auth/login" || pathname === "/api/auth/register") { return await resolve(event); } diff --git a/src/lib/server/schemas/auth.ts b/src/lib/server/schemas/auth.ts index e3d6264..2a9fcec 100644 --- a/src/lib/server/schemas/auth.ts +++ b/src/lib/server/schemas/auth.ts @@ -12,6 +12,13 @@ export const loginRequest = z.object({ }); export type LoginRequest = z.infer; +export const registerRequest = z.object({ + email: z.string().email(), + nickname: z.string().trim().min(2).max(8), + password: z.string().trim().nonempty(), +}); +export type RegisterRequest = z.infer; + export const sessionUpgradeRequest = z.object({ encPubKey: z.string().base64().nonempty(), sigPubKey: z.string().base64().nonempty(), diff --git a/src/lib/server/services/auth.ts b/src/lib/server/services/auth.ts index 81f0333..e9fd91e 100644 --- a/src/lib/server/services/auth.ts +++ b/src/lib/server/services/auth.ts @@ -9,7 +9,7 @@ import { registerSessionUpgradeChallenge, consumeSessionUpgradeChallenge, } from "$lib/server/db/session"; -import { getUser, getUserByEmail, setUserPassword } from "$lib/server/db/user"; +import { createUser, getUser, getUserByEmail, setUserPassword } from "$lib/server/db/user"; import env from "$lib/server/loadenv"; import { startSession } from "$lib/server/modules/auth"; import { verifySignature, generateChallenge } from "$lib/server/modules/crypto"; @@ -65,6 +65,27 @@ export const logout = async (sessionId: string) => { await deleteSession(sessionId); }; +export const register = async ( + email: string, + nickname: string, + password: string, + ip: string, + userAgent: string, +) => { + if (password.length < 8) { + error(400, "Too short password"); + } + + const existingUser = await getUserByEmail(email); + if (existingUser) { + error(409, "Email already registered"); + } + + const hashedPassword = await hashPassword(password); + const { id } = await createUser(email, nickname, hashedPassword); + return { sessionIdSigned: await startSession(id, ip, userAgent) }; +}; + export const createSessionUpgradeChallenge = async ( sessionId: string, userId: number, diff --git a/src/routes/(fullscreen)/auth/login/+page.svelte b/src/routes/(fullscreen)/auth/login/+page.svelte index ac3b1a5..b68c83a 100644 --- a/src/routes/(fullscreen)/auth/login/+page.svelte +++ b/src/routes/(fullscreen)/auth/login/+page.svelte @@ -3,13 +3,21 @@ import { BottomDiv, Button, FullscreenDiv, TextButton, TextInput } from "$lib/components/atoms"; import { TitledDiv } from "$lib/components/molecules"; import { clientKeyStore, masterKeyStore } from "$lib/stores"; - import { requestLogin, requestSessionUpgrade, requestMasterKeyDownload } from "./service"; + import NicknameModal from "./NicknameModal.svelte"; + import { + requestLogin, + requestRegister, + requestSessionUpgrade, + requestMasterKeyDownload, + } from "./service"; let { data } = $props(); let email = $state(""); let password = $state(""); + let isNicknameModalOpen = $state(false); + const redirect = async (url: string) => { return await goto(`${url}?redirect=${encodeURIComponent(data.redirectPath)}`); }; @@ -40,6 +48,34 @@ throw e; } }; + + const register = async (nickname: string) => { + // TODO: Validation + + try { + if (!(await requestRegister(email, nickname, password))) + throw new Error("Failed to register"); + + if (!$clientKeyStore) return await redirect("/key/generate"); + + if (!(await requestSessionUpgrade($clientKeyStore))) + throw new Error("Failed to upgrade session"); + + // TODO: Multi-user support + + if ( + $masterKeyStore || + (await requestMasterKeyDownload($clientKeyStore.decryptKey, $clientKeyStore.verifyKey)) + ) { + await goto(data.redirectPath); + } else { + await redirect("/client/pending"); + } + } catch (e) { + // TODO: Alert + throw e; + } + }; @@ -60,6 +96,8 @@ - 계정이 없어요 + (isNicknameModalOpen = true)}>계정이 없어요 + + diff --git a/src/routes/(fullscreen)/auth/login/NicknameModal.svelte b/src/routes/(fullscreen)/auth/login/NicknameModal.svelte new file mode 100644 index 0000000..de71042 --- /dev/null +++ b/src/routes/(fullscreen)/auth/login/NicknameModal.svelte @@ -0,0 +1,18 @@ + + + diff --git a/src/routes/(fullscreen)/auth/login/service.ts b/src/routes/(fullscreen)/auth/login/service.ts index 2d267e1..56921fc 100644 --- a/src/routes/(fullscreen)/auth/login/service.ts +++ b/src/routes/(fullscreen)/auth/login/service.ts @@ -1,6 +1,6 @@ import { callPostApi } from "$lib/hooks"; import { exportRSAKeyToBase64 } from "$lib/modules/crypto"; -import type { LoginRequest } from "$lib/server/schemas"; +import type { LoginRequest, RegisterRequest } from "$lib/server/schemas"; import { requestSessionUpgrade as requestSessionUpgradeInternal } from "$lib/services/auth"; import { requestClientRegistration } from "$lib/services/key"; import type { ClientKeys } from "$lib/stores"; @@ -12,6 +12,15 @@ export const requestLogin = async (email: string, password: string) => { return res.ok; }; +export const requestRegister = async (email: string, nickname: string, password: string) => { + const res = await callPostApi("/api/auth/register", { + email, + nickname, + password, + }); + return res.ok; +}; + export const requestSessionUpgrade = async ({ encryptKey, decryptKey, diff --git a/src/routes/api/auth/register/+server.ts b/src/routes/api/auth/register/+server.ts new file mode 100644 index 0000000..a1fe339 --- /dev/null +++ b/src/routes/api/auth/register/+server.ts @@ -0,0 +1,27 @@ +import { error, text } from "@sveltejs/kit"; +import env from "$lib/server/loadenv"; +import { registerRequest } from "$lib/server/schemas"; +import { register } from "$lib/server/services/auth"; +import type { RequestHandler } from "./$types"; + +export const POST: RequestHandler = async ({ locals, request, cookies }) => { + const zodRes = registerRequest.safeParse(await request.json()); + if (!zodRes.success) error(400, "Invalid request body"); + const { email, nickname, password } = zodRes.data; + + const { sessionIdSigned } = await register( + email, + nickname, + password, + locals.ip, + locals.userAgent, + ); + cookies.set("sessionId", sessionIdSigned, { + path: "/", + maxAge: env.session.exp / 1000, + secure: true, + sameSite: "strict", + }); + + return text("Registered and logged in", { headers: { "Content-Type": "text/plain" } }); +}; From 2a5200fe9d17deaab1ee7f1682a6570e900af65e Mon Sep 17 00:00:00 2001 From: static Date: Sat, 31 May 2025 21:36:27 +0900 Subject: [PATCH 04/38] =?UTF-8?q?Revert=20"=EB=8D=B0=EB=AA=A8=EC=9A=A9=20?= =?UTF-8?q?=EC=9E=84=EC=8B=9C=20=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=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 This reverts commit eb913366646f43fda669f0550788e0888c44b95a. --- src/lib/server/db/user.ts | 9 ---- src/lib/server/middlewares/authenticate.ts | 2 +- src/lib/server/schemas/auth.ts | 7 ---- src/lib/server/services/auth.ts | 23 +--------- .../(fullscreen)/auth/login/+page.svelte | 42 +------------------ .../auth/login/NicknameModal.svelte | 18 -------- src/routes/(fullscreen)/auth/login/service.ts | 11 +---- src/routes/api/auth/register/+server.ts | 27 ------------ 8 files changed, 5 insertions(+), 134 deletions(-) delete mode 100644 src/routes/(fullscreen)/auth/login/NicknameModal.svelte delete mode 100644 src/routes/api/auth/register/+server.ts diff --git a/src/lib/server/db/user.ts b/src/lib/server/db/user.ts index 1804144..3964a94 100644 --- a/src/lib/server/db/user.ts +++ b/src/lib/server/db/user.ts @@ -7,15 +7,6 @@ interface User { password: string; } -export const createUser = async (email: string, nickname: string, password: string) => { - const { id } = await db - .insertInto("user") - .values({ email, nickname, password }) - .returning("id") - .executeTakeFirstOrThrow(); - return { id, email, nickname, password } satisfies User; -}; - export const getUser = async (userId: number) => { const user = await db .selectFrom("user") diff --git a/src/lib/server/middlewares/authenticate.ts b/src/lib/server/middlewares/authenticate.ts index 37ce672..8880f1a 100644 --- a/src/lib/server/middlewares/authenticate.ts +++ b/src/lib/server/middlewares/authenticate.ts @@ -3,7 +3,7 @@ import { authenticate, AuthenticationError } from "$lib/server/modules/auth"; export const authenticateMiddleware: Handle = async ({ event, resolve }) => { const { pathname, search } = event.url; - if (pathname === "/api/auth/login" || pathname === "/api/auth/register") { + if (pathname === "/api/auth/login") { return await resolve(event); } diff --git a/src/lib/server/schemas/auth.ts b/src/lib/server/schemas/auth.ts index 2a9fcec..e3d6264 100644 --- a/src/lib/server/schemas/auth.ts +++ b/src/lib/server/schemas/auth.ts @@ -12,13 +12,6 @@ export const loginRequest = z.object({ }); export type LoginRequest = z.infer; -export const registerRequest = z.object({ - email: z.string().email(), - nickname: z.string().trim().min(2).max(8), - password: z.string().trim().nonempty(), -}); -export type RegisterRequest = z.infer; - export const sessionUpgradeRequest = z.object({ encPubKey: z.string().base64().nonempty(), sigPubKey: z.string().base64().nonempty(), diff --git a/src/lib/server/services/auth.ts b/src/lib/server/services/auth.ts index e9fd91e..81f0333 100644 --- a/src/lib/server/services/auth.ts +++ b/src/lib/server/services/auth.ts @@ -9,7 +9,7 @@ import { registerSessionUpgradeChallenge, consumeSessionUpgradeChallenge, } from "$lib/server/db/session"; -import { createUser, getUser, getUserByEmail, setUserPassword } from "$lib/server/db/user"; +import { getUser, getUserByEmail, setUserPassword } from "$lib/server/db/user"; import env from "$lib/server/loadenv"; import { startSession } from "$lib/server/modules/auth"; import { verifySignature, generateChallenge } from "$lib/server/modules/crypto"; @@ -65,27 +65,6 @@ export const logout = async (sessionId: string) => { await deleteSession(sessionId); }; -export const register = async ( - email: string, - nickname: string, - password: string, - ip: string, - userAgent: string, -) => { - if (password.length < 8) { - error(400, "Too short password"); - } - - const existingUser = await getUserByEmail(email); - if (existingUser) { - error(409, "Email already registered"); - } - - const hashedPassword = await hashPassword(password); - const { id } = await createUser(email, nickname, hashedPassword); - return { sessionIdSigned: await startSession(id, ip, userAgent) }; -}; - export const createSessionUpgradeChallenge = async ( sessionId: string, userId: number, diff --git a/src/routes/(fullscreen)/auth/login/+page.svelte b/src/routes/(fullscreen)/auth/login/+page.svelte index b68c83a..ac3b1a5 100644 --- a/src/routes/(fullscreen)/auth/login/+page.svelte +++ b/src/routes/(fullscreen)/auth/login/+page.svelte @@ -3,21 +3,13 @@ import { BottomDiv, Button, FullscreenDiv, TextButton, TextInput } from "$lib/components/atoms"; import { TitledDiv } from "$lib/components/molecules"; import { clientKeyStore, masterKeyStore } from "$lib/stores"; - import NicknameModal from "./NicknameModal.svelte"; - import { - requestLogin, - requestRegister, - requestSessionUpgrade, - requestMasterKeyDownload, - } from "./service"; + import { requestLogin, requestSessionUpgrade, requestMasterKeyDownload } from "./service"; let { data } = $props(); let email = $state(""); let password = $state(""); - let isNicknameModalOpen = $state(false); - const redirect = async (url: string) => { return await goto(`${url}?redirect=${encodeURIComponent(data.redirectPath)}`); }; @@ -48,34 +40,6 @@ throw e; } }; - - const register = async (nickname: string) => { - // TODO: Validation - - try { - if (!(await requestRegister(email, nickname, password))) - throw new Error("Failed to register"); - - if (!$clientKeyStore) return await redirect("/key/generate"); - - if (!(await requestSessionUpgrade($clientKeyStore))) - throw new Error("Failed to upgrade session"); - - // TODO: Multi-user support - - if ( - $masterKeyStore || - (await requestMasterKeyDownload($clientKeyStore.decryptKey, $clientKeyStore.verifyKey)) - ) { - await goto(data.redirectPath); - } else { - await redirect("/client/pending"); - } - } catch (e) { - // TODO: Alert - throw e; - } - }; @@ -96,8 +60,6 @@ - (isNicknameModalOpen = true)}>계정이 없어요 + 계정이 없어요 - - diff --git a/src/routes/(fullscreen)/auth/login/NicknameModal.svelte b/src/routes/(fullscreen)/auth/login/NicknameModal.svelte deleted file mode 100644 index de71042..0000000 --- a/src/routes/(fullscreen)/auth/login/NicknameModal.svelte +++ /dev/null @@ -1,18 +0,0 @@ - - - diff --git a/src/routes/(fullscreen)/auth/login/service.ts b/src/routes/(fullscreen)/auth/login/service.ts index 56921fc..2d267e1 100644 --- a/src/routes/(fullscreen)/auth/login/service.ts +++ b/src/routes/(fullscreen)/auth/login/service.ts @@ -1,6 +1,6 @@ import { callPostApi } from "$lib/hooks"; import { exportRSAKeyToBase64 } from "$lib/modules/crypto"; -import type { LoginRequest, RegisterRequest } from "$lib/server/schemas"; +import type { LoginRequest } from "$lib/server/schemas"; import { requestSessionUpgrade as requestSessionUpgradeInternal } from "$lib/services/auth"; import { requestClientRegistration } from "$lib/services/key"; import type { ClientKeys } from "$lib/stores"; @@ -12,15 +12,6 @@ export const requestLogin = async (email: string, password: string) => { return res.ok; }; -export const requestRegister = async (email: string, nickname: string, password: string) => { - const res = await callPostApi("/api/auth/register", { - email, - nickname, - password, - }); - return res.ok; -}; - export const requestSessionUpgrade = async ({ encryptKey, decryptKey, diff --git a/src/routes/api/auth/register/+server.ts b/src/routes/api/auth/register/+server.ts deleted file mode 100644 index a1fe339..0000000 --- a/src/routes/api/auth/register/+server.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { error, text } from "@sveltejs/kit"; -import env from "$lib/server/loadenv"; -import { registerRequest } from "$lib/server/schemas"; -import { register } from "$lib/server/services/auth"; -import type { RequestHandler } from "./$types"; - -export const POST: RequestHandler = async ({ locals, request, cookies }) => { - const zodRes = registerRequest.safeParse(await request.json()); - if (!zodRes.success) error(400, "Invalid request body"); - const { email, nickname, password } = zodRes.data; - - const { sessionIdSigned } = await register( - email, - nickname, - password, - locals.ip, - locals.userAgent, - ); - cookies.set("sessionId", sessionIdSigned, { - path: "/", - maxAge: env.session.exp / 1000, - secure: true, - sameSite: "strict", - }); - - return text("Registered and logged in", { headers: { "Content-Type": "text/plain" } }); -}; From 13bac598241edce97e1829ee8475e1de4d1670d1 Mon Sep 17 00:00:00 2001 From: static Date: Fri, 4 Jul 2025 22:33:44 +0900 Subject: [PATCH 05/38] =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=20=EB=B2=84?= =?UTF-8?q?=EC=A0=84=20=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 --- package.json | 50 +- pnpm-lock.yaml | 2073 +++++++++++++++++++++++++----------------------- 2 files changed, 1096 insertions(+), 1027 deletions(-) diff --git a/package.json b/package.json index 8d0ddba..2d50287 100644 --- a/package.json +++ b/package.json @@ -16,49 +16,49 @@ "db:migrate": "kysely migrate" }, "devDependencies": { - "@eslint/compat": "^1.2.4", - "@iconify-json/material-symbols": "^1.2.12", - "@sveltejs/adapter-node": "^5.2.11", - "@sveltejs/kit": "^2.15.2", + "@eslint/compat": "^1.3.1", + "@iconify-json/material-symbols": "^1.2.29", + "@sveltejs/adapter-node": "^5.2.12", + "@sveltejs/kit": "^2.22.2", "@sveltejs/vite-plugin-svelte": "^4.0.4", "@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", - "eslint": "^9.17.0", + "@types/pg": "^8.15.4", + "autoprefixer": "^10.4.21", + "axios": "^1.10.0", + "dexie": "^4.0.11", + "eslint": "^9.30.1", "eslint-config-prettier": "^9.1.0", "eslint-plugin-svelte": "^2.46.1", - "eslint-plugin-tailwindcss": "^3.17.5", - "exifreader": "^4.26.0", + "eslint-plugin-tailwindcss": "^3.18.0", + "exifreader": "^4.31.1", "file-saver": "^2.0.5", - "globals": "^15.14.0", + "globals": "^15.15.0", "heic2any": "^0.0.4", "kysely-ctl": "^0.10.1", - "mime": "^4.0.6", + "mime": "^4.0.7", "p-limit": "^6.2.0", - "prettier": "^3.4.2", - "prettier-plugin-svelte": "^3.3.2", - "prettier-plugin-tailwindcss": "^0.6.9", - "svelte": "^5.19.1", - "svelte-check": "^4.1.3", + "prettier": "^3.6.2", + "prettier-plugin-svelte": "^3.4.0", + "prettier-plugin-tailwindcss": "^0.6.13", + "svelte": "^5.35.2", + "svelte-check": "^4.2.2", "tailwindcss": "^3.4.17", - "typescript": "^5.7.3", - "typescript-eslint": "^8.19.1", + "typescript": "^5.8.3", + "typescript-eslint": "^8.35.1", "unplugin-icons": "^0.22.0", - "vite": "^5.4.11" + "vite": "^5.4.19" }, "dependencies": { "@fastify/busboy": "^3.1.1", "argon2": "^0.41.1", - "kysely": "^0.27.5", + "kysely": "^0.28.2", "ms": "^2.1.3", "node-schedule": "^2.1.1", - "pg": "^8.13.1", - "uuid": "^11.0.4", - "zod": "^3.24.1" + "pg": "^8.16.3", + "uuid": "^11.1.0", + "zod": "^3.25.73" }, "engines": { "node": "^22.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9ed4442..4ed0cb5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,8 +15,8 @@ importers: specifier: ^0.41.1 version: 0.41.1 kysely: - specifier: ^0.27.5 - version: 0.27.5 + specifier: ^0.28.2 + version: 0.28.2 ms: specifier: ^2.1.3 version: 2.1.3 @@ -24,30 +24,30 @@ importers: specifier: ^2.1.1 version: 2.1.1 pg: - specifier: ^8.13.1 - version: 8.13.1 + specifier: ^8.16.3 + version: 8.16.3 uuid: - specifier: ^11.0.4 - version: 11.0.4 + specifier: ^11.1.0 + version: 11.1.0 zod: - specifier: ^3.24.1 - version: 3.24.1 + specifier: ^3.25.73 + version: 3.25.73 devDependencies: '@eslint/compat': - specifier: ^1.2.4 - version: 1.2.4(eslint@9.17.0(jiti@2.4.2)) + specifier: ^1.3.1 + version: 1.3.1(eslint@9.30.1(jiti@2.4.2)) '@iconify-json/material-symbols': - specifier: ^1.2.12 - version: 1.2.12 + specifier: ^1.2.29 + version: 1.2.29 '@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.19.1)(vite@5.4.11(@types/node@22.10.5)))(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5))) + specifier: ^5.2.12 + version: 5.2.12(@sveltejs/kit@2.22.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10)))(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10))) '@sveltejs/kit': - specifier: ^2.15.2 - 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)) + specifier: ^2.22.2 + version: 2.22.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10)))(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10)) '@sveltejs/vite-plugin-svelte': specifier: ^4.0.4 - version: 4.0.4(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5)) + version: 4.0.4(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10)) '@types/file-saver': specifier: ^2.0.7 version: 2.0.7 @@ -58,80 +58,80 @@ importers: specifier: ^2.1.7 version: 2.1.7 '@types/pg': - specifier: ^8.11.10 - version: 8.11.10 + specifier: ^8.15.4 + version: 8.15.4 autoprefixer: - specifier: ^10.4.20 - version: 10.4.20(postcss@8.4.49) + specifier: ^10.4.21 + version: 10.4.21(postcss@8.5.6) axios: - specifier: ^1.7.9 - version: 1.7.9 + specifier: ^1.10.0 + version: 1.10.0 dexie: - specifier: ^4.0.10 - version: 4.0.10 + specifier: ^4.0.11 + version: 4.0.11 eslint: - specifier: ^9.17.0 - version: 9.17.0(jiti@2.4.2) + specifier: ^9.30.1 + version: 9.30.1(jiti@2.4.2) eslint-config-prettier: specifier: ^9.1.0 - version: 9.1.0(eslint@9.17.0(jiti@2.4.2)) + version: 9.1.0(eslint@9.30.1(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.19.1) + version: 2.46.1(eslint@9.30.1(jiti@2.4.2))(svelte@5.35.2) eslint-plugin-tailwindcss: - specifier: ^3.17.5 - version: 3.17.5(tailwindcss@3.4.17) + specifier: ^3.18.0 + version: 3.18.0(tailwindcss@3.4.17) exifreader: - specifier: ^4.26.0 - version: 4.26.0 + specifier: ^4.31.1 + version: 4.31.1 file-saver: specifier: ^2.0.5 version: 2.0.5 globals: - specifier: ^15.14.0 - version: 15.14.0 + specifier: ^15.15.0 + version: 15.15.0 heic2any: specifier: ^0.0.4 version: 0.0.4 kysely-ctl: specifier: ^0.10.1 - version: 0.10.1(kysely@0.27.5) + version: 0.10.1(kysely@0.28.2) mime: - specifier: ^4.0.6 - version: 4.0.6 + specifier: ^4.0.7 + version: 4.0.7 p-limit: specifier: ^6.2.0 version: 6.2.0 prettier: - specifier: ^3.4.2 - version: 3.4.2 + specifier: ^3.6.2 + version: 3.6.2 prettier-plugin-svelte: - specifier: ^3.3.2 - version: 3.3.2(prettier@3.4.2)(svelte@5.19.1) + specifier: ^3.4.0 + version: 3.4.0(prettier@3.6.2)(svelte@5.35.2) prettier-plugin-tailwindcss: - specifier: ^0.6.9 - version: 0.6.9(prettier-plugin-svelte@3.3.2(prettier@3.4.2)(svelte@5.19.1))(prettier@3.4.2) + specifier: ^0.6.13 + version: 0.6.13(prettier-plugin-svelte@3.4.0(prettier@3.6.2)(svelte@5.35.2))(prettier@3.6.2) svelte: - specifier: ^5.19.1 - version: 5.19.1 + specifier: ^5.35.2 + version: 5.35.2 svelte-check: - specifier: ^4.1.3 - version: 4.1.3(picomatch@4.0.2)(svelte@5.19.1)(typescript@5.7.3) + specifier: ^4.2.2 + version: 4.2.2(picomatch@4.0.2)(svelte@5.35.2)(typescript@5.8.3) tailwindcss: specifier: ^3.4.17 version: 3.4.17 typescript: - specifier: ^5.7.3 - version: 5.7.3 + specifier: ^5.8.3 + version: 5.8.3 typescript-eslint: - specifier: ^8.19.1 - version: 8.19.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3) + specifier: ^8.35.1 + version: 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) unplugin-icons: specifier: ^0.22.0 - version: 0.22.0(svelte@5.19.1) + version: 0.22.0(svelte@5.35.2) vite: - specifier: ^5.4.11 - version: 5.4.11(@types/node@22.10.5) + specifier: ^5.4.19 + version: 5.4.19(@types/node@24.0.10) packages: @@ -143,23 +143,26 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - '@antfu/install-pkg@0.4.1': - resolution: {integrity: sha512-T7yB5QNG29afhWVkVq7XeIMBa5U/vs9mX69YqayXypPRmYzUmzwnYltplHmPtZ4HPCn+sQKeXW8I47wCbuBOjw==} - '@antfu/install-pkg@0.5.0': resolution: {integrity: sha512-dKnk2xlAyC7rvTkpkHmu+Qy/2Zc3Vm/l8PtNyIOGDBtXPY3kThfU4ORNEp3V7SXw5XSOb+tOJaUYpfquPzL/Tg==} + '@antfu/install-pkg@1.1.0': + resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} + '@antfu/utils@0.7.10': resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==} + '@antfu/utils@8.1.1': + resolution: {integrity: sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==} + '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.23.1': - resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} + '@esbuild/aix-ppc64@0.25.5': + resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] @@ -170,8 +173,8 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.23.1': - resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} + '@esbuild/android-arm64@0.25.5': + resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==} engines: {node: '>=18'} cpu: [arm64] os: [android] @@ -182,8 +185,8 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-arm@0.23.1': - resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} + '@esbuild/android-arm@0.25.5': + resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==} engines: {node: '>=18'} cpu: [arm] os: [android] @@ -194,8 +197,8 @@ packages: cpu: [x64] os: [android] - '@esbuild/android-x64@0.23.1': - resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} + '@esbuild/android-x64@0.25.5': + resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==} engines: {node: '>=18'} cpu: [x64] os: [android] @@ -206,8 +209,8 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.23.1': - resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} + '@esbuild/darwin-arm64@0.25.5': + resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] @@ -218,8 +221,8 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.23.1': - resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} + '@esbuild/darwin-x64@0.25.5': + resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] @@ -230,8 +233,8 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.23.1': - resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} + '@esbuild/freebsd-arm64@0.25.5': + resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] @@ -242,8 +245,8 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.23.1': - resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} + '@esbuild/freebsd-x64@0.25.5': + resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] @@ -254,8 +257,8 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.23.1': - resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} + '@esbuild/linux-arm64@0.25.5': + resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==} engines: {node: '>=18'} cpu: [arm64] os: [linux] @@ -266,8 +269,8 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.23.1': - resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} + '@esbuild/linux-arm@0.25.5': + resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==} engines: {node: '>=18'} cpu: [arm] os: [linux] @@ -278,8 +281,8 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.23.1': - resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} + '@esbuild/linux-ia32@0.25.5': + resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==} engines: {node: '>=18'} cpu: [ia32] os: [linux] @@ -290,8 +293,8 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.23.1': - resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} + '@esbuild/linux-loong64@0.25.5': + resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] @@ -302,8 +305,8 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.23.1': - resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} + '@esbuild/linux-mips64el@0.25.5': + resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] @@ -314,8 +317,8 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.23.1': - resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} + '@esbuild/linux-ppc64@0.25.5': + resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] @@ -326,8 +329,8 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.23.1': - resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} + '@esbuild/linux-riscv64@0.25.5': + resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] @@ -338,8 +341,8 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.23.1': - resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} + '@esbuild/linux-s390x@0.25.5': + resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==} engines: {node: '>=18'} cpu: [s390x] os: [linux] @@ -350,26 +353,32 @@ packages: cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.23.1': - resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} + '@esbuild/linux-x64@0.25.5': + resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==} engines: {node: '>=18'} cpu: [x64] os: [linux] + '@esbuild/netbsd-arm64@0.25.5': + resolution: {integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.21.5': resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.23.1': - resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} + '@esbuild/netbsd-x64@0.25.5': + resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.23.1': - resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} + '@esbuild/openbsd-arm64@0.25.5': + resolution: {integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] @@ -380,8 +389,8 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.23.1': - resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} + '@esbuild/openbsd-x64@0.25.5': + resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] @@ -392,8 +401,8 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.23.1': - resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} + '@esbuild/sunos-x64@0.25.5': + resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] @@ -404,8 +413,8 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.23.1': - resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} + '@esbuild/win32-arm64@0.25.5': + resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==} engines: {node: '>=18'} cpu: [arm64] os: [win32] @@ -416,8 +425,8 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.23.1': - resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} + '@esbuild/win32-ia32@0.25.5': + resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] @@ -428,14 +437,14 @@ packages: cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.23.1': - resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} + '@esbuild/win32-x64@0.25.5': + resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==} engines: {node: '>=18'} cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.4.1': - resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} + '@eslint-community/eslint-utils@4.7.0': + resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 @@ -444,37 +453,45 @@ packages: resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/compat@1.2.4': - resolution: {integrity: sha512-S8ZdQj/N69YAtuqFt7653jwcvuUj131+6qGLUyDqfDg1OIoBQ66OCuXC473YQfO2AaxITTutiRQiDwoo7ZLYyg==} + '@eslint/compat@1.3.1': + resolution: {integrity: sha512-k8MHony59I5EPic6EQTCNOuPoVBnoYXkP+20xvwFjN7t0qI3ImyvyBgg+hIVPwC8JaxVjjUZld+cLfBLFDLucg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^9.10.0 + eslint: ^8.40 || 9 peerDependenciesMeta: eslint: optional: true - '@eslint/config-array@0.19.1': - resolution: {integrity: sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==} + '@eslint/config-array@0.21.0': + resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@0.9.1': - resolution: {integrity: sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==} + '@eslint/config-helpers@0.3.0': + resolution: {integrity: sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/eslintrc@3.2.0': - resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==} + '@eslint/core@0.14.0': + resolution: {integrity: sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.17.0': - resolution: {integrity: sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==} + '@eslint/core@0.15.1': + resolution: {integrity: sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/object-schema@2.1.5': - resolution: {integrity: sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==} + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.2.4': - resolution: {integrity: sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==} + '@eslint/js@9.30.1': + resolution: {integrity: sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.6': + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.3.3': + resolution: {integrity: sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@fastify/busboy@3.1.1': @@ -496,40 +513,35 @@ packages: resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} engines: {node: '>=18.18'} - '@humanwhocodes/retry@0.4.1': - resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==} + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} - '@iconify-json/material-symbols@1.2.12': - resolution: {integrity: sha512-2p2T13Kccy7R2HNbdiVsIcHxjp4s9a+iKlfbtt29hldG1pVNaPIlMALNA9bjdEwPjwsVFe06INCbjCRc68JysQ==} + '@iconify-json/material-symbols@1.2.29': + resolution: {integrity: sha512-UUSrsl0gHF0GjAB9eZOpXrj7/v55ayMzo3QnMwUqP/FSfSkITKLR7CsBmUIFS8eEj8eRTfBNWA1yiIJR6UOdWg==} '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} - '@iconify/utils@2.2.1': - resolution: {integrity: sha512-0/7J7hk4PqXmxo5PDBDxmnecw5PxklZJfNjIVG9FM0mEfVrvfudS22rYWsqVk6gR3UJ/mSYS90X4R3znXnqfNA==} + '@iconify/utils@2.3.0': + resolution: {integrity: sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==} '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} - '@jridgewell/gen-mapping@0.3.8': - resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} - engines: {node: '>=6.0.0'} + '@jridgewell/gen-mapping@0.3.12': + resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==} '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} - '@jridgewell/set-array@1.2.1': - resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} - engines: {node: '>=6.0.0'} + '@jridgewell/sourcemap-codec@1.5.4': + resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==} - '@jridgewell/sourcemap-codec@1.5.0': - resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} - - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@jridgewell/trace-mapping@0.3.29': + resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -551,11 +563,11 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@polka/url@1.0.0-next.28': - resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==} + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} - '@rollup/plugin-commonjs@28.0.2': - resolution: {integrity: sha512-BEFI2EDqzl+vA1rl97IDRZ61AIwGH093d9nz8+dThxJNH8oSoB7MjWvPCX3dkaK1/RCJ/1v/R1XB15FuSs0fQw==} + '@rollup/plugin-commonjs@28.0.6': + resolution: {integrity: sha512-XSQB1K7FUU5QP+3lOQmVCE3I0FcbbNvmNT4VJSj93iUjayaARrTQeoRdiYQoftAJBLrR9t2agwAd3ekaTgHNlw==} engines: {node: '>=16.0.0 || 14 >= 14.17'} peerDependencies: rollup: ^2.68.0||^3.0.0||^4.0.0 @@ -572,8 +584,8 @@ packages: rollup: optional: true - '@rollup/plugin-node-resolve@16.0.0': - resolution: {integrity: sha512-0FPvAeVUT/zdWoO0jnb/V5BlBsUSNfkIOtFHzMO4H9MOklrmQFY6FduVHKucNb/aTFxvnGhj4MNj/T1oNdDfNg==} + '@rollup/plugin-node-resolve@16.0.1': + resolution: {integrity: sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA==} engines: {node: '>=14.0.0'} peerDependencies: rollup: ^2.78.0||^3.0.0||^4.0.0 @@ -581,8 +593,8 @@ packages: rollup: optional: true - '@rollup/pluginutils@5.1.4': - resolution: {integrity: sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==} + '@rollup/pluginutils@5.2.0': + resolution: {integrity: sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==} engines: {node: '>=14.0.0'} peerDependencies: rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 @@ -590,114 +602,124 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.30.1': - resolution: {integrity: sha512-pSWY+EVt3rJ9fQ3IqlrEUtXh3cGqGtPDH1FQlNZehO2yYxCHEX1SPsz1M//NXwYfbTlcKr9WObLnJX9FsS9K1Q==} + '@rollup/rollup-android-arm-eabi@4.44.2': + resolution: {integrity: sha512-g0dF8P1e2QYPOj1gu7s/3LVP6kze9A7m6x0BZ9iTdXK8N5c2V7cpBKHV3/9A4Zd8xxavdhK0t4PnqjkqVmUc9Q==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.30.1': - resolution: {integrity: sha512-/NA2qXxE3D/BRjOJM8wQblmArQq1YoBVJjrjoTSBS09jgUisq7bqxNHJ8kjCHeV21W/9WDGwJEWSN0KQ2mtD/w==} + '@rollup/rollup-android-arm64@4.44.2': + resolution: {integrity: sha512-Yt5MKrOosSbSaAK5Y4J+vSiID57sOvpBNBR6K7xAaQvk3MkcNVV0f9fE20T+41WYN8hDn6SGFlFrKudtx4EoxA==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.30.1': - resolution: {integrity: sha512-r7FQIXD7gB0WJ5mokTUgUWPl0eYIH0wnxqeSAhuIwvnnpjdVB8cRRClyKLQr7lgzjctkbp5KmswWszlwYln03Q==} + '@rollup/rollup-darwin-arm64@4.44.2': + resolution: {integrity: sha512-EsnFot9ZieM35YNA26nhbLTJBHD0jTwWpPwmRVDzjylQT6gkar+zenfb8mHxWpRrbn+WytRRjE0WKsfaxBkVUA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.30.1': - resolution: {integrity: sha512-x78BavIwSH6sqfP2xeI1hd1GpHL8J4W2BXcVM/5KYKoAD3nNsfitQhvWSw+TFtQTLZ9OmlF+FEInEHyubut2OA==} + '@rollup/rollup-darwin-x64@4.44.2': + resolution: {integrity: sha512-dv/t1t1RkCvJdWWxQ2lWOO+b7cMsVw5YFaS04oHpZRWehI1h0fV1gF4wgGCTyQHHjJDfbNpwOi6PXEafRBBezw==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.30.1': - resolution: {integrity: sha512-HYTlUAjbO1z8ywxsDFWADfTRfTIIy/oUlfIDmlHYmjUP2QRDTzBuWXc9O4CXM+bo9qfiCclmHk1x4ogBjOUpUQ==} + '@rollup/rollup-freebsd-arm64@4.44.2': + resolution: {integrity: sha512-W4tt4BLorKND4qeHElxDoim0+BsprFTwb+vriVQnFFtT/P6v/xO5I99xvYnVzKWrK6j7Hb0yp3x7V5LUbaeOMg==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.30.1': - resolution: {integrity: sha512-1MEdGqogQLccphhX5myCJqeGNYTNcmTyaic9S7CG3JhwuIByJ7J05vGbZxsizQthP1xpVx7kd3o31eOogfEirw==} + '@rollup/rollup-freebsd-x64@4.44.2': + resolution: {integrity: sha512-tdT1PHopokkuBVyHjvYehnIe20fxibxFCEhQP/96MDSOcyjM/shlTkZZLOufV3qO6/FQOSiJTBebhVc12JyPTA==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.30.1': - resolution: {integrity: sha512-PaMRNBSqCx7K3Wc9QZkFx5+CX27WFpAMxJNiYGAXfmMIKC7jstlr32UhTgK6T07OtqR+wYlWm9IxzennjnvdJg==} + '@rollup/rollup-linux-arm-gnueabihf@4.44.2': + resolution: {integrity: sha512-+xmiDGGaSfIIOXMzkhJ++Oa0Gwvl9oXUeIiwarsdRXSe27HUIvjbSIpPxvnNsRebsNdUo7uAiQVgBD1hVriwSQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.30.1': - resolution: {integrity: sha512-B8Rcyj9AV7ZlEFqvB5BubG5iO6ANDsRKlhIxySXcF1axXYUyqwBok+XZPgIYGBgs7LDXfWfifxhw0Ik57T0Yug==} + '@rollup/rollup-linux-arm-musleabihf@4.44.2': + resolution: {integrity: sha512-bDHvhzOfORk3wt8yxIra8N4k/N0MnKInCW5OGZaeDYa/hMrdPaJzo7CSkjKZqX4JFUWjUGm88lI6QJLCM7lDrA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.30.1': - resolution: {integrity: sha512-hqVyueGxAj3cBKrAI4aFHLV+h0Lv5VgWZs9CUGqr1z0fZtlADVV1YPOij6AhcK5An33EXaxnDLmJdQikcn5NEw==} + '@rollup/rollup-linux-arm64-gnu@4.44.2': + resolution: {integrity: sha512-NMsDEsDiYghTbeZWEGnNi4F0hSbGnsuOG+VnNvxkKg0IGDvFh7UVpM/14mnMwxRxUf9AdAVJgHPvKXf6FpMB7A==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.30.1': - resolution: {integrity: sha512-i4Ab2vnvS1AE1PyOIGp2kXni69gU2DAUVt6FSXeIqUCPIR3ZlheMW3oP2JkukDfu3PsexYRbOiJrY+yVNSk9oA==} + '@rollup/rollup-linux-arm64-musl@4.44.2': + resolution: {integrity: sha512-lb5bxXnxXglVq+7imxykIp5xMq+idehfl+wOgiiix0191av84OqbjUED+PRC5OA8eFJYj5xAGcpAZ0pF2MnW+A==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.30.1': - resolution: {integrity: sha512-fARcF5g296snX0oLGkVxPmysetwUk2zmHcca+e9ObOovBR++9ZPOhqFUM61UUZ2EYpXVPN1redgqVoBB34nTpQ==} + '@rollup/rollup-linux-loongarch64-gnu@4.44.2': + resolution: {integrity: sha512-Yl5Rdpf9pIc4GW1PmkUGHdMtbx0fBLE1//SxDmuf3X0dUC57+zMepow2LK0V21661cjXdTn8hO2tXDdAWAqE5g==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.30.1': - resolution: {integrity: sha512-GLrZraoO3wVT4uFXh67ElpwQY0DIygxdv0BNW9Hkm3X34wu+BkqrDrkcsIapAY+N2ATEbvak0XQ9gxZtCIA5Rw==} + '@rollup/rollup-linux-powerpc64le-gnu@4.44.2': + resolution: {integrity: sha512-03vUDH+w55s680YYryyr78jsO1RWU9ocRMaeV2vMniJJW/6HhoTBwyyiiTPVHNWLnhsnwcQ0oH3S9JSBEKuyqw==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.30.1': - resolution: {integrity: sha512-0WKLaAUUHKBtll0wvOmh6yh3S0wSU9+yas923JIChfxOaaBarmb/lBKPF0w/+jTVozFnOXJeRGZ8NvOxvk/jcw==} + '@rollup/rollup-linux-riscv64-gnu@4.44.2': + resolution: {integrity: sha512-iYtAqBg5eEMG4dEfVlkqo05xMOk6y/JXIToRca2bAWuqjrJYJlx/I7+Z+4hSrsWU8GdJDFPL4ktV3dy4yBSrzg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.30.1': - resolution: {integrity: sha512-GWFs97Ruxo5Bt+cvVTQkOJ6TIx0xJDD/bMAOXWJg8TCSTEK8RnFeOeiFTxKniTc4vMIaWvCplMAFBt9miGxgkA==} + '@rollup/rollup-linux-riscv64-musl@4.44.2': + resolution: {integrity: sha512-e6vEbgaaqz2yEHqtkPXa28fFuBGmUJ0N2dOJK8YUfijejInt9gfCSA7YDdJ4nYlv67JfP3+PSWFX4IVw/xRIPg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.44.2': + resolution: {integrity: sha512-evFOtkmVdY3udE+0QKrV5wBx7bKI0iHz5yEVx5WqDJkxp9YQefy4Mpx3RajIVcM6o7jxTvVd/qpC1IXUhGc1Mw==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.30.1': - resolution: {integrity: sha512-UtgGb7QGgXDIO+tqqJ5oZRGHsDLO8SlpE4MhqpY9Llpzi5rJMvrK6ZGhsRCST2abZdBqIBeXW6WPD5fGK5SDwg==} + '@rollup/rollup-linux-x64-gnu@4.44.2': + resolution: {integrity: sha512-/bXb0bEsWMyEkIsUL2Yt5nFB5naLAwyOWMEviQfQY1x3l5WsLKgvZf66TM7UTfED6erckUVUJQ/jJ1FSpm3pRQ==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.30.1': - resolution: {integrity: sha512-V9U8Ey2UqmQsBT+xTOeMzPzwDzyXmnAoO4edZhL7INkwQcaW1Ckv3WJX3qrrp/VHaDkEWIBWhRwP47r8cdrOow==} + '@rollup/rollup-linux-x64-musl@4.44.2': + resolution: {integrity: sha512-3D3OB1vSSBXmkGEZR27uiMRNiwN08/RVAcBKwhUYPaiZ8bcvdeEwWPvbnXvvXHY+A/7xluzcN+kaiOFNiOZwWg==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.30.1': - resolution: {integrity: sha512-WabtHWiPaFF47W3PkHnjbmWawnX/aE57K47ZDT1BXTS5GgrBUEpvOzq0FI0V/UYzQJgdb8XlhVNH8/fwV8xDjw==} + '@rollup/rollup-win32-arm64-msvc@4.44.2': + resolution: {integrity: sha512-VfU0fsMK+rwdK8mwODqYeM2hDrF2WiHaSmCBrS7gColkQft95/8tphyzv2EupVxn3iE0FI78wzffoULH1G+dkw==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.30.1': - resolution: {integrity: sha512-pxHAU+Zv39hLUTdQQHUVHf4P+0C47y/ZloorHpzs2SXMRqeAWmGghzAhfOlzFHHwjvgokdFAhC4V+6kC1lRRfw==} + '@rollup/rollup-win32-ia32-msvc@4.44.2': + resolution: {integrity: sha512-+qMUrkbUurpE6DVRjiJCNGZBGo9xM4Y0FXU5cjgudWqIBWbcLkjE3XprJUsOFgC6xjBClwVa9k6O3A7K3vxb5Q==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.30.1': - resolution: {integrity: sha512-D6qjsXGcvhTjv0kI4fU8tUuBDF/Ueee4SVX79VfNDXZa64TfCW1Slkb6Z7O1p7vflqZjcmOVdZlqf8gvJxc6og==} + '@rollup/rollup-win32-x64-msvc@4.44.2': + resolution: {integrity: sha512-3+QZROYfJ25PDcxFF66UEk8jGWigHJeecZILvkPkyQN7oc5BvFo4YEXFkOs154j3FTMp9mn9Ky8RCOwastduEA==} cpu: [x64] os: [win32] - '@sveltejs/adapter-node@5.2.11': - resolution: {integrity: sha512-lR7/dfUaKFf3aI408KRDy/BVDYoqUws7zNOJz2Hl4JoshlTnMgdha3brXBRFXB+cWtYvJjjPhvmq3xqpbioi4w==} + '@sveltejs/acorn-typescript@1.0.5': + resolution: {integrity: sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==} + peerDependencies: + acorn: ^8.9.0 + + '@sveltejs/adapter-node@5.2.12': + resolution: {integrity: sha512-0bp4Yb3jKIEcZWVcJC/L1xXp9zzJS4hDwfb4VITAkfT4OVdkspSHsx7YhqJDbb2hgLl6R9Vs7VQR+fqIVOxPUQ==} peerDependencies: '@sveltejs/kit': ^2.4.0 - '@sveltejs/kit@2.15.2': - resolution: {integrity: sha512-p208T1kdM6zd8k4YXIUM60pLWQ8dZqehXSiqn4NulXHyHibX53uIAL2xtNL8GjxX2IVPqPRT978MwVYhCKExdQ==} + '@sveltejs/kit@2.22.2': + resolution: {integrity: sha512-2MvEpSYabUrsJAoq5qCOBGAlkICjfjunrnLcx3YAk2XV7TvAIhomlKsAgR4H/4uns5rAfYmj7Wet5KRtc8dPIg==} engines: {node: '>=18.13'} hasBin: true peerDependencies: - '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 + '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 svelte: ^4.0.0 || ^5.0.0-next.0 - vite: ^5.0.3 || ^6.0.0 + vite: ^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 '@sveltejs/vite-plugin-svelte-inspector@3.0.1': resolution: {integrity: sha512-2CKypmj1sM4GE7HjllT7UKmo4Q6L5xFRd7VMGEWhYnZ+wc6AUVU01IBd7yUi6WnFndEwWoMNOd6e8UjoN0nbvQ==} @@ -717,8 +739,8 @@ packages: '@types/cookie@0.6.0': resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} - '@types/estree@1.0.6': - resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} '@types/file-saver@2.0.7': resolution: {integrity: sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==} @@ -732,64 +754,76 @@ packages: '@types/node-schedule@2.1.7': resolution: {integrity: sha512-G7Z3R9H7r3TowoH6D2pkzUHPhcJrDF4Jz1JOQ80AX0K2DWTHoN9VC94XzFAPNMdbW9TBzMZ3LjpFi7RYdbxtXA==} - '@types/node@22.10.5': - resolution: {integrity: sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==} + '@types/node@24.0.10': + resolution: {integrity: sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==} - '@types/pg@8.11.10': - resolution: {integrity: sha512-LczQUW4dbOQzsH2RQ5qoeJ6qJPdrcM/DcMLoqWQkMLMsq83J5lAX3LXjdkWdpscFy67JSOWDnh7Ny/sPFykmkg==} + '@types/pg@8.15.4': + resolution: {integrity: sha512-I6UNVBAoYbvuWkkU3oosC8yxqH21f4/Jc4DK71JLG3dT2mdlGe1z+ep/LQGXaKaOgcvUrsQoPRqfgtMcvZiJhg==} '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} - '@typescript-eslint/eslint-plugin@8.19.1': - resolution: {integrity: sha512-tJzcVyvvb9h/PB96g30MpxACd9IrunT7GF9wfA9/0TJ1LxGOJx1TdPzSbBBnNED7K9Ka8ybJsnEpiXPktolTLg==} + '@typescript-eslint/eslint-plugin@8.35.1': + resolution: {integrity: sha512-9XNTlo7P7RJxbVeICaIIIEipqxLKguyh+3UbXuT2XQuFp6d8VOeDEGuz5IiX0dgZo8CiI6aOFLg4e8cF71SFVg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + '@typescript-eslint/parser': ^8.35.1 eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' + typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/parser@8.19.1': - resolution: {integrity: sha512-67gbfv8rAwawjYx3fYArwldTQKoYfezNUT4D5ioWetr/xCrxXxvleo3uuiFuKfejipvq+og7mjz3b0G2bVyUCw==} + '@typescript-eslint/parser@8.35.1': + resolution: {integrity: sha512-3MyiDfrfLeK06bi/g9DqJxP5pV74LNv4rFTyvGDmT3x2p1yp1lOd+qYZfiRPIOf/oON+WRZR5wxxuF85qOar+w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' + typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/scope-manager@8.19.1': - resolution: {integrity: sha512-60L9KIuN/xgmsINzonOcMDSB8p82h95hoBfSBtXuO4jlR1R9L1xSkmVZKgCPVfavDlXihh4ARNjXhh1gGnLC7Q==} + '@typescript-eslint/project-service@8.35.1': + resolution: {integrity: sha512-VYxn/5LOpVxADAuP3NrnxxHYfzVtQzLKeldIhDhzC8UHaiQvYlXvKuVho1qLduFbJjjy5U5bkGwa3rUGUb1Q6Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/scope-manager@8.35.1': + resolution: {integrity: sha512-s/Bpd4i7ht2934nG+UoSPlYXd08KYz3bmjLEb7Ye1UVob0d1ENiT3lY8bsCmik4RqfSbPw9xJJHbugpPpP5JUg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/type-utils@8.19.1': - resolution: {integrity: sha512-Rp7k9lhDKBMRJB/nM9Ksp1zs4796wVNyihG9/TU9R6KCJDNkQbc2EOKjrBtLYh3396ZdpXLtr/MkaSEmNMtykw==} + '@typescript-eslint/tsconfig-utils@8.35.1': + resolution: {integrity: sha512-K5/U9VmT9dTHoNowWZpz+/TObS3xqC5h0xAIjXPw+MNcKV9qg6eSatEnmeAwkjHijhACH0/N7bkhKvbt1+DXWQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/type-utils@8.35.1': + resolution: {integrity: sha512-HOrUBlfVRz5W2LIKpXzZoy6VTZzMu2n8q9C2V/cFngIC5U1nStJgv0tMV4sZPzdf4wQm9/ToWUFPMN9Vq9VJQQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' + typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/types@8.19.1': - resolution: {integrity: sha512-JBVHMLj7B1K1v1051ZaMMgLW4Q/jre5qGK0Ew6UgXz1Rqh+/xPzV1aW581OM00X6iOfyr1be+QyW8LOUf19BbA==} + '@typescript-eslint/types@8.35.1': + resolution: {integrity: sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.19.1': - resolution: {integrity: sha512-jk/TZwSMJlxlNnqhy0Eod1PNEvCkpY6MXOXE/WLlblZ6ibb32i2We4uByoKPv1d0OD2xebDv4hbs3fm11SMw8Q==} + '@typescript-eslint/typescript-estree@8.35.1': + resolution: {integrity: sha512-Vvpuvj4tBxIka7cPs6Y1uvM7gJgdF5Uu9F+mBJBPY4MhvjrjWGK4H0lVgLJd/8PWZ23FTqsaJaLEkBCFUk8Y9g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <5.8.0' + typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/utils@8.19.1': - resolution: {integrity: sha512-IxG5gLO0Ne+KaUc8iW1A+XuKLd63o4wlbI1Zp692n1xojCl/THvgIKXJXBZixTh5dd5+yTJ/VXH7GJaaw21qXA==} + '@typescript-eslint/utils@8.35.1': + resolution: {integrity: sha512-lhnwatFmOFcazAsUm3ZnZFpXSxiwoa1Lj50HphnDe1Et01NF4+hrdXONSUHIcbVu2eFb1bAf+5yjXkGVkXBKAQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' + typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/visitor-keys@8.19.1': - resolution: {integrity: sha512-fzmjU8CHK853V/avYZAvuVut3ZTfwN5YtMaoi+X9Y9MA9keaWNHC3zEQ9zvyX/7Hj+5JkNyK1l7TOR2hevHB6Q==} + '@typescript-eslint/visitor-keys@8.35.1': + resolution: {integrity: sha512-VRwixir4zBWCSTP/ljEo091lbpypz57PoeAQ9imjG+vbeof9LplljsL1mos4ccG6H9IjfrVGM359RozUnuFhpw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@xmldom/xmldom@0.9.6': - resolution: {integrity: sha512-Su4xcxR0CPGwlDHNmVP09fqET9YxbyDXHaSob6JlBH7L6reTYaeim6zbk9o08UarO0L5GTRo3uzl0D+9lSxmvw==} + '@xmldom/xmldom@0.9.8': + resolution: {integrity: sha512-p96FSY54r+WJ50FIOsCOjyj/wavs8921hG5+kVMmZgKcvIKxMXHTrjNJvRgWa/zuX3B6t2lijLNFaOyuxUH+2A==} engines: {node: '>=14.6'} acorn-jsx@5.3.2: @@ -797,13 +831,8 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn-typescript@1.4.13: - resolution: {integrity: sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==} - peerDependencies: - acorn: '>=8.9.0' - - acorn@8.14.0: - resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} engines: {node: '>=0.4.0'} hasBin: true @@ -850,15 +879,15 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - autoprefixer@10.4.20: - resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} + autoprefixer@10.4.21: + resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} engines: {node: ^10 || ^12 || >=14} hasBin: true peerDependencies: postcss: ^8.1.0 - axios@1.7.9: - resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==} + axios@1.10.0: + resolution: {integrity: sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==} axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} @@ -871,29 +900,33 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} - brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} - brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.24.4: - resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} + browserslist@4.25.1: + resolution: {integrity: sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - c12@2.0.1: - resolution: {integrity: sha512-Z4JgsKXHG37C6PYUtIxCfLJZvo6FyhHJoClwwb9ftUkLpPSkuYqn6Tr+vnaN8hymm0kIbcg6Ey3kv/Q71k5w/A==} + c12@2.0.4: + resolution: {integrity: sha512-3DbbhnFt0fKJHxU4tEUPmD1ahWE4PWPMomqfYsTJdrhpmEnRKJi3qSC4rO5U6E6zN1+pjBY7+z8fUmNRMaVKLw==} peerDependencies: magicast: ^0.3.5 peerDependenciesMeta: magicast: optional: true + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -902,8 +935,8 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} - caniuse-lite@1.0.30001692: - resolution: {integrity: sha512-A95VKan0kdtrsnMubMKxEKUKImOPSuCpYgxSQBo036P5YYgVIcOYJEgt/txJWqObiRQeISNCfef9nvlQ0vbV7A==} + caniuse-lite@1.0.30001726: + resolution: {integrity: sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==} chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} @@ -952,8 +985,11 @@ packages: confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} - consola@3.4.0: - resolution: {integrity: sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA==} + confbox@0.2.2: + resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} cookie@0.6.0: @@ -973,8 +1009,8 @@ packages: engines: {node: '>=4'} hasBin: true - debug@4.4.0: - resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' @@ -996,14 +1032,14 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} - destr@2.0.3: - resolution: {integrity: sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==} + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} devalue@5.1.1: resolution: {integrity: sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==} - dexie@4.0.10: - resolution: {integrity: sha512-eM2RzuR3i+M046r2Q0Optl3pS31qTWf8aFuA7H9wnsHTwl8EPvroVLwvQene/6paAs39Tbk6fWZcn2aZaHkc/w==} + dexie@4.0.11: + resolution: {integrity: sha512-SOKO002EqlvBYYKQSew3iymBoN2EQ4BDw/3yprjh7kAfFzjBYkaMNa/pZvcA7HSWlcKSQb9XhPe3wKyQ0x4A8A==} didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} @@ -1011,15 +1047,19 @@ packages: dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} - dotenv@16.4.7: - resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} engines: {node: '>=12'} + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - electron-to-chromium@1.5.79: - resolution: {integrity: sha512-nYOxJNxQ9Om4EC88BE4pPoNI8xwSFf8pU/BAeOl4Hh/b/i6V4biTAzwV7pXi3ARKeoYO5JZKMIXTryXSVer5RA==} + electron-to-chromium@1.5.179: + resolution: {integrity: sha512-UWKi/EbBopgfFsc5k61wFpV7WrnnSlSzW/e2XcBmS6qKYTivZlLtoll5/rdqRTxGglGHkmkW0j0pFNJG10EUIQ==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1027,13 +1067,29 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + 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==} + esbuild@0.25.5: + resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==} engines: {node: '>=18'} hasBin: true @@ -1067,8 +1123,8 @@ packages: svelte: optional: true - eslint-plugin-tailwindcss@3.17.5: - resolution: {integrity: sha512-8Mi7p7dm+mO1dHgRHHFdPu4RDTBk69Cn4P0B40vRQR+MrguUpwmKwhZy1kqYe3Km8/4nb+cyrCF+5SodOEmaow==} + eslint-plugin-tailwindcss@3.18.0: + resolution: {integrity: sha512-PQDU4ZMzFH0eb2DrfHPpbgo87Zgg2EXSMOj1NSfzdZm+aJzpuwGerfowMIaVehSREEa0idbf/eoNYAOHSJoDAQ==} engines: {node: '>=18.12.0'} peerDependencies: tailwindcss: ^3.4.0 @@ -1077,20 +1133,20 @@ packages: resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint-scope@8.2.0: - resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==} + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint-visitor-keys@4.2.0: - resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.17.0: - resolution: {integrity: sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==} + eslint@9.30.1: + resolution: {integrity: sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -1102,8 +1158,8 @@ packages: esm-env@1.2.2: resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} - espree@10.3.0: - resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} espree@9.6.1: @@ -1114,8 +1170,8 @@ packages: resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} engines: {node: '>=0.10'} - esrap@1.4.3: - resolution: {integrity: sha512-Xddc1RsoFJ4z9nR7W7BFaEPIp4UXoeQ0+077UdWLxbafMQFyU79sQJMk7kxNgRwQ9/aVgaKacCHC2pUACGwmYw==} + esrap@2.1.0: + resolution: {integrity: sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA==} esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} @@ -1132,12 +1188,11 @@ 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.31.1: + resolution: {integrity: sha512-rkSg/NejDN9D+8GuRWZ2Y4G8KSrj0hdaeMoew8d0J5cF5oS0p6DVar2PSQ0fP3assu6s1PYh6M1lhtS7Kigk6Q==} - exifreader@4.26.0: - resolution: {integrity: sha512-nNN9B0oaXTOpArdnIdJBAro2Sa620m7wMjMA5Xy1rcua0EYHVjzGKM5syBOWDqIG2Qay6Pes/5FOdj65hvZ9Vw==} + exsolve@1.0.7: + resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1152,11 +1207,11 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fastq@1.18.0: - resolution: {integrity: sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==} + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} - fdir@6.4.2: - resolution: {integrity: sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==} + fdir@6.4.6: + resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: @@ -1182,8 +1237,8 @@ packages: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} - flatted@3.3.2: - resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} follow-redirects@1.15.9: resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} @@ -1194,12 +1249,12 @@ packages: debug: optional: true - foreground-child@3.3.0: - resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} - form-data@4.0.1: - resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} + form-data@4.0.3: + resolution: {integrity: sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==} engines: {node: '>= 6'} fraction.js@4.3.7: @@ -1217,15 +1272,19 @@ 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-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} - get-tsconfig@4.8.1: - resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} - giget@1.2.3: - resolution: {integrity: sha512-8EHPljDvs7qKykr6uw8b+lqLiUc/vUg+KVTI0uND4s63TdsZM2Xus3mflvF0DDG9SiM4RlCkFGL+7aAjRmV7KA==} + get-tsconfig@4.10.1: + resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} + + giget@1.2.5: + resolution: {integrity: sha512-r1ekGw/Bgpi3HLV3h1MRBIlSAdHoIMklpaQ3OQLFcRw9PwAj2rqigvIbg+dBUI51OxVI2jsEtDywDBjSiuf7Ug==} hasBin: true glob-parent@5.1.2: @@ -1244,15 +1303,13 @@ packages: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} - globals@15.14.0: - resolution: {integrity: sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==} + globals@15.15.0: + resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} engines: {node: '>=18'} - globalyzer@0.1.0: - resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==} - - globrex@0.1.2: - resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} @@ -1261,6 +1318,14 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} @@ -1268,20 +1333,17 @@ packages: heic2any@0.0.4: resolution: {integrity: sha512-3lLnZiDELfabVH87htnRolZ2iehX9zwpRyGNz22GKXIu0fznlblf0/ftppXKNqS26dqFSeqfIBhAmAj/uSp0cA==} - 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==} engines: {node: '>= 4'} - import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} - import-meta-resolve@4.1.0: - resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==} + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} @@ -1320,10 +1382,6 @@ 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==} @@ -1375,9 +1433,9 @@ packages: kysely-postgres-js: optional: true - kysely@0.27.5: - resolution: {integrity: sha512-s7hZHcQeSNKpzCkHRm8yA+0JPLjncSWnjb+2TIElwS2JAqYr+Kv3Ess+9KFfJS0C1xcQ1i9NkNHpWwCYpHMWsA==} - engines: {node: '>=14.0.0'} + kysely@0.28.2: + resolution: {integrity: sha512-4YAVLoF0Sf0UTqlhgQMFU9iQECdah7n+13ANkiuVfRvlK+uI0Etbgd7bVP36dKlG+NXWbhGua8vnGt+sdhvT7A==} + engines: {node: '>=18.0.0'} levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} @@ -1398,6 +1456,10 @@ packages: resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==} engines: {node: '>=14'} + local-pkg@1.1.1: + resolution: {integrity: sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==} + engines: {node: '>=14'} + locate-character@3.0.0: resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} @@ -1414,15 +1476,16 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - luxon@3.5.0: - resolution: {integrity: sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==} + luxon@3.6.1: + resolution: {integrity: sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==} engines: {node: '>=12'} 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==} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} @@ -1440,15 +1503,11 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} - mime@4.0.6: - resolution: {integrity: sha512-4rGt7rvQHBbaSOF9POGkk1ocRP16Md1x36Xma8sz8h8/vfCUI2OtEIeCqe4Ofes853x4xDoPiFLIT47J5fI/7A==} + mime@4.0.7: + resolution: {integrity: sha512-2OfDPL+e03E0LrXaGYOtTFIYhiuzep94NSsuhrNULq+stylcJedcHdzHtz0atMUuGwJfFYs0YL5xeC/Ca2x0eQ==} engines: {node: '>=16'} hasBin: true - 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==} @@ -1477,15 +1536,15 @@ packages: engines: {node: '>=10'} hasBin: true - mlly@1.7.3: - resolution: {integrity: sha512-xUsx5n/mN0uQf4V548PKQ+YShA4/IW0KI1dZhrNrPCLG+xizETbHTkOa1f8/xut9JRPp8kQuMnz0oqwkTiLo/A==} + mlly@1.7.4: + resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==} mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} - mrmime@2.0.0: - resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==} + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} ms@2.1.3: @@ -1494,20 +1553,20 @@ packages: mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - nanoid@3.3.8: - resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - node-addon-api@8.3.0: - resolution: {integrity: sha512-8VOpLHFrOQlAH+qA0ZzuGRlALRA6/LVh8QJldbrC4DY0hXoMP0l4Acq8TzFC018HztWiRqyCEj2aTWY2UvnJUg==} + node-addon-api@8.4.0: + resolution: {integrity: sha512-D9DI/gXHvVmjHS08SVch0Em8G5S1P+QWtU31appcKT/8wFSPRcdHadIFSAntdMMVM5zz+/DL+bL/gz3UDppqtg==} engines: {node: ^18 || ^20 || >= 21} - node-fetch-native@1.6.4: - resolution: {integrity: sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==} + node-fetch-native@1.6.6: + resolution: {integrity: sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==} node-gyp-build@4.8.4: resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} @@ -1528,17 +1587,13 @@ 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==} + nypm@0.4.1: + resolution: {integrity: sha512-1b9mihliBh8UCcKtcGRu//G50iHpjxIQVUqkdhPT/SDVE7KdJKoHXLS0heuYTQCx95dFqiyUbXZB9r8ikn+93g==} engines: {node: ^14.16.0 || >=16.10.0} hasBin: true - nypm@0.4.1: - resolution: {integrity: sha512-1b9mihliBh8UCcKtcGRu//G50iHpjxIQVUqkdhPT/SDVE7KdJKoHXLS0heuYTQCx95dFqiyUbXZB9r8ikn+93g==} + nypm@0.5.4: + resolution: {integrity: sha512-X0SNNrZiGU8/e/zAB7sCTtdxWTMSIO73q+xuKgglm2Yvzwlo8UoC5FNySQFCvl84uPaeADkqHUZUkWy4aH4xOA==} engines: {node: ^14.16.0 || >=16.10.0} hasBin: true @@ -1550,18 +1605,11 @@ packages: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} - obuf@1.1.2: - resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} - 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'} + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} @@ -1582,8 +1630,11 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - package-manager-detector@0.2.8: - resolution: {integrity: sha512-ts9KSdroZisdvKMWVAVCXiKqnqNfXz4+IbrBG8/BWx/TR5le+jfenvoBuIZ6UWM9nz47W7AbD9qYfAwfWMIwzA==} + package-manager-detector@0.2.11: + resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} + + package-manager-detector@1.3.0: + resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==} parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} @@ -1597,10 +1648,6 @@ 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==} @@ -1611,42 +1658,37 @@ packages: pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + 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==} + pg-cloudflare@1.2.7: + resolution: {integrity: sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==} - pg-connection-string@2.7.0: - resolution: {integrity: sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==} + pg-connection-string@2.9.1: + resolution: {integrity: sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==} 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==} + pg-pool@3.10.1: + resolution: {integrity: sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==} peerDependencies: pg: '>=8.0' - pg-protocol@1.7.0: - resolution: {integrity: sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==} + pg-protocol@1.10.3: + resolution: {integrity: sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==} 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'} + pg@8.16.3: + resolution: {integrity: sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==} + engines: {node: '>= 16.0.0'} peerDependencies: pg-native: '>=3.0.1' peerDependenciesMeta: @@ -1671,12 +1713,15 @@ packages: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} - pirates@4.0.6: - resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} - pkg-types@1.3.0: - resolution: {integrity: sha512-kS7yWjVFCkIw9hqdJBoMxDdzEngmkr5FXeWZZfQ6GoYacjVnsW6l2CcYW/0ThD0vF4LPJgVYnrg4d0uuhwYQbg==} + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + pkg-types@2.2.0: + resolution: {integrity: sha512-2SM/GZGAEkPp3KWORxQZns4M+WSeXbC2HEvmOIJe3Cmiv6ieAJvdVhDldtHqM5J1Y7MrR1XhkBT/rMlhh9FdqQ==} postcss-import@15.1.0: resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} @@ -1739,64 +1784,45 @@ packages: postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@8.4.49: - resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} 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==} - prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prettier-plugin-svelte@3.3.2: - resolution: {integrity: sha512-kRPjH8wSj2iu+dO+XaUv4vD8qr5mdDmlak3IT/7AOgGIMRG86z/EHOLauFcClKEnOUf4A4nOA7sre5KrJD4Raw==} + prettier-plugin-svelte@3.4.0: + resolution: {integrity: sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ==} peerDependencies: prettier: ^3.0.0 svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0 - prettier-plugin-tailwindcss@0.6.9: - resolution: {integrity: sha512-r0i3uhaZAXYP0At5xGfJH876W3HHGHDp+LCRUJrs57PBeQ6mYHMwr25KH8NPX44F2yGTvdnH7OqCshlQx183Eg==} + prettier-plugin-tailwindcss@0.6.13: + resolution: {integrity: sha512-uQ0asli1+ic8xrrSmIOaElDu0FacR4x69GynTh2oZjFY10JUt6EEumTQl5tB4fMeD6I1naKd+4rXQQ7esT2i1g==} engines: {node: '>=14.21.3'} peerDependencies: '@ianvs/prettier-plugin-sort-imports': '*' '@prettier/plugin-pug': '*' '@shopify/prettier-plugin-liquid': '*' '@trivago/prettier-plugin-sort-imports': '*' - '@zackad/prettier-plugin-twig-melody': '*' + '@zackad/prettier-plugin-twig': '*' prettier: ^3.0 prettier-plugin-astro: '*' prettier-plugin-css-order: '*' @@ -1818,7 +1844,7 @@ packages: optional: true '@trivago/prettier-plugin-sort-imports': optional: true - '@zackad/prettier-plugin-twig-melody': + '@zackad/prettier-plugin-twig': optional: true prettier-plugin-astro: optional: true @@ -1843,8 +1869,8 @@ packages: prettier-plugin-svelte: optional: true - prettier@3.4.2: - resolution: {integrity: sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==} + prettier@3.6.2: + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} engines: {node: '>=14'} hasBin: true @@ -1855,6 +1881,9 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + quansync@0.2.10: + resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -1868,9 +1897,9 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} - readdirp@4.0.2: - resolution: {integrity: sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==} - engines: {node: '>= 14.16.0'} + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} @@ -1884,12 +1913,12 @@ packages: engines: {node: '>= 0.4'} hasBin: true - reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rollup@4.30.1: - resolution: {integrity: sha512-mlJ4glW020fPuLi7DkM/lN97mYEZGWeqBnrljzN0gs7GLctqX3lNWxKQ7Gl712UAX+6fog/L3jh4gb7R6aVi3w==} + rollup@4.44.2: + resolution: {integrity: sha512-PVoapzTwSEcelaWGth3uR66u7ZRo6qhPHc0f2uRO9fX6XDVNrIiGYS0Pj9+R8yIIYSD/mCx2b16Ws9itljKSPg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -1900,8 +1929,8 @@ packages: resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} engines: {node: '>=6'} - semver@7.6.3: - resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} engines: {node: '>=10'} hasBin: true @@ -1920,8 +1949,8 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - sirv@3.0.0: - resolution: {integrity: sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==} + sirv@3.0.1: + resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==} engines: {node: '>=18'} sorted-array-functions@1.3.0: @@ -1935,8 +1964,8 @@ packages: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} - std-env@3.8.0: - resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} + std-env@3.9.0: + resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} @@ -1954,10 +1983,6 @@ packages: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} - 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==} engines: {node: '>=8'} @@ -1975,8 +2000,8 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - svelte-check@4.1.3: - resolution: {integrity: sha512-IEMoQDH+TrPKwKeIyJim+PU8FxnzQMXsFHR/ldErkHpPXEGHCujHUXiR8jg6qDMqzsif5BbDOUFORltu87ex7g==} + svelte-check@4.2.2: + resolution: {integrity: sha512-1+31EOYZ7NKN0YDMKusav2hhEoA51GD9Ws6o//0SphMT0ve9mBTsTUEX7OmDMadUP3KjNHsSKtJrqdSaD8CrGQ==} engines: {node: '>= 18.0.0'} hasBin: true peerDependencies: @@ -1992,8 +2017,8 @@ packages: svelte: optional: true - svelte@5.19.1: - resolution: {integrity: sha512-H/Vs2O51bwILZbaNUSdr4P1NbLpOGsxl4jJAjd88ELjzRgeRi1BHqexcVGannDr7D1pmTYWWajzHOM7bMbtB9Q==} + svelte@5.35.2: + resolution: {integrity: sha512-uW/rRXYrhZ7Dh4UQNZ0t+oVGL1dEM+95GavCO8afAk1IY2cPq9BcZv9C3um5aLIya2y8lIeLPxLII9ASGg9Dzw==} engines: {node: '>=18'} tailwindcss@3.4.17: @@ -2012,12 +2037,12 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - tiny-glob@0.2.9: - resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==} - tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinyexec@1.0.1: + resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -2026,8 +2051,8 @@ packages: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} - ts-api-utils@2.0.0: - resolution: {integrity: sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==} + ts-api-utils@2.1.0: + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} engines: {node: '>=18.12'} peerDependencies: typescript: '>=4.8.4' @@ -2035,8 +2060,8 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - tsx@4.19.2: - resolution: {integrity: sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==} + tsx@4.20.3: + resolution: {integrity: sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==} engines: {node: '>=18.0.0'} hasBin: true @@ -2044,23 +2069,23 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - typescript-eslint@8.19.1: - resolution: {integrity: sha512-LKPUQpdEMVOeKluHi8md7rwLcoXHhwvWp3x+sJkMuq3gGm9yaYJtPo8sRZSblMFJ5pcOGCAak/scKf1mvZDlQw==} + typescript-eslint@8.35.1: + resolution: {integrity: sha512-xslJjFzhOmHYQzSB/QTeASAHbjmxOGEP6Coh93TXmUBFQoJ1VU35UHIDmG06Jd6taf3wqqC1ntBnCMeymy5Ovw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' + typescript: '>=4.8.4 <5.9.0' - typescript@5.7.3: - resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} engines: {node: '>=14.17'} hasBin: true - ufo@1.5.4: - resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} + ufo@1.6.1: + resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} - undici-types@6.20.0: - resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + undici-types@7.8.0: + resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} unplugin-icons@0.22.0: resolution: {integrity: sha512-CP+iZq5U7doOifer5bcM0jQ9t3Is7EGybIYt3myVxceI8Zuk8EZEpe1NPtJvh7iqMs1VdbK0L41t9+um9VuuLw==} @@ -2085,12 +2110,12 @@ packages: vue-template-es2015-compiler: optional: true - unplugin@2.1.2: - resolution: {integrity: sha512-Q3LU0e4zxKfRko1wMV2HmP8lB9KWislY7hxXpxd+lGx0PRInE4vhMBVEZwpdVYHvtqzhSrzuIfErsob6bQfCzw==} + unplugin@2.3.5: + resolution: {integrity: sha512-RyWSb5AHmGtjjNQ6gIlA67sHOsWpsbWpwDokLwTcejVdOjEkJZh7QKu14J00gDDVSh8kGH4KYC/TNBceXFZhtw==} engines: {node: '>=18.12.0'} - update-browserslist-db@1.1.2: - resolution: {integrity: sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==} + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -2101,12 +2126,12 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - uuid@11.0.4: - resolution: {integrity: sha512-IzL6VtTTYcAhA/oghbFJ1Dkmqev+FpQWnCBaKq/gUluLxliWvO8DPFWfIviRmYbtaavtSQe4WBL++rFjdcGWEg==} + uuid@11.1.0: + resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} hasBin: true - vite@5.4.11: - resolution: {integrity: sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==} + vite@5.4.19: + resolution: {integrity: sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -2136,10 +2161,10 @@ packages: terser: optional: true - vitefu@1.0.5: - resolution: {integrity: sha512-h4Vflt9gxODPFNGPwp4zAMZRpZR7eslzwH2c5hn5kNZ5rhnKyRJ50U+yGCdc2IRaBs8O4haIgLNGrV5CrpMsCA==} + vitefu@1.1.1: + resolution: {integrity: sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==} peerDependencies: - vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0 peerDependenciesMeta: vite: optional: true @@ -2175,24 +2200,24 @@ packages: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} - yaml@2.7.0: - resolution: {integrity: sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==} - engines: {node: '>= 14'} + yaml@2.8.0: + resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==} + engines: {node: '>= 14.6'} hasBin: true yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - yocto-queue@1.1.1: - resolution: {integrity: sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==} + yocto-queue@1.2.1: + resolution: {integrity: sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==} engines: {node: '>=12.20'} zimmerframe@1.1.2: resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==} - zod@3.24.1: - resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==} + zod@3.25.73: + resolution: {integrity: sha512-fuIKbQAWQl22Ba5d1quwEETQYjqnpKVyZIWAhbnnHgnDd3a+z4YgEfkI5SZ2xMELnLAXo/Flk2uXgysZNf0uaA==} snapshots: @@ -2200,205 +2225,217 @@ snapshots: '@ampproject/remapping@2.3.0': dependencies: - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.25 - - '@antfu/install-pkg@0.4.1': - dependencies: - package-manager-detector: 0.2.8 - tinyexec: 0.3.2 + '@jridgewell/gen-mapping': 0.3.12 + '@jridgewell/trace-mapping': 0.3.29 '@antfu/install-pkg@0.5.0': dependencies: - package-manager-detector: 0.2.8 + package-manager-detector: 0.2.11 tinyexec: 0.3.2 + '@antfu/install-pkg@1.1.0': + dependencies: + package-manager-detector: 1.3.0 + tinyexec: 1.0.1 + '@antfu/utils@0.7.10': {} + '@antfu/utils@8.1.1': {} + '@esbuild/aix-ppc64@0.21.5': optional: true - '@esbuild/aix-ppc64@0.23.1': + '@esbuild/aix-ppc64@0.25.5': optional: true '@esbuild/android-arm64@0.21.5': optional: true - '@esbuild/android-arm64@0.23.1': + '@esbuild/android-arm64@0.25.5': optional: true '@esbuild/android-arm@0.21.5': optional: true - '@esbuild/android-arm@0.23.1': + '@esbuild/android-arm@0.25.5': optional: true '@esbuild/android-x64@0.21.5': optional: true - '@esbuild/android-x64@0.23.1': + '@esbuild/android-x64@0.25.5': optional: true '@esbuild/darwin-arm64@0.21.5': optional: true - '@esbuild/darwin-arm64@0.23.1': + '@esbuild/darwin-arm64@0.25.5': optional: true '@esbuild/darwin-x64@0.21.5': optional: true - '@esbuild/darwin-x64@0.23.1': + '@esbuild/darwin-x64@0.25.5': optional: true '@esbuild/freebsd-arm64@0.21.5': optional: true - '@esbuild/freebsd-arm64@0.23.1': + '@esbuild/freebsd-arm64@0.25.5': optional: true '@esbuild/freebsd-x64@0.21.5': optional: true - '@esbuild/freebsd-x64@0.23.1': + '@esbuild/freebsd-x64@0.25.5': optional: true '@esbuild/linux-arm64@0.21.5': optional: true - '@esbuild/linux-arm64@0.23.1': + '@esbuild/linux-arm64@0.25.5': optional: true '@esbuild/linux-arm@0.21.5': optional: true - '@esbuild/linux-arm@0.23.1': + '@esbuild/linux-arm@0.25.5': optional: true '@esbuild/linux-ia32@0.21.5': optional: true - '@esbuild/linux-ia32@0.23.1': + '@esbuild/linux-ia32@0.25.5': optional: true '@esbuild/linux-loong64@0.21.5': optional: true - '@esbuild/linux-loong64@0.23.1': + '@esbuild/linux-loong64@0.25.5': optional: true '@esbuild/linux-mips64el@0.21.5': optional: true - '@esbuild/linux-mips64el@0.23.1': + '@esbuild/linux-mips64el@0.25.5': optional: true '@esbuild/linux-ppc64@0.21.5': optional: true - '@esbuild/linux-ppc64@0.23.1': + '@esbuild/linux-ppc64@0.25.5': optional: true '@esbuild/linux-riscv64@0.21.5': optional: true - '@esbuild/linux-riscv64@0.23.1': + '@esbuild/linux-riscv64@0.25.5': optional: true '@esbuild/linux-s390x@0.21.5': optional: true - '@esbuild/linux-s390x@0.23.1': + '@esbuild/linux-s390x@0.25.5': optional: true '@esbuild/linux-x64@0.21.5': optional: true - '@esbuild/linux-x64@0.23.1': + '@esbuild/linux-x64@0.25.5': + optional: true + + '@esbuild/netbsd-arm64@0.25.5': optional: true '@esbuild/netbsd-x64@0.21.5': optional: true - '@esbuild/netbsd-x64@0.23.1': + '@esbuild/netbsd-x64@0.25.5': optional: true - '@esbuild/openbsd-arm64@0.23.1': + '@esbuild/openbsd-arm64@0.25.5': optional: true '@esbuild/openbsd-x64@0.21.5': optional: true - '@esbuild/openbsd-x64@0.23.1': + '@esbuild/openbsd-x64@0.25.5': optional: true '@esbuild/sunos-x64@0.21.5': optional: true - '@esbuild/sunos-x64@0.23.1': + '@esbuild/sunos-x64@0.25.5': optional: true '@esbuild/win32-arm64@0.21.5': optional: true - '@esbuild/win32-arm64@0.23.1': + '@esbuild/win32-arm64@0.25.5': optional: true '@esbuild/win32-ia32@0.21.5': optional: true - '@esbuild/win32-ia32@0.23.1': + '@esbuild/win32-ia32@0.25.5': optional: true '@esbuild/win32-x64@0.21.5': optional: true - '@esbuild/win32-x64@0.23.1': + '@esbuild/win32-x64@0.25.5': optional: true - '@eslint-community/eslint-utils@4.4.1(eslint@9.17.0(jiti@2.4.2))': + '@eslint-community/eslint-utils@4.7.0(eslint@9.30.1(jiti@2.4.2))': dependencies: - eslint: 9.17.0(jiti@2.4.2) + eslint: 9.30.1(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@2.4.2))': + '@eslint/compat@1.3.1(eslint@9.30.1(jiti@2.4.2))': optionalDependencies: - eslint: 9.17.0(jiti@2.4.2) + eslint: 9.30.1(jiti@2.4.2) - '@eslint/config-array@0.19.1': + '@eslint/config-array@0.21.0': dependencies: - '@eslint/object-schema': 2.1.5 - debug: 4.4.0 + '@eslint/object-schema': 2.1.6 + debug: 4.4.1 minimatch: 3.1.2 transitivePeerDependencies: - supports-color - '@eslint/core@0.9.1': + '@eslint/config-helpers@0.3.0': {} + + '@eslint/core@0.14.0': dependencies: '@types/json-schema': 7.0.15 - '@eslint/eslintrc@3.2.0': + '@eslint/core@0.15.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': dependencies: ajv: 6.12.6 - debug: 4.4.0 - espree: 10.3.0 + debug: 4.4.1 + espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 - import-fresh: 3.3.0 + import-fresh: 3.3.1 js-yaml: 4.1.0 minimatch: 3.1.2 strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color - '@eslint/js@9.17.0': {} + '@eslint/js@9.30.1': {} - '@eslint/object-schema@2.1.5': {} + '@eslint/object-schema@2.1.6': {} - '@eslint/plugin-kit@0.2.4': + '@eslint/plugin-kit@0.3.3': dependencies: + '@eslint/core': 0.15.1 levn: 0.4.1 '@fastify/busboy@3.1.1': {} @@ -2414,24 +2451,24 @@ snapshots: '@humanwhocodes/retry@0.3.1': {} - '@humanwhocodes/retry@0.4.1': {} + '@humanwhocodes/retry@0.4.3': {} - '@iconify-json/material-symbols@1.2.12': + '@iconify-json/material-symbols@1.2.29': dependencies: '@iconify/types': 2.0.0 '@iconify/types@2.0.0': {} - '@iconify/utils@2.2.1': + '@iconify/utils@2.3.0': dependencies: - '@antfu/install-pkg': 0.4.1 - '@antfu/utils': 0.7.10 + '@antfu/install-pkg': 1.1.0 + '@antfu/utils': 8.1.1 '@iconify/types': 2.0.0 - debug: 4.4.0 - globals: 15.14.0 + debug: 4.4.1 + globals: 15.15.0 kolorist: 1.8.0 - local-pkg: 0.5.1 - mlly: 1.7.3 + local-pkg: 1.1.1 + mlly: 1.7.4 transitivePeerDependencies: - supports-color @@ -2444,22 +2481,19 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 - '@jridgewell/gen-mapping@0.3.8': + '@jridgewell/gen-mapping@0.3.12': dependencies: - '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/sourcemap-codec': 1.5.4 + '@jridgewell/trace-mapping': 0.3.29 '@jridgewell/resolve-uri@3.1.2': {} - '@jridgewell/set-array@1.2.1': {} + '@jridgewell/sourcemap-codec@1.5.4': {} - '@jridgewell/sourcemap-codec@1.5.0': {} - - '@jridgewell/trace-mapping@0.3.25': + '@jridgewell/trace-mapping@0.3.29': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.5.4 '@nodelib/fs.scandir@2.1.5': dependencies: @@ -2471,159 +2505,167 @@ snapshots: '@nodelib/fs.walk@1.2.8': dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.18.0 + fastq: 1.19.1 '@phc/format@1.0.0': {} '@pkgjs/parseargs@0.11.0': optional: true - '@polka/url@1.0.0-next.28': {} + '@polka/url@1.0.0-next.29': {} - '@rollup/plugin-commonjs@28.0.2(rollup@4.30.1)': + '@rollup/plugin-commonjs@28.0.6(rollup@4.44.2)': dependencies: - '@rollup/pluginutils': 5.1.4(rollup@4.30.1) + '@rollup/pluginutils': 5.2.0(rollup@4.44.2) commondir: 1.0.1 estree-walker: 2.0.2 - fdir: 6.4.2(picomatch@4.0.2) + fdir: 6.4.6(picomatch@4.0.2) is-reference: 1.2.1 magic-string: 0.30.17 picomatch: 4.0.2 optionalDependencies: - rollup: 4.30.1 + rollup: 4.44.2 - '@rollup/plugin-json@6.1.0(rollup@4.30.1)': + '@rollup/plugin-json@6.1.0(rollup@4.44.2)': dependencies: - '@rollup/pluginutils': 5.1.4(rollup@4.30.1) + '@rollup/pluginutils': 5.2.0(rollup@4.44.2) optionalDependencies: - rollup: 4.30.1 + rollup: 4.44.2 - '@rollup/plugin-node-resolve@16.0.0(rollup@4.30.1)': + '@rollup/plugin-node-resolve@16.0.1(rollup@4.44.2)': dependencies: - '@rollup/pluginutils': 5.1.4(rollup@4.30.1) + '@rollup/pluginutils': 5.2.0(rollup@4.44.2) '@types/resolve': 1.20.2 deepmerge: 4.3.1 is-module: 1.0.0 resolve: 1.22.10 optionalDependencies: - rollup: 4.30.1 + rollup: 4.44.2 - '@rollup/pluginutils@5.1.4(rollup@4.30.1)': + '@rollup/pluginutils@5.2.0(rollup@4.44.2)': dependencies: - '@types/estree': 1.0.6 + '@types/estree': 1.0.8 estree-walker: 2.0.2 picomatch: 4.0.2 optionalDependencies: - rollup: 4.30.1 + rollup: 4.44.2 - '@rollup/rollup-android-arm-eabi@4.30.1': + '@rollup/rollup-android-arm-eabi@4.44.2': optional: true - '@rollup/rollup-android-arm64@4.30.1': + '@rollup/rollup-android-arm64@4.44.2': optional: true - '@rollup/rollup-darwin-arm64@4.30.1': + '@rollup/rollup-darwin-arm64@4.44.2': optional: true - '@rollup/rollup-darwin-x64@4.30.1': + '@rollup/rollup-darwin-x64@4.44.2': optional: true - '@rollup/rollup-freebsd-arm64@4.30.1': + '@rollup/rollup-freebsd-arm64@4.44.2': optional: true - '@rollup/rollup-freebsd-x64@4.30.1': + '@rollup/rollup-freebsd-x64@4.44.2': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.30.1': + '@rollup/rollup-linux-arm-gnueabihf@4.44.2': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.30.1': + '@rollup/rollup-linux-arm-musleabihf@4.44.2': optional: true - '@rollup/rollup-linux-arm64-gnu@4.30.1': + '@rollup/rollup-linux-arm64-gnu@4.44.2': optional: true - '@rollup/rollup-linux-arm64-musl@4.30.1': + '@rollup/rollup-linux-arm64-musl@4.44.2': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.30.1': + '@rollup/rollup-linux-loongarch64-gnu@4.44.2': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.30.1': + '@rollup/rollup-linux-powerpc64le-gnu@4.44.2': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.30.1': + '@rollup/rollup-linux-riscv64-gnu@4.44.2': optional: true - '@rollup/rollup-linux-s390x-gnu@4.30.1': + '@rollup/rollup-linux-riscv64-musl@4.44.2': optional: true - '@rollup/rollup-linux-x64-gnu@4.30.1': + '@rollup/rollup-linux-s390x-gnu@4.44.2': optional: true - '@rollup/rollup-linux-x64-musl@4.30.1': + '@rollup/rollup-linux-x64-gnu@4.44.2': optional: true - '@rollup/rollup-win32-arm64-msvc@4.30.1': + '@rollup/rollup-linux-x64-musl@4.44.2': optional: true - '@rollup/rollup-win32-ia32-msvc@4.30.1': + '@rollup/rollup-win32-arm64-msvc@4.44.2': optional: true - '@rollup/rollup-win32-x64-msvc@4.30.1': + '@rollup/rollup-win32-ia32-msvc@4.44.2': optional: true - '@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)))': + '@rollup/rollup-win32-x64-msvc@4.44.2': + optional: true + + '@sveltejs/acorn-typescript@1.0.5(acorn@8.15.0)': 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.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 + acorn: 8.15.0 - '@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/adapter-node@5.2.12(@sveltejs/kit@2.22.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10)))(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10)))': dependencies: - '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5)) + '@rollup/plugin-commonjs': 28.0.6(rollup@4.44.2) + '@rollup/plugin-json': 6.1.0(rollup@4.44.2) + '@rollup/plugin-node-resolve': 16.0.1(rollup@4.44.2) + '@sveltejs/kit': 2.22.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10)))(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10)) + rollup: 4.44.2 + + '@sveltejs/kit@2.22.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10)))(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10))': + dependencies: + '@sveltejs/acorn-typescript': 1.0.5(acorn@8.15.0) + '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10)) '@types/cookie': 0.6.0 + acorn: 8.15.0 cookie: 0.6.0 devalue: 5.1.1 esm-env: 1.2.2 - import-meta-resolve: 4.1.0 kleur: 4.1.5 magic-string: 0.30.17 - mrmime: 2.0.0 + mrmime: 2.0.1 sade: 1.8.1 set-cookie-parser: 2.7.1 - sirv: 3.0.0 - svelte: 5.19.1 - tiny-glob: 0.2.9 - vite: 5.4.11(@types/node@22.10.5) + sirv: 3.0.1 + svelte: 5.35.2 + vite: 5.4.19(@types/node@24.0.10) + vitefu: 1.1.1(vite@5.4.19(@types/node@24.0.10)) - '@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))': + '@sveltejs/vite-plugin-svelte-inspector@3.0.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10)))(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10))': dependencies: - '@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.19.1 - vite: 5.4.11(@types/node@22.10.5) + '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10)) + debug: 4.4.1 + svelte: 5.35.2 + vite: 5.4.19(@types/node@24.0.10) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.1)(vite@5.4.11(@types/node@22.10.5))': + '@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10))': dependencies: - '@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 + '@sveltejs/vite-plugin-svelte-inspector': 3.0.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10)))(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10)) + debug: 4.4.1 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.17 - 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)) + svelte: 5.35.2 + vite: 5.4.19(@types/node@24.0.10) + vitefu: 1.1.1(vite@5.4.19(@types/node@24.0.10)) transitivePeerDependencies: - supports-color '@types/cookie@0.6.0': {} - '@types/estree@1.0.6': {} + '@types/estree@1.0.8': {} '@types/file-saver@2.0.7': {} @@ -2633,109 +2675,120 @@ snapshots: '@types/node-schedule@2.1.7': dependencies: - '@types/node': 22.10.5 + '@types/node': 24.0.10 - '@types/node@22.10.5': + '@types/node@24.0.10': dependencies: - undici-types: 6.20.0 + undici-types: 7.8.0 - '@types/pg@8.11.10': + '@types/pg@8.15.4': dependencies: - '@types/node': 22.10.5 - pg-protocol: 1.7.0 - pg-types: 4.0.2 + '@types/node': 24.0.10 + pg-protocol: 1.10.3 + pg-types: 2.2.0 '@types/resolve@1.20.2': {} - '@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/eslint-plugin@8.35.1(@typescript-eslint/parser@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@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@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@2.4.2) + '@typescript-eslint/parser': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.35.1 + '@typescript-eslint/type-utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.35.1 + eslint: 9.30.1(jiti@2.4.2) graphemer: 1.4.0 - ignore: 5.3.2 + ignore: 7.0.5 natural-compare: 1.4.0 - ts-api-utils: 2.0.0(typescript@5.7.3) - typescript: 5.7.3 + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.19.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3)': + '@typescript-eslint/parser@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.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@2.4.2) - typescript: 5.7.3 + '@typescript-eslint/scope-manager': 8.35.1 + '@typescript-eslint/types': 8.35.1 + '@typescript-eslint/typescript-estree': 8.35.1(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.35.1 + debug: 4.4.1 + eslint: 9.30.1(jiti@2.4.2) + typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.19.1': + '@typescript-eslint/project-service@8.35.1(typescript@5.8.3)': dependencies: - '@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@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@2.4.2))(typescript@5.7.3) - debug: 4.4.0 - eslint: 9.17.0(jiti@2.4.2) - ts-api-utils: 2.0.0(typescript@5.7.3) - typescript: 5.7.3 + '@typescript-eslint/tsconfig-utils': 8.35.1(typescript@5.8.3) + '@typescript-eslint/types': 8.35.1 + debug: 4.4.1 + typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.19.1': {} - - '@typescript-eslint/typescript-estree@8.19.1(typescript@5.7.3)': + '@typescript-eslint/scope-manager@8.35.1': dependencies: - '@typescript-eslint/types': 8.19.1 - '@typescript-eslint/visitor-keys': 8.19.1 - debug: 4.4.0 + '@typescript-eslint/types': 8.35.1 + '@typescript-eslint/visitor-keys': 8.35.1 + + '@typescript-eslint/tsconfig-utils@8.35.1(typescript@5.8.3)': + dependencies: + typescript: 5.8.3 + + '@typescript-eslint/type-utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': + dependencies: + '@typescript-eslint/typescript-estree': 8.35.1(typescript@5.8.3) + '@typescript-eslint/utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + debug: 4.4.1 + eslint: 9.30.1(jiti@2.4.2) + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.35.1': {} + + '@typescript-eslint/typescript-estree@8.35.1(typescript@5.8.3)': + dependencies: + '@typescript-eslint/project-service': 8.35.1(typescript@5.8.3) + '@typescript-eslint/tsconfig-utils': 8.35.1(typescript@5.8.3) + '@typescript-eslint/types': 8.35.1 + '@typescript-eslint/visitor-keys': 8.35.1 + debug: 4.4.1 fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 - semver: 7.6.3 - ts-api-utils: 2.0.0(typescript@5.7.3) - typescript: 5.7.3 + semver: 7.7.2 + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.19.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3)': + '@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': dependencies: - '@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@2.4.2) - typescript: 5.7.3 + '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@2.4.2)) + '@typescript-eslint/scope-manager': 8.35.1 + '@typescript-eslint/types': 8.35.1 + '@typescript-eslint/typescript-estree': 8.35.1(typescript@5.8.3) + eslint: 9.30.1(jiti@2.4.2) + typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.19.1': + '@typescript-eslint/visitor-keys@8.35.1': dependencies: - '@typescript-eslint/types': 8.19.1 - eslint-visitor-keys: 4.2.0 + '@typescript-eslint/types': 8.35.1 + eslint-visitor-keys: 4.2.1 - '@xmldom/xmldom@0.9.6': + '@xmldom/xmldom@0.9.8': optional: true - acorn-jsx@5.3.2(acorn@8.14.0): + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: - acorn: 8.14.0 + acorn: 8.15.0 - acorn-typescript@1.4.13(acorn@8.14.0): - dependencies: - acorn: 8.14.0 - - acorn@8.14.0: {} + acorn@8.15.0: {} ajv@6.12.6: dependencies: @@ -2766,7 +2819,7 @@ snapshots: argon2@0.41.1: dependencies: '@phc/format': 1.0.0 - node-addon-api: 8.3.0 + node-addon-api: 8.4.0 node-gyp-build: 4.8.4 argparse@2.0.1: {} @@ -2775,20 +2828,20 @@ snapshots: asynckit@0.4.0: {} - autoprefixer@10.4.20(postcss@8.4.49): + autoprefixer@10.4.21(postcss@8.5.6): dependencies: - browserslist: 4.24.4 - caniuse-lite: 1.0.30001692 + browserslist: 4.25.1 + caniuse-lite: 1.0.30001726 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.1 - postcss: 8.4.49 + postcss: 8.5.6 postcss-value-parser: 4.2.0 - axios@1.7.9: + axios@1.10.0: dependencies: follow-redirects: 1.15.9 - form-data: 4.0.1 + form-data: 4.0.3 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug @@ -2799,12 +2852,12 @@ snapshots: binary-extensions@2.3.0: {} - brace-expansion@1.1.11: + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@2.0.1: + brace-expansion@2.0.2: dependencies: balanced-match: 1.0.2 @@ -2812,33 +2865,38 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.24.4: + browserslist@4.25.1: dependencies: - caniuse-lite: 1.0.30001692 - electron-to-chromium: 1.5.79 + caniuse-lite: 1.0.30001726 + electron-to-chromium: 1.5.179 node-releases: 2.0.19 - update-browserslist-db: 1.1.2(browserslist@4.24.4) + update-browserslist-db: 1.1.3(browserslist@4.25.1) - c12@2.0.1: + c12@2.0.4: dependencies: chokidar: 4.0.3 confbox: 0.1.8 defu: 6.1.4 - dotenv: 16.4.7 - giget: 1.2.3 + dotenv: 16.6.1 + giget: 1.2.5 jiti: 2.4.2 - mlly: 1.7.3 - ohash: 1.1.4 - pathe: 1.1.2 + mlly: 1.7.4 + ohash: 2.0.11 + pathe: 2.0.3 perfect-debounce: 1.0.0 - pkg-types: 1.3.0 + pkg-types: 1.3.1 rc9: 2.1.2 + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + callsites@3.1.0: {} camelcase-css@2.0.1: {} - caniuse-lite@1.0.30001692: {} + caniuse-lite@1.0.30001726: {} chalk@4.1.2: dependencies: @@ -2859,13 +2917,13 @@ snapshots: chokidar@4.0.3: dependencies: - readdirp: 4.0.2 + readdirp: 4.1.2 chownr@2.0.0: {} citty@0.1.6: dependencies: - consola: 3.4.0 + consola: 3.4.2 clsx@2.1.1: {} @@ -2887,13 +2945,15 @@ snapshots: confbox@0.1.8: {} - consola@3.4.0: {} + confbox@0.2.2: {} + + consola@3.4.2: {} cookie@0.6.0: {} cron-parser@4.9.0: dependencies: - luxon: 3.5.0 + luxon: 3.6.1 cross-spawn@7.0.6: dependencies: @@ -2903,7 +2963,7 @@ snapshots: cssesc@3.0.0: {} - debug@4.4.0: + debug@4.4.1: dependencies: ms: 2.1.3 @@ -2915,26 +2975,47 @@ snapshots: delayed-stream@1.0.0: {} - destr@2.0.3: {} + destr@2.0.5: {} devalue@5.1.1: {} - dexie@4.0.10: {} + dexie@4.0.11: {} didyoumean@1.2.2: {} dlv@1.1.3: {} - dotenv@16.4.7: {} + dotenv@16.6.1: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 eastasianwidth@0.2.0: {} - electron-to-chromium@1.5.79: {} + electron-to-chromium@1.5.179: {} emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + esbuild@0.21.5: optionalDependencies: '@esbuild/aix-ppc64': 0.21.5 @@ -2961,69 +3042,70 @@ snapshots: '@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-x64': 0.21.5 - esbuild@0.23.1: + esbuild@0.25.5: 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 + '@esbuild/aix-ppc64': 0.25.5 + '@esbuild/android-arm': 0.25.5 + '@esbuild/android-arm64': 0.25.5 + '@esbuild/android-x64': 0.25.5 + '@esbuild/darwin-arm64': 0.25.5 + '@esbuild/darwin-x64': 0.25.5 + '@esbuild/freebsd-arm64': 0.25.5 + '@esbuild/freebsd-x64': 0.25.5 + '@esbuild/linux-arm': 0.25.5 + '@esbuild/linux-arm64': 0.25.5 + '@esbuild/linux-ia32': 0.25.5 + '@esbuild/linux-loong64': 0.25.5 + '@esbuild/linux-mips64el': 0.25.5 + '@esbuild/linux-ppc64': 0.25.5 + '@esbuild/linux-riscv64': 0.25.5 + '@esbuild/linux-s390x': 0.25.5 + '@esbuild/linux-x64': 0.25.5 + '@esbuild/netbsd-arm64': 0.25.5 + '@esbuild/netbsd-x64': 0.25.5 + '@esbuild/openbsd-arm64': 0.25.5 + '@esbuild/openbsd-x64': 0.25.5 + '@esbuild/sunos-x64': 0.25.5 + '@esbuild/win32-arm64': 0.25.5 + '@esbuild/win32-ia32': 0.25.5 + '@esbuild/win32-x64': 0.25.5 escalade@3.2.0: {} escape-string-regexp@4.0.0: {} - eslint-compat-utils@0.5.1(eslint@9.17.0(jiti@2.4.2)): + eslint-compat-utils@0.5.1(eslint@9.30.1(jiti@2.4.2)): dependencies: - eslint: 9.17.0(jiti@2.4.2) - semver: 7.6.3 + eslint: 9.30.1(jiti@2.4.2) + semver: 7.7.2 - eslint-config-prettier@9.1.0(eslint@9.17.0(jiti@2.4.2)): + eslint-config-prettier@9.1.0(eslint@9.30.1(jiti@2.4.2)): dependencies: - eslint: 9.17.0(jiti@2.4.2) + eslint: 9.30.1(jiti@2.4.2) - eslint-plugin-svelte@2.46.1(eslint@9.17.0(jiti@2.4.2))(svelte@5.19.1): + eslint-plugin-svelte@2.46.1(eslint@9.30.1(jiti@2.4.2))(svelte@5.35.2): dependencies: - '@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@2.4.2) - eslint-compat-utils: 0.5.1(eslint@9.17.0(jiti@2.4.2)) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@2.4.2)) + '@jridgewell/sourcemap-codec': 1.5.4 + eslint: 9.30.1(jiti@2.4.2) + eslint-compat-utils: 0.5.1(eslint@9.30.1(jiti@2.4.2)) esutils: 2.0.3 known-css-properties: 0.35.0 - postcss: 8.4.49 - postcss-load-config: 3.1.4(postcss@8.4.49) - postcss-safe-parser: 6.0.0(postcss@8.4.49) + postcss: 8.5.6 + postcss-load-config: 3.1.4(postcss@8.5.6) + postcss-safe-parser: 6.0.0(postcss@8.5.6) postcss-selector-parser: 6.1.2 - semver: 7.6.3 - svelte-eslint-parser: 0.43.0(svelte@5.19.1) + semver: 7.7.2 + svelte-eslint-parser: 0.43.0(svelte@5.35.2) optionalDependencies: - svelte: 5.19.1 + svelte: 5.35.2 transitivePeerDependencies: - ts-node - eslint-plugin-tailwindcss@3.17.5(tailwindcss@3.4.17): + eslint-plugin-tailwindcss@3.18.0(tailwindcss@3.4.17): dependencies: fast-glob: 3.3.3 - postcss: 8.4.49 + postcss: 8.5.6 tailwindcss: 3.4.17 eslint-scope@7.2.2: @@ -3031,37 +3113,38 @@ snapshots: esrecurse: 4.3.0 estraverse: 5.3.0 - eslint-scope@8.2.0: + eslint-scope@8.4.0: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 eslint-visitor-keys@3.4.3: {} - eslint-visitor-keys@4.2.0: {} + eslint-visitor-keys@4.2.1: {} - eslint@9.17.0(jiti@2.4.2): + eslint@9.30.1(jiti@2.4.2): dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@2.4.2)) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@2.4.2)) '@eslint-community/regexpp': 4.12.1 - '@eslint/config-array': 0.19.1 - '@eslint/core': 0.9.1 - '@eslint/eslintrc': 3.2.0 - '@eslint/js': 9.17.0 - '@eslint/plugin-kit': 0.2.4 + '@eslint/config-array': 0.21.0 + '@eslint/config-helpers': 0.3.0 + '@eslint/core': 0.14.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.30.1 + '@eslint/plugin-kit': 0.3.3 '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.4.1 - '@types/estree': 1.0.6 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 '@types/json-schema': 7.0.15 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.0 + debug: 4.4.1 escape-string-regexp: 4.0.0 - eslint-scope: 8.2.0 - eslint-visitor-keys: 4.2.0 - espree: 10.3.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 esquery: 1.6.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 @@ -3083,25 +3166,25 @@ snapshots: esm-env@1.2.2: {} - espree@10.3.0: + espree@10.4.0: dependencies: - acorn: 8.14.0 - acorn-jsx: 5.3.2(acorn@8.14.0) - eslint-visitor-keys: 4.2.0 + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 espree@9.6.1: dependencies: - acorn: 8.14.0 - acorn-jsx: 5.3.2(acorn@8.14.0) + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) eslint-visitor-keys: 3.4.3 esquery@1.6.0: dependencies: estraverse: 5.3.0 - esrap@1.4.3: + esrap@2.1.0: dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.5.4 esrecurse@4.3.0: dependencies: @@ -3113,21 +3196,11 @@ 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: + exifreader@4.31.1: optionalDependencies: - '@xmldom/xmldom': 0.9.6 + '@xmldom/xmldom': 0.9.8 + + exsolve@1.0.7: {} fast-deep-equal@3.1.3: {} @@ -3143,11 +3216,11 @@ snapshots: fast-levenshtein@2.0.6: {} - fastq@1.18.0: + fastq@1.19.1: dependencies: - reusify: 1.0.4 + reusify: 1.1.0 - fdir@6.4.2(picomatch@4.0.2): + fdir@6.4.6(picomatch@4.0.2): optionalDependencies: picomatch: 4.0.2 @@ -3168,22 +3241,24 @@ snapshots: flat-cache@4.0.1: dependencies: - flatted: 3.3.2 + flatted: 3.3.3 keyv: 4.5.4 - flatted@3.3.2: {} + flatted@3.3.3: {} follow-redirects@1.15.9: {} - foreground-child@3.3.0: + foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 signal-exit: 4.1.0 - form-data@4.0.1: + form-data@4.0.3: dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 mime-types: 2.1.35 fraction.js@4.3.7: {} @@ -3197,21 +3272,36 @@ snapshots: function-bind@1.1.2: {} - get-stream@8.0.1: {} + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 - get-tsconfig@4.8.1: + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-tsconfig@4.10.1: dependencies: resolve-pkg-maps: 1.0.0 - giget@1.2.3: + giget@1.2.5: dependencies: citty: 0.1.6 - consola: 3.4.0 + consola: 3.4.2 defu: 6.1.4 - node-fetch-native: 1.6.4 - nypm: 0.3.12 - ohash: 1.1.4 - pathe: 1.1.2 + node-fetch-native: 1.6.6 + nypm: 0.5.4 + pathe: 2.0.3 tar: 6.2.1 glob-parent@5.1.2: @@ -3224,7 +3314,7 @@ snapshots: glob@10.4.5: dependencies: - foreground-child: 3.3.0 + foreground-child: 3.3.1 jackspeak: 3.4.3 minimatch: 9.0.5 minipass: 7.1.2 @@ -3233,33 +3323,35 @@ snapshots: globals@14.0.0: {} - globals@15.14.0: {} + globals@15.15.0: {} - globalyzer@0.1.0: {} - - globrex@0.1.2: {} + gopd@1.2.0: {} graphemer@1.4.0: {} has-flag@4.0.0: {} + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + hasown@2.0.2: dependencies: function-bind: 1.1.2 heic2any@0.0.4: {} - human-signals@5.0.0: {} - ignore@5.3.2: {} - import-fresh@3.3.0: + ignore@7.0.5: {} + + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 - import-meta-resolve@4.1.0: {} - imurmurhash@0.1.4: {} is-binary-path@2.1.0: @@ -3284,13 +3376,11 @@ snapshots: is-reference@1.2.1: dependencies: - '@types/estree': 1.0.6 + '@types/estree': 1.0.8 is-reference@3.0.3: dependencies: - '@types/estree': 1.0.6 - - is-stream@3.0.0: {} + '@types/estree': 1.0.8 isexe@2.0.0: {} @@ -3324,22 +3414,22 @@ snapshots: kolorist@1.8.0: {} - kysely-ctl@0.10.1(kysely@0.27.5): + kysely-ctl@0.10.1(kysely@0.28.2): dependencies: - c12: 2.0.1 + c12: 2.0.4 citty: 0.1.6 - consola: 3.4.0 - kysely: 0.27.5 + consola: 3.4.2 + kysely: 0.28.2 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 + pkg-types: 1.3.1 + std-env: 3.9.0 + tsx: 4.20.3 transitivePeerDependencies: - magicast - kysely@0.27.5: {} + kysely@0.28.2: {} levn@0.4.1: dependencies: @@ -3354,8 +3444,14 @@ snapshots: local-pkg@0.5.1: dependencies: - mlly: 1.7.3 - pkg-types: 1.3.0 + mlly: 1.7.4 + pkg-types: 1.3.1 + + local-pkg@1.1.1: + dependencies: + mlly: 1.7.4 + pkg-types: 2.2.0 + quansync: 0.2.10 locate-character@3.0.0: {} @@ -3369,13 +3465,13 @@ snapshots: lru-cache@10.4.3: {} - luxon@3.5.0: {} + luxon@3.6.1: {} magic-string@0.30.17: dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.5.4 - merge-stream@2.0.0: {} + math-intrinsics@1.1.0: {} merge2@1.4.1: {} @@ -3390,17 +3486,15 @@ snapshots: dependencies: mime-db: 1.52.0 - mime@4.0.6: {} - - mimic-fn@4.0.0: {} + mime@4.0.7: {} minimatch@3.1.2: dependencies: - brace-expansion: 1.1.11 + brace-expansion: 1.1.12 minimatch@9.0.5: dependencies: - brace-expansion: 2.0.1 + brace-expansion: 2.0.2 minipass@3.3.6: dependencies: @@ -3417,16 +3511,16 @@ snapshots: mkdirp@1.0.4: {} - mlly@1.7.3: + mlly@1.7.4: dependencies: - acorn: 8.14.0 - pathe: 1.1.2 - pkg-types: 1.3.0 - ufo: 1.5.4 + acorn: 8.15.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.1 mri@1.2.0: {} - mrmime@2.0.0: {} + mrmime@2.0.1: {} ms@2.1.3: {} @@ -3436,13 +3530,13 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 - nanoid@3.3.8: {} + nanoid@3.3.11: {} natural-compare@1.4.0: {} - node-addon-api@8.3.0: {} + node-addon-api@8.4.0: {} - node-fetch-native@1.6.4: {} + node-fetch-native@1.6.6: {} node-gyp-build@4.8.4: {} @@ -3458,45 +3552,35 @@ 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 + consola: 3.4.2 pathe: 1.1.2 - pkg-types: 1.3.0 + pkg-types: 1.3.1 tinyexec: 0.3.2 - ufo: 1.5.4 + ufo: 1.6.1 + + nypm@0.5.4: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + pathe: 2.0.3 + pkg-types: 1.3.1 + tinyexec: 0.3.2 + ufo: 1.6.1 object-assign@4.1.1: {} object-hash@3.0.0: {} - obuf@1.1.2: {} - ofetch@1.4.1: dependencies: - destr: 2.0.3 - node-fetch-native: 1.6.4 - ufo: 1.5.4 + destr: 2.0.5 + node-fetch-native: 1.6.6 + ufo: 1.6.1 - ohash@1.1.4: {} - - onetime@6.0.0: - dependencies: - mimic-fn: 4.0.0 + ohash@2.0.11: {} optionator@0.9.4: dependencies: @@ -3513,7 +3597,7 @@ snapshots: p-limit@6.2.0: dependencies: - yocto-queue: 1.1.1 + yocto-queue: 1.2.1 p-locate@5.0.0: dependencies: @@ -3521,7 +3605,11 @@ snapshots: package-json-from-dist@1.0.1: {} - package-manager-detector@0.2.8: {} + package-manager-detector@0.2.11: + dependencies: + quansync: 0.2.10 + + package-manager-detector@1.3.0: {} parent-module@1.0.1: dependencies: @@ -3531,8 +3619,6 @@ snapshots: path-key@3.1.1: {} - path-key@4.0.0: {} - path-parse@1.0.7: {} path-scurry@1.11.1: @@ -3542,22 +3628,22 @@ snapshots: pathe@1.1.2: {} + pathe@2.0.3: {} + perfect-debounce@1.0.0: {} - pg-cloudflare@1.1.1: + pg-cloudflare@1.2.7: optional: true - pg-connection-string@2.7.0: {} + pg-connection-string@2.9.1: {} pg-int8@1.0.1: {} - pg-numeric@1.0.2: {} - - pg-pool@3.7.0(pg@8.13.1): + pg-pool@3.10.1(pg@8.16.3): dependencies: - pg: 8.13.1 + pg: 8.16.3 - pg-protocol@1.7.0: {} + pg-protocol@1.10.3: {} pg-types@2.2.0: dependencies: @@ -3567,25 +3653,15 @@ snapshots: postgres-date: 1.0.7 postgres-interval: 1.2.0 - pg-types@4.0.2: + pg@8.16.3: 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-connection-string: 2.9.1 + pg-pool: 3.10.1(pg@8.16.3) + pg-protocol: 1.10.3 pg-types: 2.2.0 pgpass: 1.0.5 optionalDependencies: - pg-cloudflare: 1.1.1 + pg-cloudflare: 1.2.7 pgpass@1.0.5: dependencies: @@ -3599,52 +3675,58 @@ snapshots: pify@2.3.0: {} - pirates@4.0.6: {} + pirates@4.0.7: {} - pkg-types@1.3.0: + pkg-types@1.3.1: dependencies: confbox: 0.1.8 - mlly: 1.7.3 - pathe: 1.1.2 + mlly: 1.7.4 + pathe: 2.0.3 - postcss-import@15.1.0(postcss@8.4.49): + pkg-types@2.2.0: dependencies: - postcss: 8.4.49 + confbox: 0.2.2 + exsolve: 1.0.7 + pathe: 2.0.3 + + postcss-import@15.1.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.10 - postcss-js@4.0.1(postcss@8.4.49): + postcss-js@4.0.1(postcss@8.5.6): dependencies: camelcase-css: 2.0.1 - postcss: 8.4.49 + postcss: 8.5.6 - postcss-load-config@3.1.4(postcss@8.4.49): + postcss-load-config@3.1.4(postcss@8.5.6): dependencies: lilconfig: 2.1.0 yaml: 1.10.2 optionalDependencies: - postcss: 8.4.49 + postcss: 8.5.6 - postcss-load-config@4.0.2(postcss@8.4.49): + postcss-load-config@4.0.2(postcss@8.5.6): dependencies: lilconfig: 3.1.3 - yaml: 2.7.0 + yaml: 2.8.0 optionalDependencies: - postcss: 8.4.49 + postcss: 8.5.6 - postcss-nested@6.2.0(postcss@8.4.49): + postcss-nested@6.2.0(postcss@8.5.6): dependencies: - postcss: 8.4.49 + postcss: 8.5.6 postcss-selector-parser: 6.1.2 - postcss-safe-parser@6.0.0(postcss@8.4.49): + postcss-safe-parser@6.0.0(postcss@8.5.6): dependencies: - postcss: 8.4.49 + postcss: 8.5.6 - postcss-scss@4.0.9(postcss@8.4.49): + postcss-scss@4.0.9(postcss@8.5.6): dependencies: - postcss: 8.4.49 + postcss: 8.5.6 postcss-selector-parser@6.1.2: dependencies: @@ -3653,59 +3735,49 @@ snapshots: postcss-value-parser@4.2.0: {} - postcss@8.4.49: + postcss@8.5.6: dependencies: - nanoid: 3.3.8 + nanoid: 3.3.11 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: {} - prelude-ls@1.2.1: {} - prettier-plugin-svelte@3.3.2(prettier@3.4.2)(svelte@5.19.1): + prettier-plugin-svelte@3.4.0(prettier@3.6.2)(svelte@5.35.2): dependencies: - prettier: 3.4.2 - svelte: 5.19.1 + prettier: 3.6.2 + svelte: 5.35.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): + prettier-plugin-tailwindcss@0.6.13(prettier-plugin-svelte@3.4.0(prettier@3.6.2)(svelte@5.35.2))(prettier@3.6.2): dependencies: - prettier: 3.4.2 + prettier: 3.6.2 optionalDependencies: - prettier-plugin-svelte: 3.3.2(prettier@3.4.2)(svelte@5.19.1) + prettier-plugin-svelte: 3.4.0(prettier@3.6.2)(svelte@5.35.2) - prettier@3.4.2: {} + prettier@3.6.2: {} proxy-from-env@1.1.0: {} punycode@2.3.1: {} + quansync@0.2.10: {} + queue-microtask@1.2.3: {} rc9@2.1.2: dependencies: defu: 6.1.4 - destr: 2.0.3 + destr: 2.0.5 read-cache@1.0.0: dependencies: @@ -3715,7 +3787,7 @@ snapshots: dependencies: picomatch: 2.3.1 - readdirp@4.0.2: {} + readdirp@4.1.2: {} resolve-from@4.0.0: {} @@ -3727,31 +3799,32 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - reusify@1.0.4: {} + reusify@1.1.0: {} - rollup@4.30.1: + rollup@4.44.2: dependencies: - '@types/estree': 1.0.6 + '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.30.1 - '@rollup/rollup-android-arm64': 4.30.1 - '@rollup/rollup-darwin-arm64': 4.30.1 - '@rollup/rollup-darwin-x64': 4.30.1 - '@rollup/rollup-freebsd-arm64': 4.30.1 - '@rollup/rollup-freebsd-x64': 4.30.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.30.1 - '@rollup/rollup-linux-arm-musleabihf': 4.30.1 - '@rollup/rollup-linux-arm64-gnu': 4.30.1 - '@rollup/rollup-linux-arm64-musl': 4.30.1 - '@rollup/rollup-linux-loongarch64-gnu': 4.30.1 - '@rollup/rollup-linux-powerpc64le-gnu': 4.30.1 - '@rollup/rollup-linux-riscv64-gnu': 4.30.1 - '@rollup/rollup-linux-s390x-gnu': 4.30.1 - '@rollup/rollup-linux-x64-gnu': 4.30.1 - '@rollup/rollup-linux-x64-musl': 4.30.1 - '@rollup/rollup-win32-arm64-msvc': 4.30.1 - '@rollup/rollup-win32-ia32-msvc': 4.30.1 - '@rollup/rollup-win32-x64-msvc': 4.30.1 + '@rollup/rollup-android-arm-eabi': 4.44.2 + '@rollup/rollup-android-arm64': 4.44.2 + '@rollup/rollup-darwin-arm64': 4.44.2 + '@rollup/rollup-darwin-x64': 4.44.2 + '@rollup/rollup-freebsd-arm64': 4.44.2 + '@rollup/rollup-freebsd-x64': 4.44.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.44.2 + '@rollup/rollup-linux-arm-musleabihf': 4.44.2 + '@rollup/rollup-linux-arm64-gnu': 4.44.2 + '@rollup/rollup-linux-arm64-musl': 4.44.2 + '@rollup/rollup-linux-loongarch64-gnu': 4.44.2 + '@rollup/rollup-linux-powerpc64le-gnu': 4.44.2 + '@rollup/rollup-linux-riscv64-gnu': 4.44.2 + '@rollup/rollup-linux-riscv64-musl': 4.44.2 + '@rollup/rollup-linux-s390x-gnu': 4.44.2 + '@rollup/rollup-linux-x64-gnu': 4.44.2 + '@rollup/rollup-linux-x64-musl': 4.44.2 + '@rollup/rollup-win32-arm64-msvc': 4.44.2 + '@rollup/rollup-win32-ia32-msvc': 4.44.2 + '@rollup/rollup-win32-x64-msvc': 4.44.2 fsevents: 2.3.3 run-parallel@1.2.0: @@ -3762,7 +3835,7 @@ snapshots: dependencies: mri: 1.2.0 - semver@7.6.3: {} + semver@7.7.2: {} set-cookie-parser@2.7.1: {} @@ -3774,10 +3847,10 @@ snapshots: signal-exit@4.1.0: {} - sirv@3.0.0: + sirv@3.0.1: dependencies: - '@polka/url': 1.0.0-next.28 - mrmime: 2.0.0 + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 totalist: 3.0.1 sorted-array-functions@1.3.0: {} @@ -3786,7 +3859,7 @@ snapshots: split2@4.2.0: {} - std-env@3.8.0: {} + std-env@3.9.0: {} string-width@4.2.3: dependencies: @@ -3808,18 +3881,16 @@ snapshots: dependencies: ansi-regex: 6.1.0 - strip-final-newline@3.0.0: {} - strip-json-comments@3.1.1: {} sucrase@3.35.0: dependencies: - '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/gen-mapping': 0.3.12 commander: 4.1.1 glob: 10.4.5 lines-and-columns: 1.2.4 mz: 2.7.0 - pirates: 4.0.6 + pirates: 4.0.7 ts-interface-checker: 0.1.13 supports-color@7.2.0: @@ -3828,40 +3899,40 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-check@4.1.3(picomatch@4.0.2)(svelte@5.19.1)(typescript@5.7.3): + svelte-check@4.2.2(picomatch@4.0.2)(svelte@5.35.2)(typescript@5.8.3): dependencies: - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/trace-mapping': 0.3.29 chokidar: 4.0.3 - fdir: 6.4.2(picomatch@4.0.2) + fdir: 6.4.6(picomatch@4.0.2) picocolors: 1.1.1 sade: 1.8.1 - svelte: 5.19.1 - typescript: 5.7.3 + svelte: 5.35.2 + typescript: 5.8.3 transitivePeerDependencies: - picomatch - svelte-eslint-parser@0.43.0(svelte@5.19.1): + svelte-eslint-parser@0.43.0(svelte@5.35.2): dependencies: eslint-scope: 7.2.2 eslint-visitor-keys: 3.4.3 espree: 9.6.1 - postcss: 8.4.49 - postcss-scss: 4.0.9(postcss@8.4.49) + postcss: 8.5.6 + postcss-scss: 4.0.9(postcss@8.5.6) optionalDependencies: - svelte: 5.19.1 + svelte: 5.35.2 - svelte@5.19.1: + svelte@5.35.2: dependencies: '@ampproject/remapping': 2.3.0 - '@jridgewell/sourcemap-codec': 1.5.0 - '@types/estree': 1.0.6 - acorn: 8.14.0 - acorn-typescript: 1.4.13(acorn@8.14.0) + '@jridgewell/sourcemap-codec': 1.5.4 + '@sveltejs/acorn-typescript': 1.0.5(acorn@8.15.0) + '@types/estree': 1.0.8 + acorn: 8.15.0 aria-query: 5.3.2 axobject-query: 4.1.0 clsx: 2.1.1 esm-env: 1.2.2 - esrap: 1.4.3 + esrap: 2.1.0 is-reference: 3.0.3 locate-character: 3.0.0 magic-string: 0.30.17 @@ -3883,11 +3954,11 @@ snapshots: normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.1.1 - postcss: 8.4.49 - postcss-import: 15.1.0(postcss@8.4.49) - postcss-js: 4.0.1(postcss@8.4.49) - postcss-load-config: 4.0.2(postcss@8.4.49) - postcss-nested: 6.2.0(postcss@8.4.49) + postcss: 8.5.6 + postcss-import: 15.1.0(postcss@8.5.6) + postcss-js: 4.0.1(postcss@8.5.6) + postcss-load-config: 4.0.2(postcss@8.5.6) + postcss-nested: 6.2.0(postcss@8.5.6) postcss-selector-parser: 6.1.2 resolve: 1.22.10 sucrase: 3.35.0 @@ -3911,29 +3982,26 @@ snapshots: dependencies: any-promise: 1.3.0 - tiny-glob@0.2.9: - dependencies: - globalyzer: 0.1.0 - globrex: 0.1.2 - tinyexec@0.3.2: {} + tinyexec@1.0.1: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 totalist@3.0.1: {} - ts-api-utils@2.0.0(typescript@5.7.3): + ts-api-utils@2.1.0(typescript@5.8.3): dependencies: - typescript: 5.7.3 + typescript: 5.8.3 ts-interface-checker@0.1.13: {} - tsx@4.19.2: + tsx@4.20.3: dependencies: - esbuild: 0.23.1 - get-tsconfig: 4.8.1 + esbuild: 0.25.5 + get-tsconfig: 4.10.1 optionalDependencies: fsevents: 2.3.3 @@ -3941,44 +4009,45 @@ snapshots: dependencies: prelude-ls: 1.2.1 - typescript-eslint@8.19.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.3): + typescript-eslint@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3): dependencies: - '@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 + '@typescript-eslint/eslint-plugin': 8.35.1(@typescript-eslint/parser@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/parser': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + eslint: 9.30.1(jiti@2.4.2) + typescript: 5.8.3 transitivePeerDependencies: - supports-color - typescript@5.7.3: {} + typescript@5.8.3: {} - ufo@1.5.4: {} + ufo@1.6.1: {} - undici-types@6.20.0: {} + undici-types@7.8.0: {} - unplugin-icons@0.22.0(svelte@5.19.1): + unplugin-icons@0.22.0(svelte@5.35.2): dependencies: '@antfu/install-pkg': 0.5.0 '@antfu/utils': 0.7.10 - '@iconify/utils': 2.2.1 - debug: 4.4.0 + '@iconify/utils': 2.3.0 + debug: 4.4.1 kolorist: 1.8.0 local-pkg: 0.5.1 - unplugin: 2.1.2 + unplugin: 2.3.5 optionalDependencies: - svelte: 5.19.1 + svelte: 5.35.2 transitivePeerDependencies: - supports-color - unplugin@2.1.2: + unplugin@2.3.5: dependencies: - acorn: 8.14.0 + acorn: 8.15.0 + picomatch: 4.0.2 webpack-virtual-modules: 0.6.2 - update-browserslist-db@1.1.2(browserslist@4.24.4): + update-browserslist-db@1.1.3(browserslist@4.25.1): dependencies: - browserslist: 4.24.4 + browserslist: 4.25.1 escalade: 3.2.0 picocolors: 1.1.1 @@ -3988,20 +4057,20 @@ snapshots: util-deprecate@1.0.2: {} - uuid@11.0.4: {} + uuid@11.1.0: {} - vite@5.4.11(@types/node@22.10.5): + vite@5.4.19(@types/node@24.0.10): dependencies: esbuild: 0.21.5 - postcss: 8.4.49 - rollup: 4.30.1 + postcss: 8.5.6 + rollup: 4.44.2 optionalDependencies: - '@types/node': 22.10.5 + '@types/node': 24.0.10 fsevents: 2.3.3 - vitefu@1.0.5(vite@5.4.11(@types/node@22.10.5)): + vitefu@1.1.1(vite@5.4.19(@types/node@24.0.10)): optionalDependencies: - vite: 5.4.11(@types/node@22.10.5) + vite: 5.4.19(@types/node@24.0.10) webpack-virtual-modules@0.6.2: {} @@ -4029,12 +4098,12 @@ snapshots: yaml@1.10.2: {} - yaml@2.7.0: {} + yaml@2.8.0: {} yocto-queue@0.1.0: {} - yocto-queue@1.1.1: {} + yocto-queue@1.2.1: {} zimmerframe@1.1.2: {} - zod@3.24.1: {} + zod@3.25.73: {} From c9331ae5b765b0b354f24b7719a9abcbcd419412 Mon Sep 17 00:00:00 2001 From: static Date: Fri, 4 Jul 2025 23:26:58 +0900 Subject: [PATCH 06/38] =?UTF-8?q?=ED=81=B4=EB=9D=BC=EC=9D=B4=EC=96=B8?= =?UTF-8?q?=ED=8A=B8=EA=B0=80=20Decryption=20Oracle=EB=A1=9C=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EB=90=A0=20=EC=88=98=20=EC=9E=88=EB=8A=94=20=EC=B7=A8?= =?UTF-8?q?=EC=95=BD=EC=A0=90=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/server/db/client.ts | 18 ++++++++++----- src/lib/server/db/session.ts | 14 +++++++----- src/lib/server/schemas/auth.ts | 3 ++- src/lib/server/schemas/client.ts | 3 ++- src/lib/server/services/auth.ts | 12 +++++----- src/lib/server/services/client.ts | 22 +++++++++++++------ src/lib/services/auth.ts | 4 ++-- src/lib/services/key.ts | 4 ++-- src/routes/api/auth/upgradeSession/+server.ts | 4 ++-- .../api/auth/upgradeSession/verify/+server.ts | 4 ++-- src/routes/api/client/register/+server.ts | 4 ++-- .../api/client/register/verify/+server.ts | 4 ++-- 12 files changed, 58 insertions(+), 38 deletions(-) diff --git a/src/lib/server/db/client.ts b/src/lib/server/db/client.ts index cb873c7..373357a 100644 --- a/src/lib/server/db/client.ts +++ b/src/lib/server/db/client.ts @@ -178,7 +178,7 @@ export const registerUserClientChallenge = async ( allowedIp: string, expiresAt: Date, ) => { - await db + const { id } = await db .insertInto("user_client_challenge") .values({ user_id: userId, @@ -187,19 +187,25 @@ export const registerUserClientChallenge = async ( allowed_ip: allowedIp, expires_at: expiresAt, }) - .execute(); + .returning("id") + .executeTakeFirstOrThrow(); + return { id }; }; -export const consumeUserClientChallenge = async (userId: number, answer: string, ip: string) => { +export const consumeUserClientChallenge = async ( + challengeId: number, + userId: number, + ip: string, +) => { const challenge = await db .deleteFrom("user_client_challenge") + .where("id", "=", challengeId) .where("user_id", "=", userId) - .where("answer", "=", answer) .where("allowed_ip", "=", ip) .where("expires_at", ">", new Date()) - .returning("client_id") + .returning(["client_id", "answer"]) .executeTakeFirst(); - return challenge ? { clientId: challenge.client_id } : null; + return challenge ? { clientId: challenge.client_id, answer: challenge.answer } : null; }; export const cleanupExpiredUserClientChallenges = async () => { diff --git a/src/lib/server/db/session.ts b/src/lib/server/db/session.ts index 727f795..653c20c 100644 --- a/src/lib/server/db/session.ts +++ b/src/lib/server/db/session.ts @@ -94,7 +94,7 @@ export const registerSessionUpgradeChallenge = async ( expiresAt: Date, ) => { try { - await db + const { id } = await db .insertInto("session_upgrade_challenge") .values({ session_id: sessionId, @@ -103,7 +103,9 @@ export const registerSessionUpgradeChallenge = async ( allowed_ip: allowedIp, expires_at: expiresAt, }) - .execute(); + .returning("id") + .executeTakeFirstOrThrow(); + return { id }; } catch (e) { if (e instanceof pg.DatabaseError && e.code === "23505") { throw new IntegrityError("Challenge already registered"); @@ -113,19 +115,19 @@ export const registerSessionUpgradeChallenge = async ( }; export const consumeSessionUpgradeChallenge = async ( + challengeId: number, sessionId: string, - answer: string, ip: string, ) => { const challenge = await db .deleteFrom("session_upgrade_challenge") + .where("id", "=", challengeId) .where("session_id", "=", sessionId) - .where("answer", "=", answer) .where("allowed_ip", "=", ip) .where("expires_at", ">", new Date()) - .returning("client_id") + .returning(["client_id", "answer"]) .executeTakeFirst(); - return challenge ? { clientId: challenge.client_id } : null; + return challenge ? { clientId: challenge.client_id, answer: challenge.answer } : null; }; export const cleanupExpiredSessionUpgradeChallenges = async () => { diff --git a/src/lib/server/schemas/auth.ts b/src/lib/server/schemas/auth.ts index e3d6264..e90b209 100644 --- a/src/lib/server/schemas/auth.ts +++ b/src/lib/server/schemas/auth.ts @@ -19,12 +19,13 @@ export const sessionUpgradeRequest = z.object({ export type SessionUpgradeRequest = z.infer; export const sessionUpgradeResponse = z.object({ + id: z.number().int().positive(), challenge: z.string().base64().nonempty(), }); export type SessionUpgradeResponse = z.infer; export const sessionUpgradeVerifyRequest = z.object({ - answer: z.string().base64().nonempty(), + id: z.number().int().positive(), answerSig: z.string().base64().nonempty(), }); export type SessionUpgradeVerifyRequest = z.infer; diff --git a/src/lib/server/schemas/client.ts b/src/lib/server/schemas/client.ts index 53cbb88..df15e39 100644 --- a/src/lib/server/schemas/client.ts +++ b/src/lib/server/schemas/client.ts @@ -17,12 +17,13 @@ export const clientRegisterRequest = z.object({ export type ClientRegisterRequest = z.infer; export const clientRegisterResponse = z.object({ + id: z.number().int().positive(), challenge: z.string().base64().nonempty(), }); export type ClientRegisterResponse = z.infer; export const clientRegisterVerifyRequest = z.object({ - answer: z.string().base64().nonempty(), + id: z.number().int().positive(), answerSig: z.string().base64().nonempty(), }); export type ClientRegisterVerifyRequest = z.infer; diff --git a/src/lib/server/services/auth.ts b/src/lib/server/services/auth.ts index 81f0333..2eb496c 100644 --- a/src/lib/server/services/auth.ts +++ b/src/lib/server/services/auth.ts @@ -81,7 +81,7 @@ export const createSessionUpgradeChallenge = async ( } const { answer, challenge } = await generateChallenge(32, encPubKey); - await registerSessionUpgradeChallenge( + const { id } = await registerSessionUpgradeChallenge( sessionId, client.id, answer.toString("base64"), @@ -89,16 +89,16 @@ export const createSessionUpgradeChallenge = async ( new Date(Date.now() + env.challenge.sessionUpgradeExp), ); - return { challenge: challenge.toString("base64") }; + return { id, challenge: challenge.toString("base64") }; }; export const verifySessionUpgradeChallenge = async ( sessionId: string, ip: string, - answer: string, + challengeId: number, answerSig: string, ) => { - const challenge = await consumeSessionUpgradeChallenge(sessionId, answer, ip); + const challenge = await consumeSessionUpgradeChallenge(challengeId, sessionId, ip); if (!challenge) { error(403, "Invalid challenge answer"); } @@ -106,7 +106,9 @@ export const verifySessionUpgradeChallenge = async ( const client = await getClient(challenge.clientId); if (!client) { error(500, "Invalid challenge answer"); - } else if (!verifySignature(Buffer.from(answer, "base64"), answerSig, client.sigPubKey)) { + } else if ( + !verifySignature(Buffer.from(challenge.answer, "base64"), answerSig, client.sigPubKey) + ) { error(403, "Invalid challenge answer signature"); } diff --git a/src/lib/server/services/client.ts b/src/lib/server/services/client.ts index ee1b5b3..811e58c 100644 --- a/src/lib/server/services/client.ts +++ b/src/lib/server/services/client.ts @@ -34,8 +34,14 @@ const createUserClientChallenge = async ( encPubKey: string, ) => { const { answer, challenge } = await generateChallenge(32, encPubKey); - await registerUserClientChallenge(userId, clientId, answer.toString("base64"), ip, expiresAt()); - return challenge.toString("base64"); + const { id } = await registerUserClientChallenge( + userId, + clientId, + answer.toString("base64"), + ip, + expiresAt(), + ); + return { id, challenge: challenge.toString("base64") }; }; export const registerUserClient = async ( @@ -48,7 +54,7 @@ export const registerUserClient = async ( if (client) { try { await createUserClient(userId, client.id); - return { challenge: await createUserClientChallenge(ip, userId, client.id, encPubKey) }; + return await createUserClientChallenge(ip, userId, client.id, encPubKey); } catch (e) { if (e instanceof IntegrityError && e.message === "User client already exists") { error(409, "Client already registered"); @@ -64,7 +70,7 @@ export const registerUserClient = async ( try { const { id: clientId } = await createClient(encPubKey, sigPubKey, userId); - return { challenge: await createUserClientChallenge(ip, userId, clientId, encPubKey) }; + return await createUserClientChallenge(ip, userId, clientId, encPubKey); } catch (e) { if (e instanceof IntegrityError && e.message === "Public key(s) already registered") { error(409, "Public key(s) already used"); @@ -77,10 +83,10 @@ export const registerUserClient = async ( export const verifyUserClient = async ( userId: number, ip: string, - answer: string, + challengeId: number, answerSig: string, ) => { - const challenge = await consumeUserClientChallenge(userId, answer, ip); + const challenge = await consumeUserClientChallenge(challengeId, userId, ip); if (!challenge) { error(403, "Invalid challenge answer"); } @@ -88,7 +94,9 @@ export const verifyUserClient = async ( const client = await getClient(challenge.clientId); if (!client) { error(500, "Invalid challenge answer"); - } else if (!verifySignature(Buffer.from(answer, "base64"), answerSig, client.sigPubKey)) { + } else if ( + !verifySignature(Buffer.from(challenge.answer, "base64"), answerSig, client.sigPubKey) + ) { error(403, "Invalid challenge answer signature"); } diff --git a/src/lib/services/auth.ts b/src/lib/services/auth.ts index 498c794..df49e30 100644 --- a/src/lib/services/auth.ts +++ b/src/lib/services/auth.ts @@ -18,12 +18,12 @@ export const requestSessionUpgrade = async ( }); if (!res.ok) return false; - const { challenge }: SessionUpgradeResponse = await res.json(); + const { id, challenge }: SessionUpgradeResponse = await res.json(); const answer = await decryptChallenge(challenge, decryptKey); const answerSig = await signMessageRSA(answer, signKey); res = await callPostApi("/api/auth/upgradeSession/verify", { - answer: encodeToBase64(answer), + id, answerSig: encodeToBase64(answerSig), }); return res.ok; diff --git a/src/lib/services/key.ts b/src/lib/services/key.ts index fb368dd..a7e1c08 100644 --- a/src/lib/services/key.ts +++ b/src/lib/services/key.ts @@ -27,12 +27,12 @@ export const requestClientRegistration = async ( }); if (!res.ok) return false; - const { challenge }: ClientRegisterResponse = await res.json(); + const { id, challenge }: ClientRegisterResponse = await res.json(); const answer = await decryptChallenge(challenge, decryptKey); const answerSig = await signMessageRSA(answer, signKey); res = await callPostApi("/api/client/register/verify", { - answer: encodeToBase64(answer), + id, answerSig: encodeToBase64(answerSig), }); return res.ok; diff --git a/src/routes/api/auth/upgradeSession/+server.ts b/src/routes/api/auth/upgradeSession/+server.ts index 760f4c0..fa0b6cf 100644 --- a/src/routes/api/auth/upgradeSession/+server.ts +++ b/src/routes/api/auth/upgradeSession/+server.ts @@ -15,12 +15,12 @@ export const POST: RequestHandler = async ({ locals, request }) => { if (!zodRes.success) error(400, "Invalid request body"); const { encPubKey, sigPubKey } = zodRes.data; - const { challenge } = await createSessionUpgradeChallenge( + const { id, challenge } = await createSessionUpgradeChallenge( sessionId, userId, locals.ip, encPubKey, sigPubKey, ); - return json(sessionUpgradeResponse.parse({ challenge } satisfies SessionUpgradeResponse)); + return json(sessionUpgradeResponse.parse({ id, challenge } satisfies SessionUpgradeResponse)); }; diff --git a/src/routes/api/auth/upgradeSession/verify/+server.ts b/src/routes/api/auth/upgradeSession/verify/+server.ts index 82cb315..2fe4e36 100644 --- a/src/routes/api/auth/upgradeSession/verify/+server.ts +++ b/src/routes/api/auth/upgradeSession/verify/+server.ts @@ -9,8 +9,8 @@ export const POST: RequestHandler = async ({ locals, request }) => { const zodRes = sessionUpgradeVerifyRequest.safeParse(await request.json()); if (!zodRes.success) error(400, "Invalid request body"); - const { answer, answerSig } = zodRes.data; + const { id, answerSig } = zodRes.data; - await verifySessionUpgradeChallenge(sessionId, locals.ip, answer, answerSig); + await verifySessionUpgradeChallenge(sessionId, locals.ip, id, answerSig); return text("Session upgraded", { headers: { "Content-Type": "text/plain" } }); }; diff --git a/src/routes/api/client/register/+server.ts b/src/routes/api/client/register/+server.ts index d6aa4ce..5ac2a53 100644 --- a/src/routes/api/client/register/+server.ts +++ b/src/routes/api/client/register/+server.ts @@ -15,6 +15,6 @@ export const POST: RequestHandler = async ({ locals, request }) => { if (!zodRes.success) error(400, "Invalid request body"); const { encPubKey, sigPubKey } = zodRes.data; - const { challenge } = await registerUserClient(userId, locals.ip, encPubKey, sigPubKey); - return json(clientRegisterResponse.parse({ challenge } satisfies ClientRegisterResponse)); + const { id, challenge } = await registerUserClient(userId, locals.ip, encPubKey, sigPubKey); + return json(clientRegisterResponse.parse({ id, challenge } satisfies ClientRegisterResponse)); }; diff --git a/src/routes/api/client/register/verify/+server.ts b/src/routes/api/client/register/verify/+server.ts index 32d5214..5ac9396 100644 --- a/src/routes/api/client/register/verify/+server.ts +++ b/src/routes/api/client/register/verify/+server.ts @@ -9,8 +9,8 @@ export const POST: RequestHandler = async ({ locals, request }) => { const zodRes = clientRegisterVerifyRequest.safeParse(await request.json()); if (!zodRes.success) error(400, "Invalid request body"); - const { answer, answerSig } = zodRes.data; + const { id, answerSig } = zodRes.data; - await verifyUserClient(userId, locals.ip, answer, answerSig); + await verifyUserClient(userId, locals.ip, id, answerSig); return text("Client verified", { headers: { "Content-Type": "text/plain" } }); }; From 7b88679ff09e4c35354975d53b3eef734b110781 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 5 Jul 2025 04:13:39 +0900 Subject: [PATCH 07/38] =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=20=EB=B2=84?= =?UTF-8?q?=EC=A0=84=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 16 +- pnpm-lock.yaml | 605 ++++++++----------------------------------------- 2 files changed, 105 insertions(+), 516 deletions(-) diff --git a/package.json b/package.json index 2d50287..cae9662 100644 --- a/package.json +++ b/package.json @@ -22,21 +22,21 @@ "@sveltejs/kit": "^2.22.2", "@sveltejs/vite-plugin-svelte": "^4.0.4", "@types/file-saver": "^2.0.7", - "@types/ms": "^0.7.34", + "@types/ms": "^2.1.0", "@types/node-schedule": "^2.1.7", "@types/pg": "^8.15.4", "autoprefixer": "^10.4.21", "axios": "^1.10.0", "dexie": "^4.0.11", "eslint": "^9.30.1", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-svelte": "^2.46.1", + "eslint-config-prettier": "^10.1.5", + "eslint-plugin-svelte": "^3.10.1", "eslint-plugin-tailwindcss": "^3.18.0", "exifreader": "^4.31.1", "file-saver": "^2.0.5", - "globals": "^15.15.0", + "globals": "^16.3.0", "heic2any": "^0.0.4", - "kysely-ctl": "^0.10.1", + "kysely-ctl": "^0.13.1", "mime": "^4.0.7", "p-limit": "^6.2.0", "prettier": "^3.6.2", @@ -47,18 +47,18 @@ "tailwindcss": "^3.4.17", "typescript": "^5.8.3", "typescript-eslint": "^8.35.1", - "unplugin-icons": "^0.22.0", + "unplugin-icons": "^22.1.0", "vite": "^5.4.19" }, "dependencies": { "@fastify/busboy": "^3.1.1", - "argon2": "^0.41.1", + "argon2": "^0.43.0", "kysely": "^0.28.2", "ms": "^2.1.3", "node-schedule": "^2.1.1", "pg": "^8.16.3", "uuid": "^11.1.0", - "zod": "^3.25.73" + "zod": "^3.25.74" }, "engines": { "node": "^22.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4ed0cb5..e7e04aa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^3.1.1 version: 3.1.1 argon2: - specifier: ^0.41.1 - version: 0.41.1 + specifier: ^0.43.0 + version: 0.43.0 kysely: specifier: ^0.28.2 version: 0.28.2 @@ -30,8 +30,8 @@ importers: specifier: ^11.1.0 version: 11.1.0 zod: - specifier: ^3.25.73 - version: 3.25.73 + specifier: ^3.25.74 + version: 3.25.74 devDependencies: '@eslint/compat': specifier: ^1.3.1 @@ -52,8 +52,8 @@ importers: specifier: ^2.0.7 version: 2.0.7 '@types/ms': - specifier: ^0.7.34 - version: 0.7.34 + specifier: ^2.1.0 + version: 2.1.0 '@types/node-schedule': specifier: ^2.1.7 version: 2.1.7 @@ -73,11 +73,11 @@ importers: specifier: ^9.30.1 version: 9.30.1(jiti@2.4.2) eslint-config-prettier: - specifier: ^9.1.0 - version: 9.1.0(eslint@9.30.1(jiti@2.4.2)) + specifier: ^10.1.5 + version: 10.1.5(eslint@9.30.1(jiti@2.4.2)) eslint-plugin-svelte: - specifier: ^2.46.1 - version: 2.46.1(eslint@9.30.1(jiti@2.4.2))(svelte@5.35.2) + specifier: ^3.10.1 + version: 3.10.1(eslint@9.30.1(jiti@2.4.2))(svelte@5.35.2) eslint-plugin-tailwindcss: specifier: ^3.18.0 version: 3.18.0(tailwindcss@3.4.17) @@ -88,14 +88,14 @@ importers: specifier: ^2.0.5 version: 2.0.5 globals: - specifier: ^15.15.0 - version: 15.15.0 + specifier: ^16.3.0 + version: 16.3.0 heic2any: specifier: ^0.0.4 version: 0.0.4 kysely-ctl: - specifier: ^0.10.1 - version: 0.10.1(kysely@0.28.2) + specifier: ^0.13.1 + version: 0.13.1(kysely@0.28.2) mime: specifier: ^4.0.7 version: 4.0.7 @@ -127,8 +127,8 @@ importers: specifier: ^8.35.1 version: 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) unplugin-icons: - specifier: ^0.22.0 - version: 0.22.0(svelte@5.35.2) + specifier: ^22.1.0 + version: 22.1.0(svelte@5.35.2) vite: specifier: ^5.4.19 version: 5.4.19(@types/node@24.0.10) @@ -143,15 +143,9 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - '@antfu/install-pkg@0.5.0': - resolution: {integrity: sha512-dKnk2xlAyC7rvTkpkHmu+Qy/2Zc3Vm/l8PtNyIOGDBtXPY3kThfU4ORNEp3V7SXw5XSOb+tOJaUYpfquPzL/Tg==} - '@antfu/install-pkg@1.1.0': resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} - '@antfu/utils@0.7.10': - resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==} - '@antfu/utils@8.1.1': resolution: {integrity: sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==} @@ -161,288 +155,138 @@ packages: cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.25.5': - resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - '@esbuild/android-arm64@0.21.5': resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} engines: {node: '>=12'} cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.25.5': - resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - '@esbuild/android-arm@0.21.5': resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} engines: {node: '>=12'} cpu: [arm] os: [android] - '@esbuild/android-arm@0.25.5': - resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - '@esbuild/android-x64@0.21.5': resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} engines: {node: '>=12'} cpu: [x64] os: [android] - '@esbuild/android-x64@0.25.5': - resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - '@esbuild/darwin-arm64@0.21.5': resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.25.5': - resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - '@esbuild/darwin-x64@0.21.5': resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} engines: {node: '>=12'} cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.25.5': - resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - '@esbuild/freebsd-arm64@0.21.5': resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.25.5': - resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - '@esbuild/freebsd-x64@0.21.5': resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.5': - resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - '@esbuild/linux-arm64@0.21.5': resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} engines: {node: '>=12'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.25.5': - resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - '@esbuild/linux-arm@0.21.5': resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} engines: {node: '>=12'} cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.25.5': - resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - '@esbuild/linux-ia32@0.21.5': resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} engines: {node: '>=12'} cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.25.5': - resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - '@esbuild/linux-loong64@0.21.5': resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} engines: {node: '>=12'} cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.25.5': - resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - '@esbuild/linux-mips64el@0.21.5': resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.25.5': - resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - '@esbuild/linux-ppc64@0.21.5': resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.25.5': - resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - '@esbuild/linux-riscv64@0.21.5': resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.25.5': - resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - '@esbuild/linux-s390x@0.21.5': resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} engines: {node: '>=12'} cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.25.5': - resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - '@esbuild/linux-x64@0.21.5': resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} engines: {node: '>=12'} cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.25.5': - resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.25.5': - resolution: {integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - '@esbuild/netbsd-x64@0.21.5': resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.5': - resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.25.5': - resolution: {integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - '@esbuild/openbsd-x64@0.21.5': resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.5': - resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - '@esbuild/sunos-x64@0.21.5': resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} engines: {node: '>=12'} cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.25.5': - resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - '@esbuild/win32-arm64@0.21.5': resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} engines: {node: '>=12'} cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.25.5': - resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - '@esbuild/win32-ia32@0.21.5': resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} engines: {node: '>=12'} cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.25.5': - resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - '@esbuild/win32-x64@0.21.5': resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} engines: {node: '>=12'} cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.25.5': - resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - '@eslint-community/eslint-utils@4.7.0': resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -748,8 +592,8 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/ms@0.7.34': - resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} '@types/node-schedule@2.1.7': resolution: {integrity: sha512-G7Z3R9H7r3TowoH6D2pkzUHPhcJrDF4Jz1JOQ80AX0K2DWTHoN9VC94XzFAPNMdbW9TBzMZ3LjpFi7RYdbxtXA==} @@ -865,8 +709,8 @@ packages: arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} - argon2@0.41.1: - resolution: {integrity: sha512-dqCW8kJXke8Ik+McUcMDltrbuAWETPyU6iq+4AhxqKphWi7pChB/Zgd/Tp/o8xRLbg8ksMj46F/vph9wnxpTzQ==} + argon2@0.43.0: + resolution: {integrity: sha512-u/HKLcbWShVDhkfwI4hWyiUf3qyX8QhTfaIv2cWE18uqhXCmR5hb6Ed7oqYi2KCQegeAnRhiFzbjzm7i5yl1GA==} engines: {node: '>=16.17.0'} argparse@2.0.1: @@ -915,8 +759,8 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - c12@2.0.4: - resolution: {integrity: sha512-3DbbhnFt0fKJHxU4tEUPmD1ahWE4PWPMomqfYsTJdrhpmEnRKJi3qSC4rO5U6E6zN1+pjBY7+z8fUmNRMaVKLw==} + c12@3.0.4: + resolution: {integrity: sha512-t5FaZTYbbCtvxuZq9xxIruYydrAGsJ+8UdP0pZzMiK2xl/gNiSOy0OxhLzHUEEb0m1QXYqfzfvyIFEmz/g9lqg==} peerDependencies: magicast: ^0.3.5 peerDependenciesMeta: @@ -950,10 +794,6 @@ packages: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} - chownr@2.0.0: - resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} - engines: {node: '>=10'} - citty@0.1.6: resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} @@ -1088,11 +928,6 @@ packages: engines: {node: '>=12'} hasBin: true - esbuild@0.25.5: - resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==} - engines: {node: '>=18'} - hasBin: true - escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -1101,23 +936,17 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - eslint-compat-utils@0.5.1: - resolution: {integrity: sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==} - engines: {node: '>=12'} - peerDependencies: - eslint: '>=6.0.0' - - eslint-config-prettier@9.1.0: - resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} + eslint-config-prettier@10.1.5: + resolution: {integrity: sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==} hasBin: true peerDependencies: eslint: '>=7.0.0' - eslint-plugin-svelte@2.46.1: - resolution: {integrity: sha512-7xYr2o4NID/f9OEYMqxsEQsCsj4KaMy4q5sANaKkAb6/QeCjYFxRmDm2S3YC3A3pl1kyPZ/syOx/i7LcWYSbIw==} - engines: {node: ^14.17.0 || >=16.0.0} + eslint-plugin-svelte@3.10.1: + resolution: {integrity: sha512-csCh2x0ge/DugXC7dCANh46Igi7bjMZEy6rHZCdS13AoGVJSu7a90Kru3I8oMYLGEemPRE1hQXadxvRPVMAAXQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^7.0.0 || ^8.0.0-0 || ^9.0.0-0 + eslint: ^8.57.1 || ^9.0.0 svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 peerDependenciesMeta: svelte: @@ -1129,10 +958,6 @@ packages: peerDependencies: tailwindcss: ^3.4.0 - eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint-scope@8.4.0: resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1162,10 +987,6 @@ packages: resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - esquery@1.6.0: resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} engines: {node: '>=0.10'} @@ -1260,10 +1081,6 @@ packages: fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} - 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==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1280,11 +1097,8 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} - get-tsconfig@4.10.1: - resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} - - giget@1.2.5: - resolution: {integrity: sha512-r1ekGw/Bgpi3HLV3h1MRBIlSAdHoIMklpaQ3OQLFcRw9PwAj2rqigvIbg+dBUI51OxVI2jsEtDywDBjSiuf7Ug==} + giget@2.0.0: + resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} hasBin: true glob-parent@5.1.2: @@ -1307,6 +1121,10 @@ packages: resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} engines: {node: '>=18'} + globals@16.3.0: + resolution: {integrity: sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==} + engines: {node: '>=18'} + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -1416,18 +1234,18 @@ packages: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} - known-css-properties@0.35.0: - resolution: {integrity: sha512-a/RAk2BfKk+WFGhhOCAYqSiFLc34k8Mt/6NWRI4joER0EYUzXIcFivjjnoD3+XU1DggLn/tZc3DOAgke7l8a4A==} + known-css-properties@0.37.0: + resolution: {integrity: sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==} kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} - kysely-ctl@0.10.1: - resolution: {integrity: sha512-tB2mGV/MbfaQC6Lo582Rs2OdtfX23ueWDscCSDT42Iy8pYHYbhMy9ncXU35ee8LQz4BO2apQihyY8rDProP+9w==} + kysely-ctl@0.13.1: + resolution: {integrity: sha512-DhTgpt1ru3Y74rI8O3IRLJ5HV09vMSz4q2KEeqaUX0GYMB09zO1KsdwSsF+ZMa8bHeS4F4J3Vjfbc4ulJQ1r0A==} engines: {node: '>=18'} hasBin: true peerDependencies: - kysely: '>=0.18.1 <0.28.0' + kysely: '>=0.18.1 <0.29.0' kysely-postgres-js: ^2 peerDependenciesMeta: kysely-postgres-js: @@ -1452,10 +1270,6 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - local-pkg@0.5.1: - resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==} - engines: {node: '>=14'} - local-pkg@1.1.1: resolution: {integrity: sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==} engines: {node: '>=14'} @@ -1515,27 +1329,10 @@ packages: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} - 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'} - 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.4: resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==} @@ -1587,13 +1384,8 @@ packages: resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} engines: {node: '>=0.10.0'} - nypm@0.4.1: - resolution: {integrity: sha512-1b9mihliBh8UCcKtcGRu//G50iHpjxIQVUqkdhPT/SDVE7KdJKoHXLS0heuYTQCx95dFqiyUbXZB9r8ikn+93g==} - engines: {node: ^14.16.0 || >=16.10.0} - hasBin: true - - nypm@0.5.4: - resolution: {integrity: sha512-X0SNNrZiGU8/e/zAB7sCTtdxWTMSIO73q+xuKgglm2Yvzwlo8UoC5FNySQFCvl84uPaeADkqHUZUkWy4aH4xOA==} + nypm@0.6.0: + resolution: {integrity: sha512-mn8wBFV9G9+UFHIrq+pZ2r2zL4aPau/by3kJb3cM7+5tQHMt6HGQB8FDIeKFYp8o0D2pnH6nVsO88N4AmUxIWg==} engines: {node: ^14.16.0 || >=16.10.0} hasBin: true @@ -1630,9 +1422,6 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - package-manager-detector@0.2.11: - resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} - package-manager-detector@1.3.0: resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==} @@ -1655,9 +1444,6 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} - pathe@1.1.2: - resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -1765,11 +1551,11 @@ packages: peerDependencies: postcss: ^8.2.14 - postcss-safe-parser@6.0.0: - resolution: {integrity: sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==} - engines: {node: '>=12.0'} + postcss-safe-parser@7.0.1: + resolution: {integrity: sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==} + engines: {node: '>=18.0'} peerDependencies: - postcss: ^8.3.3 + postcss: ^8.4.31 postcss-scss@4.0.9: resolution: {integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==} @@ -1781,6 +1567,10 @@ packages: resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} engines: {node: '>=4'} + postcss-selector-parser@7.1.0: + resolution: {integrity: sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==} + engines: {node: '>=4'} + postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} @@ -1905,9 +1695,6 @@ packages: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} - resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - resolve@1.22.10: resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} engines: {node: '>= 0.4'} @@ -2008,9 +1795,9 @@ packages: svelte: ^4.0.0 || ^5.0.0-next.0 typescript: '>=5.0.0' - svelte-eslint-parser@0.43.0: - resolution: {integrity: sha512-GpU52uPKKcVnh8tKN5P4UZpJ/fUDndmq7wfsvoVXsyP+aY0anol7Yqo01fyrlaWGMFfm4av5DyrjlaXdLRJvGA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + svelte-eslint-parser@1.2.0: + resolution: {integrity: sha512-mbPtajIeuiyU80BEyGvwAktBeTX7KCr5/0l+uRGLq1dafwRNrjfM5kHGJScEBlPG3ipu6dJqfW/k0/fujvIEVw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 peerDependenciesMeta: @@ -2026,10 +1813,6 @@ packages: engines: {node: '>=14.0.0'} hasBin: true - 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==} engines: {node: '>=0.8'} @@ -2060,11 +1843,6 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - tsx@4.20.3: - resolution: {integrity: sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==} - engines: {node: '>=18.0.0'} - hasBin: true - type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -2087,8 +1865,8 @@ packages: undici-types@7.8.0: resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} - unplugin-icons@0.22.0: - resolution: {integrity: sha512-CP+iZq5U7doOifer5bcM0jQ9t3Is7EGybIYt3myVxceI8Zuk8EZEpe1NPtJvh7iqMs1VdbK0L41t9+um9VuuLw==} + unplugin-icons@22.1.0: + resolution: {integrity: sha512-ect2ZNtk1Zgwb0NVHd0C1IDW/MV+Jk/xaq4t8o6rYdVS3+L660ZdD5kTSQZvsgdwCvquRw+/wYn75hsweRjoIA==} peerDependencies: '@svgr/core': '>=7.0.0' '@svgx/core': ^1.0.1 @@ -2193,9 +1971,6 @@ packages: 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'} @@ -2216,8 +1991,8 @@ packages: zimmerframe@1.1.2: resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==} - zod@3.25.73: - resolution: {integrity: sha512-fuIKbQAWQl22Ba5d1quwEETQYjqnpKVyZIWAhbnnHgnDd3a+z4YgEfkI5SZ2xMELnLAXo/Flk2uXgysZNf0uaA==} + zod@3.25.74: + resolution: {integrity: sha512-J8poo92VuhKjNknViHRAIuuN6li/EwFbAC8OedzI8uxpEPGiXHGQu9wemIAioIpqgfB4SySaJhdk0mH5Y4ICBg==} snapshots: @@ -2228,164 +2003,82 @@ snapshots: '@jridgewell/gen-mapping': 0.3.12 '@jridgewell/trace-mapping': 0.3.29 - '@antfu/install-pkg@0.5.0': - dependencies: - package-manager-detector: 0.2.11 - tinyexec: 0.3.2 - '@antfu/install-pkg@1.1.0': dependencies: package-manager-detector: 1.3.0 tinyexec: 1.0.1 - '@antfu/utils@0.7.10': {} - '@antfu/utils@8.1.1': {} '@esbuild/aix-ppc64@0.21.5': optional: true - '@esbuild/aix-ppc64@0.25.5': - optional: true - '@esbuild/android-arm64@0.21.5': optional: true - '@esbuild/android-arm64@0.25.5': - optional: true - '@esbuild/android-arm@0.21.5': optional: true - '@esbuild/android-arm@0.25.5': - optional: true - '@esbuild/android-x64@0.21.5': optional: true - '@esbuild/android-x64@0.25.5': - optional: true - '@esbuild/darwin-arm64@0.21.5': optional: true - '@esbuild/darwin-arm64@0.25.5': - optional: true - '@esbuild/darwin-x64@0.21.5': optional: true - '@esbuild/darwin-x64@0.25.5': - optional: true - '@esbuild/freebsd-arm64@0.21.5': optional: true - '@esbuild/freebsd-arm64@0.25.5': - optional: true - '@esbuild/freebsd-x64@0.21.5': optional: true - '@esbuild/freebsd-x64@0.25.5': - optional: true - '@esbuild/linux-arm64@0.21.5': optional: true - '@esbuild/linux-arm64@0.25.5': - optional: true - '@esbuild/linux-arm@0.21.5': optional: true - '@esbuild/linux-arm@0.25.5': - optional: true - '@esbuild/linux-ia32@0.21.5': optional: true - '@esbuild/linux-ia32@0.25.5': - optional: true - '@esbuild/linux-loong64@0.21.5': optional: true - '@esbuild/linux-loong64@0.25.5': - optional: true - '@esbuild/linux-mips64el@0.21.5': optional: true - '@esbuild/linux-mips64el@0.25.5': - optional: true - '@esbuild/linux-ppc64@0.21.5': optional: true - '@esbuild/linux-ppc64@0.25.5': - optional: true - '@esbuild/linux-riscv64@0.21.5': optional: true - '@esbuild/linux-riscv64@0.25.5': - optional: true - '@esbuild/linux-s390x@0.21.5': optional: true - '@esbuild/linux-s390x@0.25.5': - optional: true - '@esbuild/linux-x64@0.21.5': optional: true - '@esbuild/linux-x64@0.25.5': - optional: true - - '@esbuild/netbsd-arm64@0.25.5': - optional: true - '@esbuild/netbsd-x64@0.21.5': optional: true - '@esbuild/netbsd-x64@0.25.5': - optional: true - - '@esbuild/openbsd-arm64@0.25.5': - optional: true - '@esbuild/openbsd-x64@0.21.5': optional: true - '@esbuild/openbsd-x64@0.25.5': - optional: true - '@esbuild/sunos-x64@0.21.5': optional: true - '@esbuild/sunos-x64@0.25.5': - optional: true - '@esbuild/win32-arm64@0.21.5': optional: true - '@esbuild/win32-arm64@0.25.5': - optional: true - '@esbuild/win32-ia32@0.21.5': optional: true - '@esbuild/win32-ia32@0.25.5': - optional: true - '@esbuild/win32-x64@0.21.5': optional: true - '@esbuild/win32-x64@0.25.5': - optional: true - '@eslint-community/eslint-utils@4.7.0(eslint@9.30.1(jiti@2.4.2))': dependencies: eslint: 9.30.1(jiti@2.4.2) @@ -2671,7 +2364,7 @@ snapshots: '@types/json-schema@7.0.15': {} - '@types/ms@0.7.34': {} + '@types/ms@2.1.0': {} '@types/node-schedule@2.1.7': dependencies: @@ -2816,7 +2509,7 @@ snapshots: arg@5.0.2: {} - argon2@0.41.1: + argon2@0.43.0: dependencies: '@phc/format': 1.0.0 node-addon-api: 8.4.0 @@ -2872,19 +2565,19 @@ snapshots: node-releases: 2.0.19 update-browserslist-db: 1.1.3(browserslist@4.25.1) - c12@2.0.4: + c12@3.0.4: dependencies: chokidar: 4.0.3 - confbox: 0.1.8 + confbox: 0.2.2 defu: 6.1.4 dotenv: 16.6.1 - giget: 1.2.5 + exsolve: 1.0.7 + giget: 2.0.0 jiti: 2.4.2 - mlly: 1.7.4 ohash: 2.0.11 pathe: 2.0.3 perfect-debounce: 1.0.0 - pkg-types: 1.3.1 + pkg-types: 2.2.0 rc9: 2.1.2 call-bind-apply-helpers@1.0.2: @@ -2919,8 +2612,6 @@ snapshots: dependencies: readdirp: 4.1.2 - chownr@2.0.0: {} - citty@0.1.6: dependencies: consola: 3.4.2 @@ -3042,61 +2733,27 @@ snapshots: '@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-x64': 0.21.5 - esbuild@0.25.5: - optionalDependencies: - '@esbuild/aix-ppc64': 0.25.5 - '@esbuild/android-arm': 0.25.5 - '@esbuild/android-arm64': 0.25.5 - '@esbuild/android-x64': 0.25.5 - '@esbuild/darwin-arm64': 0.25.5 - '@esbuild/darwin-x64': 0.25.5 - '@esbuild/freebsd-arm64': 0.25.5 - '@esbuild/freebsd-x64': 0.25.5 - '@esbuild/linux-arm': 0.25.5 - '@esbuild/linux-arm64': 0.25.5 - '@esbuild/linux-ia32': 0.25.5 - '@esbuild/linux-loong64': 0.25.5 - '@esbuild/linux-mips64el': 0.25.5 - '@esbuild/linux-ppc64': 0.25.5 - '@esbuild/linux-riscv64': 0.25.5 - '@esbuild/linux-s390x': 0.25.5 - '@esbuild/linux-x64': 0.25.5 - '@esbuild/netbsd-arm64': 0.25.5 - '@esbuild/netbsd-x64': 0.25.5 - '@esbuild/openbsd-arm64': 0.25.5 - '@esbuild/openbsd-x64': 0.25.5 - '@esbuild/sunos-x64': 0.25.5 - '@esbuild/win32-arm64': 0.25.5 - '@esbuild/win32-ia32': 0.25.5 - '@esbuild/win32-x64': 0.25.5 - escalade@3.2.0: {} escape-string-regexp@4.0.0: {} - eslint-compat-utils@0.5.1(eslint@9.30.1(jiti@2.4.2)): - dependencies: - eslint: 9.30.1(jiti@2.4.2) - semver: 7.7.2 - - eslint-config-prettier@9.1.0(eslint@9.30.1(jiti@2.4.2)): + eslint-config-prettier@10.1.5(eslint@9.30.1(jiti@2.4.2)): dependencies: eslint: 9.30.1(jiti@2.4.2) - eslint-plugin-svelte@2.46.1(eslint@9.30.1(jiti@2.4.2))(svelte@5.35.2): + eslint-plugin-svelte@3.10.1(eslint@9.30.1(jiti@2.4.2))(svelte@5.35.2): dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@2.4.2)) '@jridgewell/sourcemap-codec': 1.5.4 eslint: 9.30.1(jiti@2.4.2) - eslint-compat-utils: 0.5.1(eslint@9.30.1(jiti@2.4.2)) esutils: 2.0.3 - known-css-properties: 0.35.0 + globals: 16.3.0 + known-css-properties: 0.37.0 postcss: 8.5.6 postcss-load-config: 3.1.4(postcss@8.5.6) - postcss-safe-parser: 6.0.0(postcss@8.5.6) - postcss-selector-parser: 6.1.2 + postcss-safe-parser: 7.0.1(postcss@8.5.6) semver: 7.7.2 - svelte-eslint-parser: 0.43.0(svelte@5.35.2) + svelte-eslint-parser: 1.2.0(svelte@5.35.2) optionalDependencies: svelte: 5.35.2 transitivePeerDependencies: @@ -3108,11 +2765,6 @@ snapshots: postcss: 8.5.6 tailwindcss: 3.4.17 - eslint-scope@7.2.2: - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - eslint-scope@8.4.0: dependencies: esrecurse: 4.3.0 @@ -3172,12 +2824,6 @@ snapshots: acorn-jsx: 5.3.2(acorn@8.15.0) eslint-visitor-keys: 4.2.1 - espree@9.6.1: - dependencies: - acorn: 8.15.0 - acorn-jsx: 5.3.2(acorn@8.15.0) - eslint-visitor-keys: 3.4.3 - esquery@1.6.0: dependencies: estraverse: 5.3.0 @@ -3263,10 +2909,6 @@ snapshots: fraction.js@4.3.7: {} - fs-minipass@2.1.0: - dependencies: - minipass: 3.3.6 - fsevents@2.3.3: optional: true @@ -3290,19 +2932,14 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 - get-tsconfig@4.10.1: - dependencies: - resolve-pkg-maps: 1.0.0 - - giget@1.2.5: + giget@2.0.0: dependencies: citty: 0.1.6 consola: 3.4.2 defu: 6.1.4 node-fetch-native: 1.6.6 - nypm: 0.5.4 + nypm: 0.6.0 pathe: 2.0.3 - tar: 6.2.1 glob-parent@5.1.2: dependencies: @@ -3325,6 +2962,8 @@ snapshots: globals@15.15.0: {} + globals@16.3.0: {} + gopd@1.2.0: {} graphemer@1.4.0: {} @@ -3410,22 +3049,22 @@ snapshots: kleur@4.1.5: {} - known-css-properties@0.35.0: {} + known-css-properties@0.37.0: {} kolorist@1.8.0: {} - kysely-ctl@0.10.1(kysely@0.28.2): + kysely-ctl@0.13.1(kysely@0.28.2): dependencies: - c12: 2.0.4 + c12: 3.0.4 citty: 0.1.6 consola: 3.4.2 + jiti: 2.4.2 kysely: 0.28.2 - nypm: 0.4.1 + nypm: 0.6.0 ofetch: 1.4.1 - pathe: 1.1.2 - pkg-types: 1.3.1 + pathe: 2.0.3 + pkg-types: 2.2.0 std-env: 3.9.0 - tsx: 4.20.3 transitivePeerDependencies: - magicast @@ -3442,11 +3081,6 @@ snapshots: lines-and-columns@1.2.4: {} - local-pkg@0.5.1: - dependencies: - mlly: 1.7.4 - pkg-types: 1.3.1 - local-pkg@1.1.1: dependencies: mlly: 1.7.4 @@ -3496,21 +3130,8 @@ snapshots: dependencies: brace-expansion: 2.0.2 - minipass@3.3.6: - dependencies: - yallist: 4.0.0 - - minipass@5.0.0: {} - minipass@7.1.2: {} - minizlib@2.1.2: - dependencies: - minipass: 3.3.6 - yallist: 4.0.0 - - mkdirp@1.0.4: {} - mlly@1.7.4: dependencies: acorn: 8.15.0 @@ -3552,23 +3173,13 @@ snapshots: normalize-range@0.1.2: {} - nypm@0.4.1: - dependencies: - citty: 0.1.6 - consola: 3.4.2 - pathe: 1.1.2 - pkg-types: 1.3.1 - tinyexec: 0.3.2 - ufo: 1.6.1 - - nypm@0.5.4: + nypm@0.6.0: dependencies: citty: 0.1.6 consola: 3.4.2 pathe: 2.0.3 - pkg-types: 1.3.1 + pkg-types: 2.2.0 tinyexec: 0.3.2 - ufo: 1.6.1 object-assign@4.1.1: {} @@ -3605,10 +3216,6 @@ snapshots: package-json-from-dist@1.0.1: {} - package-manager-detector@0.2.11: - dependencies: - quansync: 0.2.10 - package-manager-detector@1.3.0: {} parent-module@1.0.1: @@ -3626,8 +3233,6 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 - pathe@1.1.2: {} - pathe@2.0.3: {} perfect-debounce@1.0.0: {} @@ -3720,7 +3325,7 @@ snapshots: postcss: 8.5.6 postcss-selector-parser: 6.1.2 - postcss-safe-parser@6.0.0(postcss@8.5.6): + postcss-safe-parser@7.0.1(postcss@8.5.6): dependencies: postcss: 8.5.6 @@ -3733,6 +3338,11 @@ snapshots: cssesc: 3.0.0 util-deprecate: 1.0.2 + postcss-selector-parser@7.1.0: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + postcss-value-parser@4.2.0: {} postcss@8.5.6: @@ -3791,8 +3401,6 @@ snapshots: resolve-from@4.0.0: {} - resolve-pkg-maps@1.0.0: {} - resolve@1.22.10: dependencies: is-core-module: 2.16.1 @@ -3911,13 +3519,14 @@ snapshots: transitivePeerDependencies: - picomatch - svelte-eslint-parser@0.43.0(svelte@5.35.2): + svelte-eslint-parser@1.2.0(svelte@5.35.2): dependencies: - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 postcss: 8.5.6 postcss-scss: 4.0.9(postcss@8.5.6) + postcss-selector-parser: 7.1.0 optionalDependencies: svelte: 5.35.2 @@ -3965,15 +3574,6 @@ snapshots: transitivePeerDependencies: - ts-node - tar@6.2.1: - dependencies: - 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: thenify: 3.3.1 @@ -3998,13 +3598,6 @@ snapshots: ts-interface-checker@0.1.13: {} - tsx@4.20.3: - dependencies: - esbuild: 0.25.5 - get-tsconfig: 4.10.1 - optionalDependencies: - fsevents: 2.3.3 - type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -4025,14 +3618,12 @@ snapshots: undici-types@7.8.0: {} - unplugin-icons@0.22.0(svelte@5.35.2): + unplugin-icons@22.1.0(svelte@5.35.2): dependencies: - '@antfu/install-pkg': 0.5.0 - '@antfu/utils': 0.7.10 + '@antfu/install-pkg': 1.1.0 '@iconify/utils': 2.3.0 debug: 4.4.1 - kolorist: 1.8.0 - local-pkg: 0.5.1 + local-pkg: 1.1.1 unplugin: 2.3.5 optionalDependencies: svelte: 5.35.2 @@ -4094,8 +3685,6 @@ snapshots: xtend@4.0.2: {} - yallist@4.0.0: {} - yaml@1.10.2: {} yaml@2.8.0: {} @@ -4106,4 +3695,4 @@ snapshots: zimmerframe@1.1.2: {} - zod@3.25.73: {} + zod@3.25.74: {} From 36d082e0f8b67152e40d98c762ba6b0adffb152e Mon Sep 17 00:00:00 2001 From: static Date: Sat, 5 Jul 2025 05:44:00 +0900 Subject: [PATCH 08/38] =?UTF-8?q?/api/file/[id]/thumbnail,=20/api/file/[id?= =?UTF-8?q?]/thumbnail/download,=20/api/file/[id]/thumbnail/upload=20Endpo?= =?UTF-8?q?int=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 1 + docker-compose.yaml | 2 + src/lib/server/db/file.ts | 3 +- src/lib/server/db/media.ts | 86 +++++++++++++++++++ src/lib/server/db/schema/media.ts | 4 +- src/lib/server/loadenv.ts | 1 + src/lib/server/schemas/file.ts | 11 +++ src/lib/server/services/file.ts | 54 ++++++++++++ src/routes/api/file/[id]/thumbnail/+server.ts | 23 +++++ .../file/[id]/thumbnail/download/+server.ts | 25 ++++++ .../api/file/[id]/thumbnail/upload/+server.ts | 72 ++++++++++++++++ 11 files changed, 279 insertions(+), 3 deletions(-) create mode 100644 src/lib/server/db/media.ts create mode 100644 src/routes/api/file/[id]/thumbnail/+server.ts create mode 100644 src/routes/api/file/[id]/thumbnail/download/+server.ts create mode 100644 src/routes/api/file/[id]/thumbnail/upload/+server.ts diff --git a/.env.example b/.env.example index f492443..e3b6365 100644 --- a/.env.example +++ b/.env.example @@ -11,3 +11,4 @@ SESSION_EXPIRES= USER_CLIENT_CHALLENGE_EXPIRES= SESSION_UPGRADE_CHALLENGE_EXPIRES= LIBRARY_PATH= +THUMBNAILS_PATH= diff --git a/docker-compose.yaml b/docker-compose.yaml index dc7f392..eba1e94 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -7,6 +7,7 @@ services: user: ${CONTAINER_UID:-0}:${CONTAINER_GID:-0} volumes: - ./data/library:/app/data/library + - ./data/thumbnails:/app/data/thumbnails environment: # ArkVault - DATABASE_HOST=database @@ -17,6 +18,7 @@ services: - USER_CLIENT_CHALLENGE_EXPIRES - SESSION_UPGRADE_CHALLENGE_EXPIRES - LIBRARY_PATH=/app/data/library + - THUMBNAILS_PATH=/app/data/thumbnails # SvelteKit - ADDRESS_HEADER=${TRUST_PROXY:+X-Forwarded-For} - XFF_DEPTH=${TRUST_PROXY:-} diff --git a/src/lib/server/db/file.ts b/src/lib/server/db/file.ts index 20343b2..2affc7d 100644 --- a/src/lib/server/db/file.ts +++ b/src/lib/server/db/file.ts @@ -327,7 +327,8 @@ export const getAllFilesByCategory = async ( .where("user_id", "=", userId) .where("file_id", "is not", null) .$narrowType<{ file_id: NotNull }>() - .orderBy(["file_id", "depth"]) + .orderBy("file_id") + .orderBy("depth") .execute(); return files.map(({ file_id, depth }) => ({ id: file_id, isRecursive: depth > 0 })); }; diff --git a/src/lib/server/db/media.ts b/src/lib/server/db/media.ts new file mode 100644 index 0000000..1f1e065 --- /dev/null +++ b/src/lib/server/db/media.ts @@ -0,0 +1,86 @@ +import type { NotNull } from "kysely"; +import { IntegrityError } from "./error"; +import db from "./kysely"; + +interface Thumbnail { + id: number; + path: string; + createdAt: Date; + encContentIv: string; +} + +interface FileThumbnail extends Thumbnail { + fileId: number; +} + +export const updateFileThumbnail = async ( + userId: number, + fileId: number, + dekVersion: Date, + path: string, + encContentIv: string, +) => { + return 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"); + } + + const thumbnail = await trx + .selectFrom("thumbnail") + .select("path as old_path") + .where("file_id", "=", fileId) + .limit(1) + .forUpdate() + .executeTakeFirst(); + const now = new Date(); + + await trx + .insertInto("thumbnail") + .values({ + file_id: fileId, + path, + created_at: now, + encrypted_content_iv: encContentIv, + }) + .onConflict((oc) => + oc.column("file_id").doUpdateSet({ + path, + created_at: now, + encrypted_content_iv: encContentIv, + }), + ) + .execute(); + return thumbnail?.old_path; + }); +}; + +export const getFileThumbnail = async (userId: number, fileId: number) => { + const thumbnail = await db + .selectFrom("thumbnail") + .innerJoin("file", "thumbnail.file_id", "file.id") + .selectAll("thumbnail") + .where("file.id", "=", fileId) + .where("file.user_id", "=", userId) + .$narrowType<{ file_id: NotNull }>() + .limit(1) + .executeTakeFirst(); + return thumbnail + ? ({ + id: thumbnail.id, + fileId: thumbnail.file_id, + path: thumbnail.path, + encContentIv: thumbnail.encrypted_content_iv, + createdAt: thumbnail.created_at, + } satisfies FileThumbnail) + : null; +}; diff --git a/src/lib/server/db/schema/media.ts b/src/lib/server/db/schema/media.ts index 9eeccf7..ad593b4 100644 --- a/src/lib/server/db/schema/media.ts +++ b/src/lib/server/db/schema/media.ts @@ -1,4 +1,4 @@ -import type { ColumnType, Generated } from "kysely"; +import type { Generated } from "kysely"; interface ThumbnailTable { id: Generated; @@ -6,7 +6,7 @@ interface ThumbnailTable { file_id: number | null; category_id: number | null; path: string; - created_at: ColumnType; + created_at: Date; encrypted_content_iv: string; // Base64 } diff --git a/src/lib/server/loadenv.ts b/src/lib/server/loadenv.ts index d6f4675..3a805d8 100644 --- a/src/lib/server/loadenv.ts +++ b/src/lib/server/loadenv.ts @@ -25,4 +25,5 @@ export default { sessionUpgradeExp: ms(env.SESSION_UPGRADE_CHALLENGE_EXPIRES || "5m"), }, libraryPath: env.LIBRARY_PATH || "library", + thumbnailsPath: env.THUMBNAILS_PATH || "thumbnails", }; diff --git a/src/lib/server/schemas/file.ts b/src/lib/server/schemas/file.ts index b6aa648..4cf140f 100644 --- a/src/lib/server/schemas/file.ts +++ b/src/lib/server/schemas/file.ts @@ -30,6 +30,17 @@ export const fileRenameRequest = z.object({ }); export type FileRenameRequest = z.infer; +export const fileThumbnailInfoResponse = z.object({ + encContentIv: z.string().base64().nonempty(), +}); +export type FileThumbnailInfoResponse = z.infer; + +export const fileThumbnailUploadRequest = z.object({ + dekVersion: z.string().datetime(), + encContentIv: z.string().base64().nonempty(), +}); +export type FileThumbnailUploadRequest = z.infer; + export const duplicateFileScanRequest = z.object({ hskVersion: z.number().int().positive(), contentHmac: z.string().base64().nonempty(), diff --git a/src/lib/server/services/file.ts b/src/lib/server/services/file.ts index d0b35ef..83e5750 100644 --- a/src/lib/server/services/file.ts +++ b/src/lib/server/services/file.ts @@ -16,6 +16,7 @@ import { getAllFileCategories, type NewFile, } from "$lib/server/db/file"; +import { getFileThumbnail, updateFileThumbnail } from "$lib/server/db/media"; import type { Ciphertext } from "$lib/server/db/schema"; import env from "$lib/server/loadenv"; @@ -85,6 +86,59 @@ export const renameFile = async ( } }; +export const getFileThumbnailInformation = async (userId: number, fileId: number) => { + const thumbnail = await getFileThumbnail(userId, fileId); + if (!thumbnail) { + error(404, "File or its thumbnail not found"); + } + + return { encContentIv: thumbnail.encContentIv }; +}; + +export const getFileThumbnailStream = async (userId: number, fileId: number) => { + const thumbnail = await getFileThumbnail(userId, fileId); + if (!thumbnail) { + error(404, "File or its thumbnail not found"); + } + + const { size } = await stat(thumbnail.path); + return { + encContentStream: Readable.toWeb(createReadStream(thumbnail.path)), + encContentSize: size, + }; +}; + +export const uploadFileThumbnail = async ( + userId: number, + fileId: number, + dekVersion: Date, + encContentIv: string, + encContentStream: Readable, +) => { + const path = `${env.thumbnailsPath}/${userId}/${uuidv4()}`; + await mkdir(dirname(path), { recursive: true }); + + try { + await pipeline(encContentStream, createWriteStream(path, { flags: "wx", mode: 0o600 })); + + const oldPath = await updateFileThumbnail(userId, fileId, dekVersion, path, encContentIv); + if (oldPath) { + safeUnlink(oldPath); // Intended + } + } catch (e) { + await safeUnlink(path); + + if (e instanceof IntegrityError) { + if (e.message === "File not found") { + error(404, "File not found"); + } else if (e.message === "Invalid DEK version") { + error(400, "Mismatched DEK version"); + } + } + throw e; + } +}; + export const scanDuplicateFiles = async ( userId: number, hskVersion: number, diff --git a/src/routes/api/file/[id]/thumbnail/+server.ts b/src/routes/api/file/[id]/thumbnail/+server.ts new file mode 100644 index 0000000..ab9d48c --- /dev/null +++ b/src/routes/api/file/[id]/thumbnail/+server.ts @@ -0,0 +1,23 @@ +import { error, json } from "@sveltejs/kit"; +import { z } from "zod"; +import { authorize } from "$lib/server/modules/auth"; +import { fileThumbnailInfoResponse, type FileThumbnailInfoResponse } from "$lib/server/schemas"; +import { getFileThumbnailInformation } from "$lib/server/services/file"; +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 { encContentIv } = await getFileThumbnailInformation(userId, id); + return json( + fileThumbnailInfoResponse.parse({ encContentIv } satisfies FileThumbnailInfoResponse), + ); +}; diff --git a/src/routes/api/file/[id]/thumbnail/download/+server.ts b/src/routes/api/file/[id]/thumbnail/download/+server.ts new file mode 100644 index 0000000..addd800 --- /dev/null +++ b/src/routes/api/file/[id]/thumbnail/download/+server.ts @@ -0,0 +1,25 @@ +import { error } from "@sveltejs/kit"; +import { z } from "zod"; +import { authorize } from "$lib/server/modules/auth"; +import { getFileThumbnailStream } from "$lib/server/services/file"; +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 { encContentStream, encContentSize } = await getFileThumbnailStream(userId, id); + return new Response(encContentStream as ReadableStream, { + headers: { + "Content-Type": "application/octet-stream", + "Content-Length": encContentSize.toString(), + }, + }); +}; diff --git a/src/routes/api/file/[id]/thumbnail/upload/+server.ts b/src/routes/api/file/[id]/thumbnail/upload/+server.ts new file mode 100644 index 0000000..52b99a8 --- /dev/null +++ b/src/routes/api/file/[id]/thumbnail/upload/+server.ts @@ -0,0 +1,72 @@ +import Busboy from "@fastify/busboy"; +import { error, text } from "@sveltejs/kit"; +import { Readable, Writable } from "stream"; +import { z } from "zod"; +import { authorize } from "$lib/server/modules/auth"; +import { fileThumbnailUploadRequest, type FileThumbnailUploadRequest } from "$lib/server/schemas"; +import { uploadFileThumbnail } from "$lib/server/services/file"; +import type { RequestHandler } from "./$types"; + +export const POST: RequestHandler = async ({ locals, params, request }) => { + 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 contentType = request.headers.get("Content-Type"); + if (!contentType?.startsWith("multipart/form-data") || !request.body) { + error(400, "Invalid request body"); + } + + return new Promise((resolve, reject) => { + const bb = Busboy({ headers: { "content-type": contentType } }); + const handler = + (f: (...args: T) => Promise) => + (...args: T) => { + f(...args).catch(reject); + }; + + let metadata: FileThumbnailUploadRequest | null = null; + let content: Readable | null = null; + bb.on( + "field", + handler(async (fieldname, val) => { + if (fieldname === "metadata") { + // Ignore subsequent metadata fields + if (!metadata) { + metadata = fileThumbnailUploadRequest.parse(val); + } + } 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; + + await uploadFileThumbnail( + userId, + id, + new Date(metadata.dekVersion), + metadata.encContentIv, + content, + ); + resolve(text("Thumbnail uploaded", { headers: { "Content-Type": "text/plain" } })); + }), + ); + bb.on("error", (e) => { + content?.emit("error", e) ?? reject(e); + }); + + request.body!.pipeTo(Writable.toWeb(bb)).catch(() => {}); // busboy will handle the error + }); +}; From c2362421363fee47892be3451cefd5eb417f5ba2 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 5 Jul 2025 05:54:55 +0900 Subject: [PATCH 09/38] =?UTF-8?q?thumbnail=20=ED=85=8C=EC=9D=B4=EB=B8=94?= =?UTF-8?q?=EC=9D=98=20created=5Fat=20=EC=BB=AC=EB=9F=BC=EC=9D=98=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=EC=9D=84=20updated=5Fat=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/server/db/media.ts | 8 ++++---- src/lib/server/db/migrations/1738409340-AddThumbnail.ts | 2 +- src/lib/server/db/schema/media.ts | 2 +- src/lib/server/schemas/file.ts | 1 + src/lib/server/services/file.ts | 2 +- src/routes/api/file/[id]/thumbnail/+server.ts | 7 +++++-- 6 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/lib/server/db/media.ts b/src/lib/server/db/media.ts index 1f1e065..fbd8976 100644 --- a/src/lib/server/db/media.ts +++ b/src/lib/server/db/media.ts @@ -5,7 +5,7 @@ import db from "./kysely"; interface Thumbnail { id: number; path: string; - createdAt: Date; + updatedAt: Date; encContentIv: string; } @@ -49,13 +49,13 @@ export const updateFileThumbnail = async ( .values({ file_id: fileId, path, - created_at: now, + updated_at: now, encrypted_content_iv: encContentIv, }) .onConflict((oc) => oc.column("file_id").doUpdateSet({ path, - created_at: now, + updated_at: now, encrypted_content_iv: encContentIv, }), ) @@ -80,7 +80,7 @@ export const getFileThumbnail = async (userId: number, fileId: number) => { fileId: thumbnail.file_id, path: thumbnail.path, encContentIv: thumbnail.encrypted_content_iv, - createdAt: thumbnail.created_at, + updatedAt: thumbnail.updated_at, } satisfies FileThumbnail) : null; }; diff --git a/src/lib/server/db/migrations/1738409340-AddThumbnail.ts b/src/lib/server/db/migrations/1738409340-AddThumbnail.ts index 0e38647..c3ce806 100644 --- a/src/lib/server/db/migrations/1738409340-AddThumbnail.ts +++ b/src/lib/server/db/migrations/1738409340-AddThumbnail.ts @@ -16,7 +16,7 @@ export const up = async (db: Kysely) => { col.references("category.id").onDelete("cascade").unique(), ) .addColumn("path", "text", (col) => col.unique().notNull()) - .addColumn("created_at", "timestamp(3)", (col) => col.notNull()) + .addColumn("updated_at", "timestamp(3)", (col) => col.notNull()) .addColumn("encrypted_content_iv", "text", (col) => col.notNull()) .addCheckConstraint( "thumbnail_ck01", diff --git a/src/lib/server/db/schema/media.ts b/src/lib/server/db/schema/media.ts index ad593b4..ebfbf29 100644 --- a/src/lib/server/db/schema/media.ts +++ b/src/lib/server/db/schema/media.ts @@ -6,7 +6,7 @@ interface ThumbnailTable { file_id: number | null; category_id: number | null; path: string; - created_at: Date; + updated_at: Date; encrypted_content_iv: string; // Base64 } diff --git a/src/lib/server/schemas/file.ts b/src/lib/server/schemas/file.ts index 4cf140f..1d7ccb5 100644 --- a/src/lib/server/schemas/file.ts +++ b/src/lib/server/schemas/file.ts @@ -31,6 +31,7 @@ export const fileRenameRequest = z.object({ export type FileRenameRequest = z.infer; export const fileThumbnailInfoResponse = z.object({ + updatedAt: z.string().datetime(), encContentIv: z.string().base64().nonempty(), }); export type FileThumbnailInfoResponse = z.infer; diff --git a/src/lib/server/services/file.ts b/src/lib/server/services/file.ts index 83e5750..7616739 100644 --- a/src/lib/server/services/file.ts +++ b/src/lib/server/services/file.ts @@ -92,7 +92,7 @@ export const getFileThumbnailInformation = async (userId: number, fileId: number error(404, "File or its thumbnail not found"); } - return { encContentIv: thumbnail.encContentIv }; + return { updatedAt: thumbnail.updatedAt, encContentIv: thumbnail.encContentIv }; }; export const getFileThumbnailStream = async (userId: number, fileId: number) => { diff --git a/src/routes/api/file/[id]/thumbnail/+server.ts b/src/routes/api/file/[id]/thumbnail/+server.ts index ab9d48c..7bc81ca 100644 --- a/src/routes/api/file/[id]/thumbnail/+server.ts +++ b/src/routes/api/file/[id]/thumbnail/+server.ts @@ -16,8 +16,11 @@ export const GET: RequestHandler = async ({ locals, params }) => { if (!zodRes.success) error(400, "Invalid path parameters"); const { id } = zodRes.data; - const { encContentIv } = await getFileThumbnailInformation(userId, id); + const { updatedAt, encContentIv } = await getFileThumbnailInformation(userId, id); return json( - fileThumbnailInfoResponse.parse({ encContentIv } satisfies FileThumbnailInfoResponse), + fileThumbnailInfoResponse.parse({ + updatedAt: updatedAt.toISOString(), + encContentIv, + } satisfies FileThumbnailInfoResponse), ); }; From eaf2d7f2020eed816d5b381befe4c1244bb21569 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 5 Jul 2025 16:55:09 +0900 Subject: [PATCH 10/38] =?UTF-8?q?=EC=8D=B8=EB=84=A4=EC=9D=BC=20=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .dockerignore | 1 + .gitignore | 1 + src/lib/modules/file/upload.ts | 53 ++++++++++++++++++- src/lib/server/schemas/file.ts | 4 +- src/routes/api/file/[id]/thumbnail/+server.ts | 2 +- .../api/file/[id]/thumbnail/upload/+server.ts | 6 ++- 6 files changed, 60 insertions(+), 7 deletions(-) diff --git a/.dockerignore b/.dockerignore index ed4c8e5..495d123 100644 --- a/.dockerignore +++ b/.dockerignore @@ -10,6 +10,7 @@ node_modules /build /data /library +/thumbnails # OS .DS_Store diff --git a/.gitignore b/.gitignore index aac77c6..73eddae 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ node_modules /build /data /library +/thumbnails # OS .DS_Store diff --git a/src/lib/modules/file/upload.ts b/src/lib/modules/file/upload.ts index 71a38fb..9583fd1 100644 --- a/src/lib/modules/file/upload.ts +++ b/src/lib/modules/file/upload.ts @@ -11,9 +11,11 @@ import { digestMessage, signMessageHmac, } from "$lib/modules/crypto"; +import { generateImageThumbnail, generateVideoThumbnail } from "$lib/modules/thumbnail"; import type { DuplicateFileScanRequest, DuplicateFileScanResponse, + FileThumbnailUploadRequest, FileUploadRequest, FileUploadResponse, } from "$lib/server/schemas"; @@ -76,6 +78,24 @@ const extractExifDateTime = (fileBuffer: ArrayBuffer) => { return new Date(utcDate - offsetMs); }; +const generateThumbnail = async (file: File, fileType: string) => { + let url; + try { + if (fileType.startsWith("image/")) { + url = URL.createObjectURL(file); + return await generateImageThumbnail(url); + } else if (fileType.startsWith("video/")) { + url = URL.createObjectURL(file); + return await generateVideoThumbnail(url); + } + return null; + } finally { + if (url) { + URL.revokeObjectURL(url); + } + } +}; + const encryptFile = limitFunction( async ( status: Writable, @@ -106,6 +126,11 @@ const encryptFile = limitFunction( createdAt && (await encryptString(createdAt.getTime().toString(), dataKey)); const lastModifiedAtEncrypted = await encryptString(file.lastModified.toString(), dataKey); + const thumbnail = await generateThumbnail(file, fileType); + const thumbnailEncrypted = thumbnail + ? await encryptData(await thumbnail.arrayBuffer(), dataKey) + : null; + status.update((value) => { value.status = "upload-pending"; return value; @@ -120,13 +145,14 @@ const encryptFile = limitFunction( nameEncrypted, createdAtEncrypted, lastModifiedAtEncrypted, + thumbnailEncrypted, }; }, { concurrency: 4 }, ); const requestFileUpload = limitFunction( - async (status: Writable, form: FormData) => { + async (status: Writable, form: FormData, thumbnailForm: FormData | null) => { status.update((value) => { value.status = "uploading"; return value; @@ -144,6 +170,15 @@ const requestFileUpload = limitFunction( }); const { file }: FileUploadResponse = res.data; + if (thumbnailForm) { + try { + await axios.post(`/api/file/${file}/thumbnail/upload`, thumbnailForm); + } catch (e) { + // TODO + console.error(e); + } + } + status.update((value) => { value.status = "uploaded"; return value; @@ -198,6 +233,7 @@ export const uploadFile = async ( nameEncrypted, createdAtEncrypted, lastModifiedAtEncrypted, + thumbnailEncrypted, } = await encryptFile(status, file, fileBuffer, masterKey); const form = new FormData(); @@ -223,7 +259,20 @@ export const uploadFile = async ( form.set("content", new Blob([fileEncrypted.ciphertext])); form.set("checksum", fileEncryptedHash); - const { fileId } = await requestFileUpload(status, form); + let thumbnailForm = null; + if (thumbnailEncrypted) { + thumbnailForm = new FormData(); + thumbnailForm.set( + "metadata", + JSON.stringify({ + dekVersion: dataKeyVersion.toISOString(), + contentIv: thumbnailEncrypted.iv, + } as FileThumbnailUploadRequest), + ); + thumbnailForm.set("content", new Blob([thumbnailEncrypted.ciphertext])); + } + + const { fileId } = await requestFileUpload(status, form, thumbnailForm); return { fileId, fileBuffer }; } catch (e) { status.update((value) => { diff --git a/src/lib/server/schemas/file.ts b/src/lib/server/schemas/file.ts index 1d7ccb5..7c38911 100644 --- a/src/lib/server/schemas/file.ts +++ b/src/lib/server/schemas/file.ts @@ -32,13 +32,13 @@ export type FileRenameRequest = z.infer; export const fileThumbnailInfoResponse = z.object({ updatedAt: z.string().datetime(), - encContentIv: z.string().base64().nonempty(), + contentIv: z.string().base64().nonempty(), }); export type FileThumbnailInfoResponse = z.infer; export const fileThumbnailUploadRequest = z.object({ dekVersion: z.string().datetime(), - encContentIv: z.string().base64().nonempty(), + contentIv: z.string().base64().nonempty(), }); export type FileThumbnailUploadRequest = z.infer; diff --git a/src/routes/api/file/[id]/thumbnail/+server.ts b/src/routes/api/file/[id]/thumbnail/+server.ts index 7bc81ca..12c9347 100644 --- a/src/routes/api/file/[id]/thumbnail/+server.ts +++ b/src/routes/api/file/[id]/thumbnail/+server.ts @@ -20,7 +20,7 @@ export const GET: RequestHandler = async ({ locals, params }) => { return json( fileThumbnailInfoResponse.parse({ updatedAt: updatedAt.toISOString(), - encContentIv, + contentIv: encContentIv, } satisfies FileThumbnailInfoResponse), ); }; diff --git a/src/routes/api/file/[id]/thumbnail/upload/+server.ts b/src/routes/api/file/[id]/thumbnail/upload/+server.ts index 52b99a8..62dfe42 100644 --- a/src/routes/api/file/[id]/thumbnail/upload/+server.ts +++ b/src/routes/api/file/[id]/thumbnail/upload/+server.ts @@ -39,7 +39,9 @@ export const POST: RequestHandler = async ({ locals, params, request }) => { if (fieldname === "metadata") { // Ignore subsequent metadata fields if (!metadata) { - metadata = fileThumbnailUploadRequest.parse(val); + const zodRes = fileThumbnailUploadRequest.safeParse(JSON.parse(val)); + if (!zodRes.success) error(400, "Invalid request body"); + metadata = zodRes.data; } } else { error(400, "Invalid request body"); @@ -57,7 +59,7 @@ export const POST: RequestHandler = async ({ locals, params, request }) => { userId, id, new Date(metadata.dekVersion), - metadata.encContentIv, + metadata.contentIv, content, ); resolve(text("Thumbnail uploaded", { headers: { "Content-Type": "text/plain" } })); From 9e6792096866d0eade1fb339e2d87e053da409d0 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 5 Jul 2025 18:18:10 +0900 Subject: [PATCH 11/38] =?UTF-8?q?=EC=8D=B8=EB=84=A4=EC=9D=BC=20=ED=91=9C?= =?UTF-8?q?=EC=8B=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../labels/DirectoryEntryLabel.svelte | 31 +++++++++++++++++-- .../molecules/labels/IconLabel.svelte | 2 +- .../[[id]]/DirectoryEntries/File.svelte | 19 ++++++++++++ .../[[id]]/DirectoryEntries/service.ts | 17 ++++++++++ 4 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts diff --git a/src/lib/components/molecules/labels/DirectoryEntryLabel.svelte b/src/lib/components/molecules/labels/DirectoryEntryLabel.svelte index 5d4fb81..9878e26 100644 --- a/src/lib/components/molecules/labels/DirectoryEntryLabel.svelte +++ b/src/lib/components/molecules/labels/DirectoryEntryLabel.svelte @@ -10,18 +10,45 @@ name: string; subtext?: string; textClass?: ClassValue; + thumbnail?: ArrayBuffer; type: "directory" | "file"; } - let { class: className, name, subtext, textClass: textClassName, type }: Props = $props(); + let { + class: className, + name, + subtext, + textClass: textClassName, + thumbnail, + type, + }: Props = $props(); + + let thumbnailUrl: string | undefined = $state(); + + $effect(() => { + thumbnailUrl = thumbnail && URL.createObjectURL(new Blob([thumbnail])); + return () => thumbnailUrl && URL.revokeObjectURL(thumbnailUrl); + }); +{#snippet iconSnippet()} +
+ {#if thumbnailUrl} + {name} + {:else if type === "directory"} + + {:else} + + {/if} +
+{/snippet} + {#snippet subtextSnippet()} {subtext} {/snippet} ; + icon: Component | Snippet; iconClass?: ClassValue; subtext?: Snippet; textClass?: ClassValue; diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte b/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte index fd59d03..22870e6 100644 --- a/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte +++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte @@ -4,6 +4,7 @@ import { DirectoryEntryLabel } from "$lib/components/molecules"; import type { FileInfo } from "$lib/modules/filesystem"; import { formatDateTime } from "$lib/modules/util"; + import { getFileThumbnail } from "./service"; import type { SelectedEntry } from "../service.svelte"; import IconMoreVert from "~icons/material-symbols/more-vert"; @@ -16,6 +17,8 @@ let { info, onclick, onOpenMenuClick }: Props = $props(); + let thumbnail: ArrayBuffer | undefined = $state(); + const openFile = () => { const { id, dataKey, dataKeyVersion, name } = $info!; if (!dataKey || !dataKeyVersion) return; // TODO: Error handling @@ -29,6 +32,21 @@ onOpenMenuClick({ type: "file", id, dataKey, dataKeyVersion, name }); }; + + $effect(() => { + if ($info?.dataKey) { + getFileThumbnail($info.id, $info.dataKey) + .then((thumbnailData) => { + thumbnail = thumbnailData ?? undefined; + }) + .catch(() => { + // TODO: Error handling + thumbnail = undefined; + }); + } else { + thumbnail = undefined; + } + }); {#if $info} @@ -40,6 +58,7 @@ > diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts b/src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts new file mode 100644 index 0000000..a14a866 --- /dev/null +++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts @@ -0,0 +1,17 @@ +import { callGetApi } from "$lib/hooks"; +import { decryptData } from "$lib/modules/crypto"; +import type { FileThumbnailInfoResponse } from "$lib/server/schemas"; + +export const getFileThumbnail = async (fileId: number, dataKey: CryptoKey) => { + let res = await callGetApi(`/api/file/${fileId}/thumbnail`); + if (!res.ok) return null; + + const { contentIv: thumbnailEncryptedIv }: FileThumbnailInfoResponse = await res.json(); + + res = await callGetApi(`/api/file/${fileId}/thumbnail/download`); + if (!res.ok) return null; + + const thumbnailEncrypted = await res.arrayBuffer(); + const thumbnail = await decryptData(thumbnailEncrypted, thumbnailEncryptedIv, dataKey); + return thumbnail; +}; From 3a637b14b429bb433a3d709dd717eaf05de41fd8 Mon Sep 17 00:00:00 2001 From: static Date: Sun, 6 Jul 2025 00:25:50 +0900 Subject: [PATCH 12/38] =?UTF-8?q?=EB=88=84=EB=9D=BD=EB=90=9C=20=EC=8D=B8?= =?UTF-8?q?=EB=84=A4=EC=9D=BC=20=EC=83=9D=EC=84=B1=20=EA=B8=B0=EB=8A=A5=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/file/upload.ts | 7 +- src/lib/server/db/file.ts | 2 +- src/lib/server/db/media.ts | 24 ++++++ src/lib/server/schemas/file.ts | 5 ++ src/lib/server/services/file.ts | 13 ++- .../settings/thumbnails/+page.svelte | 84 +++++++++++++++++++ .../(fullscreen)/settings/thumbnails/+page.ts | 14 ++++ .../settings/thumbnails/File.svelte | 33 ++++++++ .../settings/thumbnails/service.ts | 61 ++++++++++++++ src/routes/(main)/menu/+page.svelte | 8 ++ .../api/file/scanMissingThumbnails/+server.ts | 17 ++++ 11 files changed, 263 insertions(+), 5 deletions(-) create mode 100644 src/routes/(fullscreen)/settings/thumbnails/+page.svelte create mode 100644 src/routes/(fullscreen)/settings/thumbnails/+page.ts create mode 100644 src/routes/(fullscreen)/settings/thumbnails/File.svelte create mode 100644 src/routes/(fullscreen)/settings/thumbnails/service.ts create mode 100644 src/routes/api/file/scanMissingThumbnails/+server.ts diff --git a/src/lib/modules/file/upload.ts b/src/lib/modules/file/upload.ts index 9583fd1..ac03e47 100644 --- a/src/lib/modules/file/upload.ts +++ b/src/lib/modules/file/upload.ts @@ -89,6 +89,9 @@ const generateThumbnail = async (file: File, fileType: string) => { return await generateVideoThumbnail(url); } return null; + } catch { + // TODO: Error handling + return null; } finally { if (url) { URL.revokeObjectURL(url); @@ -254,7 +257,7 @@ export const uploadFile = async ( createdAtIv: createdAtEncrypted?.iv, lastModifiedAt: lastModifiedAtEncrypted.ciphertext, lastModifiedAtIv: lastModifiedAtEncrypted.iv, - } as FileUploadRequest), + } satisfies FileUploadRequest), ); form.set("content", new Blob([fileEncrypted.ciphertext])); form.set("checksum", fileEncryptedHash); @@ -267,7 +270,7 @@ export const uploadFile = async ( JSON.stringify({ dekVersion: dataKeyVersion.toISOString(), contentIv: thumbnailEncrypted.iv, - } as FileThumbnailUploadRequest), + } satisfies FileThumbnailUploadRequest), ); thumbnailForm.set("content", new Blob([thumbnailEncrypted.ciphertext])); } diff --git a/src/lib/server/db/file.ts b/src/lib/server/db/file.ts index 2affc7d..0a76b6d 100644 --- a/src/lib/server/db/file.ts +++ b/src/lib/server/db/file.ts @@ -345,7 +345,7 @@ export const getAllFileIdsByContentHmac = async ( .where("hmac_secret_key_version", "=", hskVersion) .where("content_hmac", "=", contentHmac) .execute(); - return files.map(({ id }) => ({ id })); + return files.map(({ id }) => id); }; export const getFile = async (userId: number, fileId: number) => { diff --git a/src/lib/server/db/media.ts b/src/lib/server/db/media.ts index fbd8976..360ed49 100644 --- a/src/lib/server/db/media.ts +++ b/src/lib/server/db/media.ts @@ -84,3 +84,27 @@ export const getFileThumbnail = async (userId: number, fileId: number) => { } satisfies FileThumbnail) : null; }; + +export const getMissingFileThumbnails = async (userId: number, limit: number = 100) => { + const files = await db + .selectFrom("file") + .select("id") + .where("user_id", "=", userId) + .where((eb) => + eb.or([eb("content_type", "like", "image/%"), eb("content_type", "like", "video/%")]), + ) + .where((eb) => + eb.not( + eb.exists( + eb + .selectFrom("thumbnail") + .select("thumbnail.id") + .whereRef("thumbnail.file_id", "=", "file.id") + .limit(1), + ), + ), + ) + .limit(limit) + .execute(); + return files.map((file) => file.id); +}; diff --git a/src/lib/server/schemas/file.ts b/src/lib/server/schemas/file.ts index 7c38911..d0687b7 100644 --- a/src/lib/server/schemas/file.ts +++ b/src/lib/server/schemas/file.ts @@ -53,6 +53,11 @@ export const duplicateFileScanResponse = z.object({ }); export type DuplicateFileScanResponse = z.infer; +export const missingThumbnailFileScanResponse = z.object({ + files: z.number().int().positive().array(), +}); +export type MissingThumbnailFileScanResponse = z.infer; + export const fileUploadRequest = z.object({ parent: directoryIdSchema, mekVersion: z.number().int().positive(), diff --git a/src/lib/server/services/file.ts b/src/lib/server/services/file.ts index 7616739..6f5af03 100644 --- a/src/lib/server/services/file.ts +++ b/src/lib/server/services/file.ts @@ -16,7 +16,11 @@ import { getAllFileCategories, type NewFile, } from "$lib/server/db/file"; -import { getFileThumbnail, updateFileThumbnail } from "$lib/server/db/media"; +import { + updateFileThumbnail, + getFileThumbnail, + getMissingFileThumbnails, +} from "$lib/server/db/media"; import type { Ciphertext } from "$lib/server/db/schema"; import env from "$lib/server/loadenv"; @@ -145,7 +149,12 @@ export const scanDuplicateFiles = async ( contentHmac: string, ) => { const fileIds = await getAllFileIdsByContentHmac(userId, hskVersion, contentHmac); - return { files: fileIds.map(({ id }) => id) }; + return { files: fileIds }; +}; + +export const scanMissingFileThumbnails = async (userId: number) => { + const fileIds = await getMissingFileThumbnails(userId); + return { files: fileIds }; }; const safeUnlink = async (path: string) => { diff --git a/src/routes/(fullscreen)/settings/thumbnails/+page.svelte b/src/routes/(fullscreen)/settings/thumbnails/+page.svelte new file mode 100644 index 0000000..f57d542 --- /dev/null +++ b/src/routes/(fullscreen)/settings/thumbnails/+page.svelte @@ -0,0 +1,84 @@ + + + + 썸네일 설정 + + + + + {#if fileInfos && fileInfos.length > 0} +
+
+

+ {fileInfos.length}개 파일의 썸네일이 존재하지 않아요. +

+
+
+ {#each fileInfos as fileInfo} + goto(`/file/${id}`)} + onGenerateThumbnailClick={generateThumbnail} + /> + {/each} +
+
+ + + + {:else} +
+

모든 파일의 썸네일이 존재해요.

+
+ {/if} +
diff --git a/src/routes/(fullscreen)/settings/thumbnails/+page.ts b/src/routes/(fullscreen)/settings/thumbnails/+page.ts new file mode 100644 index 0000000..3fc7cff --- /dev/null +++ b/src/routes/(fullscreen)/settings/thumbnails/+page.ts @@ -0,0 +1,14 @@ +import { error } from "@sveltejs/kit"; +import { callPostApi } from "$lib/hooks"; +import type { MissingThumbnailFileScanResponse } from "$lib/server/schemas/file"; +import type { PageLoad } from "./$types"; + +export const load: PageLoad = async ({ fetch }) => { + const res = await callPostApi("/api/file/scanMissingThumbnails", undefined, fetch); + if (!res.ok) { + error(500, "Internal server error"); + } + + const { files }: MissingThumbnailFileScanResponse = await res.json(); + return { files }; +}; diff --git a/src/routes/(fullscreen)/settings/thumbnails/File.svelte b/src/routes/(fullscreen)/settings/thumbnails/File.svelte new file mode 100644 index 0000000..d06d435 --- /dev/null +++ b/src/routes/(fullscreen)/settings/thumbnails/File.svelte @@ -0,0 +1,33 @@ + + +{#if $info} + onclick($info)} + actionButtonIcon={IconCamera} + onActionButtonClick={() => onGenerateThumbnailClick($info)} + actionButtonClass="text-gray-800" + > + + +{/if} diff --git a/src/routes/(fullscreen)/settings/thumbnails/service.ts b/src/routes/(fullscreen)/settings/thumbnails/service.ts new file mode 100644 index 0000000..a064078 --- /dev/null +++ b/src/routes/(fullscreen)/settings/thumbnails/service.ts @@ -0,0 +1,61 @@ +import { limitFunction } from "p-limit"; +import { encryptData } from "$lib/modules/crypto"; +import { getFileCache, storeFileCache, downloadFile } from "$lib/modules/file"; +import { generateImageThumbnail, generateVideoThumbnail } from "$lib/modules/thumbnail"; +import type { FileThumbnailUploadRequest } from "$lib/server/schemas"; + +export const requestFileDownload = async ( + fileId: number, + fileEncryptedIv: string, + dataKey: CryptoKey, +) => { + const cache = await getFileCache(fileId); + if (cache) return cache; + + const fileBuffer = await downloadFile(fileId, fileEncryptedIv, dataKey); + storeFileCache(fileId, fileBuffer); // Intended + return fileBuffer; +}; + +export const generateThumbnail = limitFunction( + async (fileBuffer: ArrayBuffer, fileType: string) => { + let url; + try { + if (fileType.startsWith("image/")) { + url = URL.createObjectURL(new Blob([fileBuffer], { type: fileType })); + return await generateImageThumbnail(url); + } else if (fileType.startsWith("video/")) { + url = URL.createObjectURL(new Blob([fileBuffer], { type: fileType })); + return await generateVideoThumbnail(url); + } + return null; + } catch { + // TODO: Error handling + return null; + } finally { + if (url) { + URL.revokeObjectURL(url); + } + } + }, + { concurrency: 4 }, +); + +export const requestThumbnailUpload = limitFunction( + async (fileId: number, thumbnail: ArrayBuffer, dataKey: CryptoKey, dataKeyVersion: Date) => { + const thumbnailEncrypted = await encryptData(thumbnail, dataKey); + const form = new FormData(); + form.set( + "metadata", + JSON.stringify({ + dekVersion: dataKeyVersion.toISOString(), + contentIv: thumbnailEncrypted.iv, + } satisfies FileThumbnailUploadRequest), + ); + form.set("content", new Blob([thumbnailEncrypted.ciphertext])); + + const res = await fetch(`/api/file/${fileId}/thumbnail/upload`, { method: "POST", body: form }); + return res.ok; + }, + { concurrency: 1 }, +); diff --git a/src/routes/(main)/menu/+page.svelte b/src/routes/(main)/menu/+page.svelte index 13ccb92..6a52128 100644 --- a/src/routes/(main)/menu/+page.svelte +++ b/src/routes/(main)/menu/+page.svelte @@ -4,6 +4,7 @@ import { requestLogout } from "./service"; import IconStorage from "~icons/material-symbols/storage"; + import IconImage from "~icons/material-symbols/image"; import IconPassword from "~icons/material-symbols/password"; import IconLogout from "~icons/material-symbols/logout"; @@ -33,6 +34,13 @@ > 캐시 + goto("/settings/thumbnails")} + icon={IconImage} + iconColor="text-blue-500" + > + 썸네일 +

보안

diff --git a/src/routes/api/file/scanMissingThumbnails/+server.ts b/src/routes/api/file/scanMissingThumbnails/+server.ts new file mode 100644 index 0000000..c7ceef2 --- /dev/null +++ b/src/routes/api/file/scanMissingThumbnails/+server.ts @@ -0,0 +1,17 @@ +import { json } from "@sveltejs/kit"; +import { authorize } from "$lib/server/modules/auth"; +import { + missingThumbnailFileScanResponse, + type MissingThumbnailFileScanResponse, +} from "$lib/server/schemas/file"; +import { scanMissingFileThumbnails } from "$lib/server/services/file"; +import type { RequestHandler } from "./$types"; + +export const POST: RequestHandler = async ({ locals }) => { + const { userId } = await authorize(locals, "activeClient"); + + const { files } = await scanMissingFileThumbnails(userId); + return json( + missingThumbnailFileScanResponse.parse({ files } satisfies MissingThumbnailFileScanResponse), + ); +}; From 781642fed64d2b6814e6e0d0131495e0b1e97d1c Mon Sep 17 00:00:00 2001 From: static Date: Sun, 6 Jul 2025 05:36:05 +0900 Subject: [PATCH 13/38] =?UTF-8?q?=EC=8D=B8=EB=84=A4=EC=9D=BC=EC=9D=84=20?= =?UTF-8?q?=EB=A9=94=EB=AA=A8=EB=A6=AC=EC=99=80=20OPFS=EC=97=90=20?= =?UTF-8?q?=EC=BA=90=EC=8B=9C=ED=95=98=EB=8F=84=EB=A1=9D=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + pnpm-lock.yaml | 9 ++++++ .../labels/DirectoryEntryLabel.svelte | 13 ++------- src/lib/modules/file/index.ts | 1 + src/lib/modules/file/thumbnail.ts | 29 +++++++++++++++++++ src/lib/modules/file/upload.ts | 22 +++++++------- src/lib/modules/thumbnail.ts | 6 ++++ .../settings/thumbnails/service.ts | 9 ++++-- .../[[id]]/DirectoryEntries/File.svelte | 16 ++++++---- .../[[id]]/DirectoryEntries/service.ts | 8 +++-- .../(main)/directory/[[id]]/service.svelte.ts | 6 +++- 11 files changed, 88 insertions(+), 32 deletions(-) create mode 100644 src/lib/modules/file/thumbnail.ts diff --git a/package.json b/package.json index 8d0ddba..7228980 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "globals": "^15.14.0", "heic2any": "^0.0.4", "kysely-ctl": "^0.10.1", + "lru-cache": "^11.1.0", "mime": "^4.0.6", "p-limit": "^6.2.0", "prettier": "^3.4.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9ed4442..be3e935 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -96,6 +96,9 @@ importers: kysely-ctl: specifier: ^0.10.1 version: 0.10.1(kysely@0.27.5) + lru-cache: + specifier: ^11.1.0 + version: 11.1.0 mime: specifier: ^4.0.6 version: 4.0.6 @@ -1414,6 +1417,10 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@11.1.0: + resolution: {integrity: sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==} + engines: {node: 20 || >=22} + luxon@3.5.0: resolution: {integrity: sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==} engines: {node: '>=12'} @@ -3369,6 +3376,8 @@ snapshots: lru-cache@10.4.3: {} + lru-cache@11.1.0: {} + luxon@3.5.0: {} magic-string@0.30.17: diff --git a/src/lib/components/molecules/labels/DirectoryEntryLabel.svelte b/src/lib/components/molecules/labels/DirectoryEntryLabel.svelte index 9878e26..e38b348 100644 --- a/src/lib/components/molecules/labels/DirectoryEntryLabel.svelte +++ b/src/lib/components/molecules/labels/DirectoryEntryLabel.svelte @@ -10,7 +10,7 @@ name: string; subtext?: string; textClass?: ClassValue; - thumbnail?: ArrayBuffer; + thumbnail?: string; type: "directory" | "file"; } @@ -22,19 +22,12 @@ thumbnail, type, }: Props = $props(); - - let thumbnailUrl: string | undefined = $state(); - - $effect(() => { - thumbnailUrl = thumbnail && URL.createObjectURL(new Blob([thumbnail])); - return () => thumbnailUrl && URL.revokeObjectURL(thumbnailUrl); - }); {#snippet iconSnippet()}
- {#if thumbnailUrl} - {name} + {#if thumbnail} + {name} {:else if type === "directory"} {:else} diff --git a/src/lib/modules/file/index.ts b/src/lib/modules/file/index.ts index 42a5613..dc708ac 100644 --- a/src/lib/modules/file/index.ts +++ b/src/lib/modules/file/index.ts @@ -1,3 +1,4 @@ export * from "./cache"; export * from "./download"; +export * from "./thumbnail"; export * from "./upload"; diff --git a/src/lib/modules/file/thumbnail.ts b/src/lib/modules/file/thumbnail.ts new file mode 100644 index 0000000..e78786c --- /dev/null +++ b/src/lib/modules/file/thumbnail.ts @@ -0,0 +1,29 @@ +import { LRUCache } from "lru-cache"; +import { readFile, writeFile, deleteFile } from "$lib/modules/opfs"; +import { getThumbnailUrl } from "$lib/modules/thumbnail"; + +const loadedThumbnails = new LRUCache({ max: 100 }); + +export const getFileThumbnail = async (fileId: number) => { + const thumbnail = loadedThumbnails.get(fileId); + if (thumbnail) { + return thumbnail; + } + + const thumbnailBuffer = await readFile(`/thumbnails/${fileId}`); + if (!thumbnailBuffer) return null; + + const thumbnailUrl = getThumbnailUrl(thumbnailBuffer); + loadedThumbnails.set(fileId, thumbnailUrl); + return thumbnailUrl; +}; + +export const storeFileThumbnail = async (fileId: number, thumbnailBuffer: ArrayBuffer) => { + await writeFile(`/thumbnails/${fileId}`, thumbnailBuffer); + loadedThumbnails.set(fileId, getThumbnailUrl(thumbnailBuffer)); +}; + +export const deleteFileThumbnail = async (fileId: number) => { + loadedThumbnails.delete(fileId); + await deleteFile(`/thumbnails/${fileId}`); +}; diff --git a/src/lib/modules/file/upload.ts b/src/lib/modules/file/upload.ts index ac03e47..b56375f 100644 --- a/src/lib/modules/file/upload.ts +++ b/src/lib/modules/file/upload.ts @@ -130,9 +130,8 @@ const encryptFile = limitFunction( const lastModifiedAtEncrypted = await encryptString(file.lastModified.toString(), dataKey); const thumbnail = await generateThumbnail(file, fileType); - const thumbnailEncrypted = thumbnail - ? await encryptData(await thumbnail.arrayBuffer(), dataKey) - : null; + const thumbnailBuffer = await thumbnail?.arrayBuffer(); + const thumbnailEncrypted = thumbnailBuffer ? await encryptData(thumbnailBuffer, dataKey) : null; status.update((value) => { value.status = "upload-pending"; @@ -148,7 +147,8 @@ const encryptFile = limitFunction( nameEncrypted, createdAtEncrypted, lastModifiedAtEncrypted, - thumbnailEncrypted, + thumbnail: thumbnail && + thumbnailEncrypted && { plaintext: thumbnailBuffer, ...thumbnailEncrypted }, }; }, { concurrency: 4 }, @@ -198,7 +198,9 @@ export const uploadFile = async ( hmacSecret: HmacSecret, masterKey: MasterKey, onDuplicate: () => Promise, -): Promise<{ fileId: number; fileBuffer: ArrayBuffer } | undefined> => { +): Promise< + { fileId: number; fileBuffer: ArrayBuffer; thumbnailBuffer?: ArrayBuffer } | undefined +> => { const status = writable({ name: file.name, parentId, @@ -236,7 +238,7 @@ export const uploadFile = async ( nameEncrypted, createdAtEncrypted, lastModifiedAtEncrypted, - thumbnailEncrypted, + thumbnail, } = await encryptFile(status, file, fileBuffer, masterKey); const form = new FormData(); @@ -263,20 +265,20 @@ export const uploadFile = async ( form.set("checksum", fileEncryptedHash); let thumbnailForm = null; - if (thumbnailEncrypted) { + if (thumbnail) { thumbnailForm = new FormData(); thumbnailForm.set( "metadata", JSON.stringify({ dekVersion: dataKeyVersion.toISOString(), - contentIv: thumbnailEncrypted.iv, + contentIv: thumbnail.iv, } satisfies FileThumbnailUploadRequest), ); - thumbnailForm.set("content", new Blob([thumbnailEncrypted.ciphertext])); + thumbnailForm.set("content", new Blob([thumbnail.ciphertext])); } const { fileId } = await requestFileUpload(status, form, thumbnailForm); - return { fileId, fileBuffer }; + return { fileId, fileBuffer, thumbnailBuffer: thumbnail?.plaintext }; } catch (e) { status.update((value) => { value.status = "error"; diff --git a/src/lib/modules/thumbnail.ts b/src/lib/modules/thumbnail.ts index 30e931e..2352c65 100644 --- a/src/lib/modules/thumbnail.ts +++ b/src/lib/modules/thumbnail.ts @@ -1,3 +1,5 @@ +import { encodeToBase64 } from "$lib/modules/crypto"; + const scaleSize = (width: number, height: number, targetSize: number) => { if (width <= targetSize || height <= targetSize) { return { width, height }; @@ -74,3 +76,7 @@ export const generateVideoThumbnail = (videoUrl: string, time = 0) => { video.src = videoUrl; }); }; + +export const getThumbnailUrl = (thumbnailBuffer: ArrayBuffer) => { + return `data:image/webp;base64,${encodeToBase64(thumbnailBuffer)}`; +}; diff --git a/src/routes/(fullscreen)/settings/thumbnails/service.ts b/src/routes/(fullscreen)/settings/thumbnails/service.ts index a064078..ad24954 100644 --- a/src/routes/(fullscreen)/settings/thumbnails/service.ts +++ b/src/routes/(fullscreen)/settings/thumbnails/service.ts @@ -1,6 +1,6 @@ import { limitFunction } from "p-limit"; import { encryptData } from "$lib/modules/crypto"; -import { getFileCache, storeFileCache, downloadFile } from "$lib/modules/file"; +import { getFileCache, storeFileCache, downloadFile, storeFileThumbnail } from "$lib/modules/file"; import { generateImageThumbnail, generateVideoThumbnail } from "$lib/modules/thumbnail"; import type { FileThumbnailUploadRequest } from "$lib/server/schemas"; @@ -55,7 +55,10 @@ export const requestThumbnailUpload = limitFunction( form.set("content", new Blob([thumbnailEncrypted.ciphertext])); const res = await fetch(`/api/file/${fileId}/thumbnail/upload`, { method: "POST", body: form }); - return res.ok; + if (!res.ok) return false; + + storeFileThumbnail(fileId, thumbnail); // Intended + return true; }, - { concurrency: 1 }, + { concurrency: 4 }, ); diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte b/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte index 22870e6..4245898 100644 --- a/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte +++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte @@ -2,9 +2,10 @@ import type { Writable } from "svelte/store"; import { ActionEntryButton } from "$lib/components/atoms"; import { DirectoryEntryLabel } from "$lib/components/molecules"; + import { getFileThumbnail } from "$lib/modules/file"; import type { FileInfo } from "$lib/modules/filesystem"; import { formatDateTime } from "$lib/modules/util"; - import { getFileThumbnail } from "./service"; + import { requestFileThumbnailDownload } from "./service"; import type { SelectedEntry } from "../service.svelte"; import IconMoreVert from "~icons/material-symbols/more-vert"; @@ -17,7 +18,7 @@ let { info, onclick, onOpenMenuClick }: Props = $props(); - let thumbnail: ArrayBuffer | undefined = $state(); + let thumbnail: string | undefined = $state(); const openFile = () => { const { id, dataKey, dataKeyVersion, name } = $info!; @@ -35,12 +36,15 @@ $effect(() => { if ($info?.dataKey) { - getFileThumbnail($info.id, $info.dataKey) - .then((thumbnailData) => { - thumbnail = thumbnailData ?? undefined; + getFileThumbnail($info.id) + .then( + (thumbnailUrl) => thumbnailUrl || requestFileThumbnailDownload($info.id, $info.dataKey!), + ) + .then((thumbnailUrl) => { + thumbnail = thumbnailUrl ?? undefined; }) .catch(() => { - // TODO: Error handling + // TODO: Error Handling thumbnail = undefined; }); } else { diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts b/src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts index a14a866..70d8887 100644 --- a/src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts +++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts @@ -1,8 +1,10 @@ import { callGetApi } from "$lib/hooks"; import { decryptData } from "$lib/modules/crypto"; +import { storeFileThumbnail } from "$lib/modules/file"; +import { getThumbnailUrl } from "$lib/modules/thumbnail"; import type { FileThumbnailInfoResponse } from "$lib/server/schemas"; -export const getFileThumbnail = async (fileId: number, dataKey: CryptoKey) => { +export const requestFileThumbnailDownload = async (fileId: number, dataKey: CryptoKey) => { let res = await callGetApi(`/api/file/${fileId}/thumbnail`); if (!res.ok) return null; @@ -13,5 +15,7 @@ export const getFileThumbnail = async (fileId: number, dataKey: CryptoKey) => { const thumbnailEncrypted = await res.arrayBuffer(); const thumbnail = await decryptData(thumbnailEncrypted, thumbnailEncryptedIv, dataKey); - return thumbnail; + + storeFileThumbnail(fileId, thumbnail); // Intended + return getThumbnailUrl(thumbnail); }; diff --git a/src/routes/(main)/directory/[[id]]/service.svelte.ts b/src/routes/(main)/directory/[[id]]/service.svelte.ts index 3c5f689..d4a0556 100644 --- a/src/routes/(main)/directory/[[id]]/service.svelte.ts +++ b/src/routes/(main)/directory/[[id]]/service.svelte.ts @@ -2,7 +2,7 @@ import { getContext, setContext } from "svelte"; import { callGetApi, callPostApi } from "$lib/hooks"; import { storeHmacSecrets } from "$lib/indexedDB"; import { generateDataKey, wrapDataKey, unwrapHmacSecret, encryptString } from "$lib/modules/crypto"; -import { storeFileCache, deleteFileCache, uploadFile } from "$lib/modules/file"; +import { storeFileCache, deleteFileCache, storeFileThumbnail, uploadFile } from "$lib/modules/file"; import type { DirectoryRenameRequest, DirectoryCreateRequest, @@ -81,6 +81,10 @@ export const requestFileUpload = async ( if (!res) return false; storeFileCache(res.fileId, res.fileBuffer); // Intended + if (res.thumbnailBuffer) { + storeFileThumbnail(res.fileId, res.thumbnailBuffer); // Intended + } + return true; }; From 8975a0200dcc8516cd6b58b7a2df1a1483eaa291 Mon Sep 17 00:00:00 2001 From: static Date: Sun, 6 Jul 2025 17:38:04 +0900 Subject: [PATCH 14/38] =?UTF-8?q?=ED=8C=8C=EC=9D=BC=EC=9D=84=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=ED=95=A0=20=EA=B2=BD=EC=9A=B0=20=EC=84=9C=EB=B2=84?= =?UTF-8?q?=EC=99=80=20=ED=81=B4=EB=9D=BC=EC=9D=B4=EC=96=B8=ED=8A=B8?= =?UTF-8?q?=EC=97=90=20=EC=A0=80=EC=9E=A5=EB=90=9C=20=EC=8D=B8=EB=84=A4?= =?UTF-8?q?=EC=9D=BC=EC=9D=84=20=ED=95=A8=EA=BB=98=20=EC=82=AD=EC=A0=9C?= =?UTF-8?q?=ED=95=98=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 --- src/lib/server/db/file.ts | 40 +++++++++++++------ src/lib/server/db/media.ts | 4 +- src/lib/server/services/directory.ts | 11 ++++- src/lib/server/services/file.ts | 19 ++++----- .../(main)/directory/[[id]]/service.svelte.ts | 14 +++++-- 5 files changed, 59 insertions(+), 29 deletions(-) diff --git a/src/lib/server/db/file.ts b/src/lib/server/db/file.ts index 0a76b6d..db450c7 100644 --- a/src/lib/server/db/file.ts +++ b/src/lib/server/db/file.ts @@ -163,16 +163,24 @@ export const unregisterDirectory = async (userId: number, directoryId: number) = .setIsolationLevel("repeatable read") // TODO: Sufficient? .execute(async (trx) => { const unregisterFiles = async (parentId: number) => { - return await trx + const files = await trx + .selectFrom("file") + .leftJoin("thumbnail", "file.id", "thumbnail.file_id") + .select(["file.id", "file.path", "thumbnail.path as thumbnailPath"]) + .where("file.parent_id", "=", parentId) + .where("file.user_id", "=", userId) + .forUpdate("file") + .execute(); + await trx .deleteFrom("file") .where("parent_id", "=", parentId) .where("user_id", "=", userId) - .returning(["id", "path"]) .execute(); + return files; }; const unregisterDirectoryRecursively = async ( directoryId: number, - ): Promise<{ id: number; path: string }[]> => { + ): Promise<{ id: number; path: string; thumbnailPath: string | null }[]> => { const files = await unregisterFiles(directoryId); const subDirectories = await trx .selectFrom("directory") @@ -417,16 +425,22 @@ export const setFileEncName = async ( }; export const unregisterFile = async (userId: number, fileId: number) => { - 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 { path: file.path }; + return await db.transaction().execute(async (trx) => { + const file = await trx + .selectFrom("file") + .leftJoin("thumbnail", "file.id", "thumbnail.file_id") + .select(["file.path", "thumbnail.path as thumbnailPath"]) + .where("file.id", "=", fileId) + .where("file.user_id", "=", userId) + .forUpdate("file") + .executeTakeFirst(); + if (!file) { + throw new IntegrityError("File not found"); + } + + await trx.deleteFrom("file").where("id", "=", fileId).execute(); + return file; + }); }; export const addFileToCategory = async (fileId: number, categoryId: number) => { diff --git a/src/lib/server/db/media.ts b/src/lib/server/db/media.ts index 360ed49..8386ffc 100644 --- a/src/lib/server/db/media.ts +++ b/src/lib/server/db/media.ts @@ -37,7 +37,7 @@ export const updateFileThumbnail = async ( const thumbnail = await trx .selectFrom("thumbnail") - .select("path as old_path") + .select("path as oldPath") .where("file_id", "=", fileId) .limit(1) .forUpdate() @@ -60,7 +60,7 @@ export const updateFileThumbnail = async ( }), ) .execute(); - return thumbnail?.old_path; + return thumbnail?.oldPath ?? null; }); }; diff --git a/src/lib/server/services/directory.ts b/src/lib/server/services/directory.ts index 2525069..fdab587 100644 --- a/src/lib/server/services/directory.ts +++ b/src/lib/server/services/directory.ts @@ -34,12 +34,19 @@ export const getDirectoryInformation = async (userId: number, directoryId: Direc }; }; +const safeUnlink = async (path: string | null) => { + if (path) { + await unlink(path).catch(console.error); + } +}; + export const deleteDirectory = async (userId: number, directoryId: number) => { try { const files = await unregisterDirectory(userId, directoryId); return { - files: files.map(({ id, path }) => { - unlink(path); // Intended + files: files.map(({ id, path, thumbnailPath }) => { + safeUnlink(path); // Intended + safeUnlink(thumbnailPath); // Intended return id; }), }; diff --git a/src/lib/server/services/file.ts b/src/lib/server/services/file.ts index 6f5af03..0e20676 100644 --- a/src/lib/server/services/file.ts +++ b/src/lib/server/services/file.ts @@ -45,10 +45,17 @@ export const getFileInformation = async (userId: number, fileId: number) => { }; }; +const safeUnlink = async (path: string | null) => { + if (path) { + await unlink(path).catch(console.error); + } +}; + export const deleteFile = async (userId: number, fileId: number) => { try { - const { path } = await unregisterFile(userId, fileId); - unlink(path); // Intended + const { path, thumbnailPath } = await unregisterFile(userId, fileId); + safeUnlink(path); // Intended + safeUnlink(thumbnailPath); // Intended } catch (e) { if (e instanceof IntegrityError && e.message === "File not found") { error(404, "Invalid file id"); @@ -126,9 +133,7 @@ export const uploadFileThumbnail = async ( await pipeline(encContentStream, createWriteStream(path, { flags: "wx", mode: 0o600 })); const oldPath = await updateFileThumbnail(userId, fileId, dekVersion, path, encContentIv); - if (oldPath) { - safeUnlink(oldPath); // Intended - } + safeUnlink(oldPath); // Intended } catch (e) { await safeUnlink(path); @@ -157,10 +162,6 @@ export const scanMissingFileThumbnails = async (userId: number) => { return { files: fileIds }; }; -const safeUnlink = async (path: string) => { - await unlink(path).catch(console.error); -}; - export const uploadFile = async ( params: Omit, encContentStream: Readable, diff --git a/src/routes/(main)/directory/[[id]]/service.svelte.ts b/src/routes/(main)/directory/[[id]]/service.svelte.ts index d4a0556..b29630a 100644 --- a/src/routes/(main)/directory/[[id]]/service.svelte.ts +++ b/src/routes/(main)/directory/[[id]]/service.svelte.ts @@ -2,7 +2,13 @@ import { getContext, setContext } from "svelte"; import { callGetApi, callPostApi } from "$lib/hooks"; import { storeHmacSecrets } from "$lib/indexedDB"; import { generateDataKey, wrapDataKey, unwrapHmacSecret, encryptString } from "$lib/modules/crypto"; -import { storeFileCache, deleteFileCache, storeFileThumbnail, uploadFile } from "$lib/modules/file"; +import { + storeFileCache, + deleteFileCache, + storeFileThumbnail, + deleteFileThumbnail, + uploadFile, +} from "$lib/modules/file"; import type { DirectoryRenameRequest, DirectoryCreateRequest, @@ -114,10 +120,12 @@ export const requestEntryDeletion = async (entry: SelectedEntry) => { if (entry.type === "directory") { const { deletedFiles }: DirectoryDeleteResponse = await res.json(); - await Promise.all(deletedFiles.map(deleteFileCache)); + await Promise.all( + deletedFiles.flatMap((fileId) => [deleteFileCache(fileId), deleteFileThumbnail(fileId)]), + ); return true; } else { - await deleteFileCache(entry.id); + await Promise.all([deleteFileCache(entry.id), deleteFileThumbnail(entry.id)]); return true; } }; From bcb969dc22389267c93d54acf42560e7855040a5 Mon Sep 17 00:00:00 2001 From: static Date: Sun, 6 Jul 2025 19:55:13 +0900 Subject: [PATCH 15/38] =?UTF-8?q?heic=20=ED=8C=8C=EC=9D=BC=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=9C=20=EC=8D=B8=EB=84=A4=EC=9D=BC=20=EC=A7=80?= =?UTF-8?q?=EC=9B=90=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=ED=8E=98=EC=9D=B4=EC=A7=80=EC=97=90?= =?UTF-8?q?=EC=84=9C=EB=8F=84=20=ED=8C=8C=EC=9D=BC=EC=9D=98=20=EC=8D=B8?= =?UTF-8?q?=EB=84=A4=EC=9D=BC=EC=9D=B4=20=ED=91=9C=EC=8B=9C=EB=90=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 --- .../labels/DirectoryEntryLabel.svelte | 4 +-- .../components/organisms/Category/File.svelte | 25 +++++++++++++++++-- .../components/organisms/Category/service.ts | 2 ++ src/lib/modules/file/upload.ts | 6 ++++- src/lib/services/file.ts | 21 ++++++++++++++++ .../settings/thumbnails/service.ts | 11 +++++++- .../DirectoryEntries/UploadingFile.svelte | 4 +-- .../[[id]]/DirectoryEntries/service.ts | 22 +--------------- 8 files changed, 66 insertions(+), 29 deletions(-) create mode 100644 src/lib/services/file.ts diff --git a/src/lib/components/molecules/labels/DirectoryEntryLabel.svelte b/src/lib/components/molecules/labels/DirectoryEntryLabel.svelte index e38b348..57523b5 100644 --- a/src/lib/components/molecules/labels/DirectoryEntryLabel.svelte +++ b/src/lib/components/molecules/labels/DirectoryEntryLabel.svelte @@ -25,9 +25,9 @@ {#snippet iconSnippet()} -
+
{#if thumbnail} - {name} + {name} {:else if type === "directory"} {:else} diff --git a/src/lib/components/organisms/Category/File.svelte b/src/lib/components/organisms/Category/File.svelte index 5263b95..23465c1 100644 --- a/src/lib/components/organisms/Category/File.svelte +++ b/src/lib/components/organisms/Category/File.svelte @@ -2,8 +2,9 @@ import type { Writable } from "svelte/store"; import { ActionEntryButton } from "$lib/components/atoms"; import { DirectoryEntryLabel } from "$lib/components/molecules"; + import { getFileThumbnail } from "$lib/modules/file"; import type { FileInfo } from "$lib/modules/filesystem"; - import type { SelectedFile } from "./service"; + import { requestFileThumbnailDownload, type SelectedFile } from "./service"; import IconClose from "~icons/material-symbols/close"; @@ -15,6 +16,8 @@ let { info, onclick, onRemoveClick }: Props = $props(); + let thumbnail: string | undefined = $state(); + const openFile = () => { const { id, dataKey, dataKeyVersion, name } = $info as FileInfo; if (!dataKey || !dataKeyVersion) return; // TODO: Error handling @@ -28,6 +31,24 @@ onRemoveClick!({ id, dataKey, dataKeyVersion, name }); }; + + $effect(() => { + if ($info?.dataKey) { + getFileThumbnail($info.id) + .then( + (thumbnailUrl) => thumbnailUrl || requestFileThumbnailDownload($info.id, $info.dataKey!), + ) + .then((thumbnailUrl) => { + thumbnail = thumbnailUrl ?? undefined; + }) + .catch(() => { + // TODO: Error Handling + thumbnail = undefined; + }); + } else { + thumbnail = undefined; + } + }); {#if $info} @@ -37,6 +58,6 @@ actionButtonIcon={onRemoveClick && IconClose} onActionButtonClick={removeFile} > - + {/if} diff --git a/src/lib/components/organisms/Category/service.ts b/src/lib/components/organisms/Category/service.ts index 1d587b5..fb6e640 100644 --- a/src/lib/components/organisms/Category/service.ts +++ b/src/lib/components/organisms/Category/service.ts @@ -1,3 +1,5 @@ +export { requestFileThumbnailDownload } from "$lib/services/file"; + export interface SelectedFile { id: number; dataKey: CryptoKey; diff --git a/src/lib/modules/file/upload.ts b/src/lib/modules/file/upload.ts index b56375f..3f70b13 100644 --- a/src/lib/modules/file/upload.ts +++ b/src/lib/modules/file/upload.ts @@ -81,7 +81,11 @@ const extractExifDateTime = (fileBuffer: ArrayBuffer) => { const generateThumbnail = async (file: File, fileType: string) => { let url; try { - if (fileType.startsWith("image/")) { + if (fileType === "image/heic") { + const { default: heic2any } = await import("heic2any"); + url = URL.createObjectURL((await heic2any({ blob: file, toType: "image/png" })) as Blob); + return await generateImageThumbnail(url); + } else if (fileType.startsWith("image/")) { url = URL.createObjectURL(file); return await generateImageThumbnail(url); } else if (fileType.startsWith("video/")) { diff --git a/src/lib/services/file.ts b/src/lib/services/file.ts new file mode 100644 index 0000000..70d8887 --- /dev/null +++ b/src/lib/services/file.ts @@ -0,0 +1,21 @@ +import { callGetApi } from "$lib/hooks"; +import { decryptData } from "$lib/modules/crypto"; +import { storeFileThumbnail } from "$lib/modules/file"; +import { getThumbnailUrl } from "$lib/modules/thumbnail"; +import type { FileThumbnailInfoResponse } from "$lib/server/schemas"; + +export const requestFileThumbnailDownload = async (fileId: number, dataKey: CryptoKey) => { + let res = await callGetApi(`/api/file/${fileId}/thumbnail`); + if (!res.ok) return null; + + const { contentIv: thumbnailEncryptedIv }: FileThumbnailInfoResponse = await res.json(); + + res = await callGetApi(`/api/file/${fileId}/thumbnail/download`); + if (!res.ok) return null; + + const thumbnailEncrypted = await res.arrayBuffer(); + const thumbnail = await decryptData(thumbnailEncrypted, thumbnailEncryptedIv, dataKey); + + storeFileThumbnail(fileId, thumbnail); // Intended + return getThumbnailUrl(thumbnail); +}; diff --git a/src/routes/(fullscreen)/settings/thumbnails/service.ts b/src/routes/(fullscreen)/settings/thumbnails/service.ts index ad24954..e3c828c 100644 --- a/src/routes/(fullscreen)/settings/thumbnails/service.ts +++ b/src/routes/(fullscreen)/settings/thumbnails/service.ts @@ -21,7 +21,16 @@ export const generateThumbnail = limitFunction( async (fileBuffer: ArrayBuffer, fileType: string) => { let url; try { - if (fileType.startsWith("image/")) { + if (fileType === "image/heic") { + const { default: heic2any } = await import("heic2any"); + url = URL.createObjectURL( + (await heic2any({ + blob: new Blob([fileBuffer], { type: fileType }), + toType: "image/png", + })) as Blob, + ); + return await generateImageThumbnail(url); + } else if (fileType.startsWith("image/")) { url = URL.createObjectURL(new Blob([fileBuffer], { type: fileType })); return await generateImageThumbnail(url); } else if (fileType.startsWith("video/")) { diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/UploadingFile.svelte b/src/routes/(main)/directory/[[id]]/DirectoryEntries/UploadingFile.svelte index 7977c53..a6df05a 100644 --- a/src/routes/(main)/directory/[[id]]/DirectoryEntries/UploadingFile.svelte +++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/UploadingFile.svelte @@ -13,8 +13,8 @@ {#if isFileUploading($status.status)} -
-
+
+
diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts b/src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts index 70d8887..d4b47f8 100644 --- a/src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts +++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts @@ -1,21 +1 @@ -import { callGetApi } from "$lib/hooks"; -import { decryptData } from "$lib/modules/crypto"; -import { storeFileThumbnail } from "$lib/modules/file"; -import { getThumbnailUrl } from "$lib/modules/thumbnail"; -import type { FileThumbnailInfoResponse } from "$lib/server/schemas"; - -export const requestFileThumbnailDownload = async (fileId: number, dataKey: CryptoKey) => { - let res = await callGetApi(`/api/file/${fileId}/thumbnail`); - if (!res.ok) return null; - - const { contentIv: thumbnailEncryptedIv }: FileThumbnailInfoResponse = await res.json(); - - res = await callGetApi(`/api/file/${fileId}/thumbnail/download`); - if (!res.ok) return null; - - const thumbnailEncrypted = await res.arrayBuffer(); - const thumbnail = await decryptData(thumbnailEncrypted, thumbnailEncryptedIv, dataKey); - - storeFileThumbnail(fileId, thumbnail); // Intended - return getThumbnailUrl(thumbnail); -}; +export { requestFileThumbnailDownload } from "$lib/services/file"; From 8fefbc1bcb29daa68d3a17a68722e1a9470fb73f Mon Sep 17 00:00:00 2001 From: static Date: Sun, 6 Jul 2025 23:17:48 +0900 Subject: [PATCH 16/38] =?UTF-8?q?=EC=8D=B8=EB=84=A4=EC=9D=BC=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=99=84=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/modules/file/upload.ts | 1 - src/lib/services/file.ts | 15 +- src/routes/(fullscreen)/file/[id]/service.ts | 15 +- .../settings/thumbnails/+page.svelte | 62 ++++----- .../settings/thumbnails/File.svelte | 24 +++- .../settings/thumbnails/service.svelte.ts | 129 ++++++++++++++++++ .../settings/thumbnails/service.ts | 73 ---------- 7 files changed, 185 insertions(+), 134 deletions(-) create mode 100644 src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts delete mode 100644 src/routes/(fullscreen)/settings/thumbnails/service.ts diff --git a/src/lib/modules/file/upload.ts b/src/lib/modules/file/upload.ts index 3f70b13..e2652a1 100644 --- a/src/lib/modules/file/upload.ts +++ b/src/lib/modules/file/upload.ts @@ -94,7 +94,6 @@ const generateThumbnail = async (file: File, fileType: string) => { } return null; } catch { - // TODO: Error handling return null; } finally { if (url) { diff --git a/src/lib/services/file.ts b/src/lib/services/file.ts index 70d8887..57a0749 100644 --- a/src/lib/services/file.ts +++ b/src/lib/services/file.ts @@ -1,9 +1,22 @@ import { callGetApi } from "$lib/hooks"; import { decryptData } from "$lib/modules/crypto"; -import { storeFileThumbnail } from "$lib/modules/file"; +import { getFileCache, storeFileCache, downloadFile, storeFileThumbnail } from "$lib/modules/file"; import { getThumbnailUrl } from "$lib/modules/thumbnail"; import type { FileThumbnailInfoResponse } from "$lib/server/schemas"; +export const requestFileDownload = async ( + fileId: number, + fileEncryptedIv: string, + dataKey: CryptoKey, +) => { + const cache = await getFileCache(fileId); + if (cache) return cache; + + const fileBuffer = await downloadFile(fileId, fileEncryptedIv, dataKey); + storeFileCache(fileId, fileBuffer); // Intended + return fileBuffer; +}; + export const requestFileThumbnailDownload = async (fileId: number, dataKey: CryptoKey) => { let res = await callGetApi(`/api/file/${fileId}/thumbnail`); if (!res.ok) return null; diff --git a/src/routes/(fullscreen)/file/[id]/service.ts b/src/routes/(fullscreen)/file/[id]/service.ts index 43f0134..ccedcd1 100644 --- a/src/routes/(fullscreen)/file/[id]/service.ts +++ b/src/routes/(fullscreen)/file/[id]/service.ts @@ -1,21 +1,8 @@ import { callPostApi } from "$lib/hooks"; -import { getFileCache, storeFileCache, downloadFile } from "$lib/modules/file"; import type { CategoryFileAddRequest } from "$lib/server/schemas"; export { requestCategoryCreation, requestFileRemovalFromCategory } from "$lib/services/category"; - -export const requestFileDownload = async ( - fileId: number, - fileEncryptedIv: string, - dataKey: CryptoKey, -) => { - const cache = await getFileCache(fileId); - if (cache) return cache; - - const fileBuffer = await downloadFile(fileId, fileEncryptedIv, dataKey); - storeFileCache(fileId, fileBuffer); // Intended - return fileBuffer; -}; +export { requestFileDownload } from "$lib/services/file"; export const requestFileAdditionToCategory = async (fileId: number, categoryId: number) => { const res = await callPostApi(`/api/category/${categoryId}/file/add`, { diff --git a/src/routes/(fullscreen)/settings/thumbnails/+page.svelte b/src/routes/(fullscreen)/settings/thumbnails/+page.svelte index f57d542..5be902e 100644 --- a/src/routes/(fullscreen)/settings/thumbnails/+page.svelte +++ b/src/routes/(fullscreen)/settings/thumbnails/+page.svelte @@ -1,52 +1,35 @@ @@ -56,19 +39,20 @@ - {#if fileInfos && fileInfos.length > 0} + {#if persistentStates.files.length > 0}

- {fileInfos.length}개 파일의 썸네일이 존재하지 않아요. + {persistentStates.files.length}개 파일의 썸네일이 존재하지 않아요.

- {#each fileInfos as fileInfo} + {#each persistentStates.files as { info, status }} goto(`/file/${id}`)} - onGenerateThumbnailClick={generateThumbnail} + onGenerateThumbnailClick={requestFileThumbnailGeneration} /> {/each}
diff --git a/src/routes/(fullscreen)/settings/thumbnails/File.svelte b/src/routes/(fullscreen)/settings/thumbnails/File.svelte index d06d435..a9530e1 100644 --- a/src/routes/(fullscreen)/settings/thumbnails/File.svelte +++ b/src/routes/(fullscreen)/settings/thumbnails/File.svelte @@ -1,9 +1,20 @@ + + {#if $info} @@ -24,10 +36,10 @@ onActionButtonClick={() => onGenerateThumbnailClick($info)} actionButtonClass="text-gray-800" > - + {@const subtext = + $generationStatus && $generationStatus !== "uploaded" + ? subtexts[$generationStatus] + : formatDateTime($info.createdAt ?? $info.lastModifiedAt)} + {/if} diff --git a/src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts b/src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts new file mode 100644 index 0000000..66d3e18 --- /dev/null +++ b/src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts @@ -0,0 +1,129 @@ +import { limitFunction } from "p-limit"; +import { get, writable, type Writable } from "svelte/store"; +import { encryptData } from "$lib/modules/crypto"; +import { storeFileThumbnail } from "$lib/modules/file"; +import type { FileInfo } from "$lib/modules/filesystem"; +import { generateImageThumbnail, generateVideoThumbnail } from "$lib/modules/thumbnail"; +import type { FileThumbnailUploadRequest } from "$lib/server/schemas"; +import { requestFileDownload } from "$lib/services/file"; + +export type GenerationStatus = + | "generation-pending" + | "generating" + | "upload-pending" + | "uploading" + | "uploaded" + | "error"; + +interface File { + id: number; + info: Writable; + status?: Writable; +} + +const workingFiles = new Map>(); + +export const persistentStates = $state({ + files: [] as File[], +}); + +export const getGenerationStatus = (fileId: number): Writable | undefined => { + return workingFiles.get(fileId); +}; + +const generateThumbnail = limitFunction( + async ( + status: Writable, + fileBuffer: ArrayBuffer, + fileType: string, + dataKey: CryptoKey, + ) => { + let url, thumbnail; + status.set("generating"); + + try { + if (fileType === "image/heic") { + const { default: heic2any } = await import("heic2any"); + url = URL.createObjectURL( + (await heic2any({ + blob: new Blob([fileBuffer], { type: fileType }), + toType: "image/png", + })) as Blob, + ); + thumbnail = await generateImageThumbnail(url); + } else if (fileType.startsWith("image/")) { + url = URL.createObjectURL(new Blob([fileBuffer], { type: fileType })); + thumbnail = await generateImageThumbnail(url); + } else if (fileType.startsWith("video/")) { + url = URL.createObjectURL(new Blob([fileBuffer], { type: fileType })); + thumbnail = await generateVideoThumbnail(url); + } else { + status.set("error"); + return null; + } + + const thumbnailBuffer = await thumbnail.arrayBuffer(); + const thumbnailEncrypted = await encryptData(thumbnailBuffer, dataKey); + status.set("upload-pending"); + return { plaintext: thumbnailBuffer, ...thumbnailEncrypted }; + } catch { + status.set("error"); + return null; + } finally { + if (url) { + URL.revokeObjectURL(url); + } + } + }, + { concurrency: 4 }, +); + +const requestThumbnailUpload = limitFunction( + async ( + status: Writable, + fileId: number, + dataKeyVersion: Date, + thumbnail: { plaintext: ArrayBuffer; ciphertext: ArrayBuffer; iv: string }, + ) => { + status.set("uploading"); + + const form = new FormData(); + form.set( + "metadata", + JSON.stringify({ + dekVersion: dataKeyVersion.toISOString(), + contentIv: thumbnail.iv, + } satisfies FileThumbnailUploadRequest), + ); + form.set("content", new Blob([thumbnail.ciphertext])); + + const res = await fetch(`/api/file/${fileId}/thumbnail/upload`, { method: "POST", body: form }); + if (!res.ok) return false; + + status.set("uploaded"); + workingFiles.delete(fileId); + persistentStates.files = persistentStates.files.filter(({ id }) => id != fileId); + + storeFileThumbnail(fileId, thumbnail.plaintext); // Intended + return true; + }, + { concurrency: 4 }, +); + +export const requestFileThumbnailGeneration = async (fileInfo: FileInfo) => { + let status = workingFiles.get(fileInfo.id); + if (status && get(status) !== "error") return; + + status = writable("generation-pending"); + workingFiles.set(fileInfo.id, status); + persistentStates.files = persistentStates.files.map((file) => + file.id === fileInfo.id ? { ...file, status } : file, + ); + + // TODO: Error Handling + const file = await requestFileDownload(fileInfo.id, fileInfo.contentIv!, fileInfo.dataKey!); + const thumbnail = await generateThumbnail(status, file, fileInfo.contentType, fileInfo.dataKey!); + if (!thumbnail) return; + + await requestThumbnailUpload(status, fileInfo.id, fileInfo.dataKeyVersion!, thumbnail); +}; diff --git a/src/routes/(fullscreen)/settings/thumbnails/service.ts b/src/routes/(fullscreen)/settings/thumbnails/service.ts deleted file mode 100644 index e3c828c..0000000 --- a/src/routes/(fullscreen)/settings/thumbnails/service.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { limitFunction } from "p-limit"; -import { encryptData } from "$lib/modules/crypto"; -import { getFileCache, storeFileCache, downloadFile, storeFileThumbnail } from "$lib/modules/file"; -import { generateImageThumbnail, generateVideoThumbnail } from "$lib/modules/thumbnail"; -import type { FileThumbnailUploadRequest } from "$lib/server/schemas"; - -export const requestFileDownload = async ( - fileId: number, - fileEncryptedIv: string, - dataKey: CryptoKey, -) => { - const cache = await getFileCache(fileId); - if (cache) return cache; - - const fileBuffer = await downloadFile(fileId, fileEncryptedIv, dataKey); - storeFileCache(fileId, fileBuffer); // Intended - return fileBuffer; -}; - -export const generateThumbnail = limitFunction( - async (fileBuffer: ArrayBuffer, fileType: string) => { - let url; - try { - if (fileType === "image/heic") { - const { default: heic2any } = await import("heic2any"); - url = URL.createObjectURL( - (await heic2any({ - blob: new Blob([fileBuffer], { type: fileType }), - toType: "image/png", - })) as Blob, - ); - return await generateImageThumbnail(url); - } else if (fileType.startsWith("image/")) { - url = URL.createObjectURL(new Blob([fileBuffer], { type: fileType })); - return await generateImageThumbnail(url); - } else if (fileType.startsWith("video/")) { - url = URL.createObjectURL(new Blob([fileBuffer], { type: fileType })); - return await generateVideoThumbnail(url); - } - return null; - } catch { - // TODO: Error handling - return null; - } finally { - if (url) { - URL.revokeObjectURL(url); - } - } - }, - { concurrency: 4 }, -); - -export const requestThumbnailUpload = limitFunction( - async (fileId: number, thumbnail: ArrayBuffer, dataKey: CryptoKey, dataKeyVersion: Date) => { - const thumbnailEncrypted = await encryptData(thumbnail, dataKey); - const form = new FormData(); - form.set( - "metadata", - JSON.stringify({ - dekVersion: dataKeyVersion.toISOString(), - contentIv: thumbnailEncrypted.iv, - } satisfies FileThumbnailUploadRequest), - ); - form.set("content", new Blob([thumbnailEncrypted.ciphertext])); - - const res = await fetch(`/api/file/${fileId}/thumbnail/upload`, { method: "POST", body: form }); - if (!res.ok) return false; - - storeFileThumbnail(fileId, thumbnail); // Intended - return true; - }, - { concurrency: 4 }, -); From e4cce6b8a0d40773cea773cfc7f25b6563fbb842 Mon Sep 17 00:00:00 2001 From: static Date: Mon, 7 Jul 2025 00:30:38 +0900 Subject: [PATCH 17/38] =?UTF-8?q?OPFS=EC=97=90=20=EC=BA=90=EC=8B=9C?= =?UTF-8?q?=EB=90=9C=20=EC=8D=B8=EB=84=A4=EC=9D=BC=EC=9D=84=20=EB=AA=A8?= =?UTF-8?q?=EB=91=90=20=EC=82=AD=EC=A0=9C=ED=95=98=EB=8A=94=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../atoms/divs/FullscreenDiv.svelte | 12 +++- src/lib/modules/file/thumbnail.ts | 7 ++- src/lib/modules/opfs.ts | 36 +++++++++++ .../(fullscreen)/settings/cache/+page.svelte | 3 +- .../(fullscreen)/settings/cache/service.ts | 5 -- .../settings/thumbnails/+page.svelte | 63 +++++++++++-------- .../settings/thumbnails/File.svelte | 2 +- .../settings/thumbnails/service.svelte.ts | 2 +- 8 files changed, 91 insertions(+), 39 deletions(-) delete mode 100644 src/routes/(fullscreen)/settings/cache/service.ts diff --git a/src/lib/components/atoms/divs/FullscreenDiv.svelte b/src/lib/components/atoms/divs/FullscreenDiv.svelte index c90e02c..4bb1cc0 100644 --- a/src/lib/components/atoms/divs/FullscreenDiv.svelte +++ b/src/lib/components/atoms/divs/FullscreenDiv.svelte @@ -1,7 +1,15 @@ -
+
{@render children()}
diff --git a/src/lib/modules/file/thumbnail.ts b/src/lib/modules/file/thumbnail.ts index e78786c..6757ffc 100644 --- a/src/lib/modules/file/thumbnail.ts +++ b/src/lib/modules/file/thumbnail.ts @@ -1,5 +1,5 @@ import { LRUCache } from "lru-cache"; -import { readFile, writeFile, deleteFile } from "$lib/modules/opfs"; +import { readFile, writeFile, deleteFile, deleteDirectory } from "$lib/modules/opfs"; import { getThumbnailUrl } from "$lib/modules/thumbnail"; const loadedThumbnails = new LRUCache({ max: 100 }); @@ -27,3 +27,8 @@ export const deleteFileThumbnail = async (fileId: number) => { loadedThumbnails.delete(fileId); await deleteFile(`/thumbnails/${fileId}`); }; + +export const deleteAllFileThumbnails = async () => { + loadedThumbnails.clear(); + await deleteDirectory("/thumbnails"); +}; diff --git a/src/lib/modules/opfs.ts b/src/lib/modules/opfs.ts index 5ac70da..41f1f72 100644 --- a/src/lib/modules/opfs.ts +++ b/src/lib/modules/opfs.ts @@ -59,3 +59,39 @@ export const deleteFile = async (path: string) => { await parentHandle.removeEntry(filename); }; + +const getDirectoryHandle = async (path: string) => { + if (!rootHandle) { + throw new Error("OPFS not prepared"); + } else if (path[0] !== "/") { + throw new Error("Path must be absolute"); + } + + const parts = path.split("/"); + if (parts.length <= 1) { + throw new Error("Invalid path"); + } + + try { + let directoryHandle = rootHandle; + let parentHandle; + for (const part of parts.slice(1)) { + if (!part) continue; + parentHandle = directoryHandle; + directoryHandle = await directoryHandle.getDirectoryHandle(part); + } + return { directoryHandle, parentHandle }; + } catch (e) { + if (e instanceof DOMException && e.name === "NotFoundError") { + return {}; + } + throw e; + } +}; + +export const deleteDirectory = async (path: string) => { + const { directoryHandle, parentHandle } = await getDirectoryHandle(path); + if (!parentHandle) return; + + await parentHandle.removeEntry(directoryHandle.name, { recursive: true }); +}; diff --git a/src/routes/(fullscreen)/settings/cache/+page.svelte b/src/routes/(fullscreen)/settings/cache/+page.svelte index 262aacf..af375c2 100644 --- a/src/routes/(fullscreen)/settings/cache/+page.svelte +++ b/src/routes/(fullscreen)/settings/cache/+page.svelte @@ -4,12 +4,11 @@ import { FullscreenDiv } from "$lib/components/atoms"; import { TopBar } from "$lib/components/molecules"; import type { FileCacheIndex } from "$lib/indexedDB"; - import { getFileCacheIndex } from "$lib/modules/file"; + import { getFileCacheIndex, deleteFileCache as doDeleteFileCache } from "$lib/modules/file"; import { getFileInfo, type FileInfo } from "$lib/modules/filesystem"; import { formatFileSize } from "$lib/modules/util"; import { masterKeyStore } from "$lib/stores"; import File from "./File.svelte"; - import { deleteFileCache as doDeleteFileCache } from "./service"; interface FileCache { index: FileCacheIndex; diff --git a/src/routes/(fullscreen)/settings/cache/service.ts b/src/routes/(fullscreen)/settings/cache/service.ts deleted file mode 100644 index 35b0251..0000000 --- a/src/routes/(fullscreen)/settings/cache/service.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { deleteFileCache as doDeleteFileCache } from "$lib/modules/file"; - -export const deleteFileCache = async (fileId: number) => { - await doDeleteFileCache(fileId); -}; diff --git a/src/routes/(fullscreen)/settings/thumbnails/+page.svelte b/src/routes/(fullscreen)/settings/thumbnails/+page.svelte index 5be902e..a498f1c 100644 --- a/src/routes/(fullscreen)/settings/thumbnails/+page.svelte +++ b/src/routes/(fullscreen)/settings/thumbnails/+page.svelte @@ -3,23 +3,26 @@ import { get } from "svelte/store"; import { goto } from "$app/navigation"; import { BottomDiv, Button, FullscreenDiv } from "$lib/components/atoms"; - import { TopBar } from "$lib/components/molecules"; + import { IconEntryButton, TopBar } from "$lib/components/molecules"; + import { deleteAllFileThumbnails } from "$lib/modules/file"; import { getFileInfo } from "$lib/modules/filesystem"; import { masterKeyStore } from "$lib/stores"; import File from "./File.svelte"; import { persistentStates, getGenerationStatus, - requestFileThumbnailGeneration, + requestThumbnailGeneration, } from "./service.svelte"; + import IconDelete from "~icons/material-symbols/delete"; + let { data } = $props(); - const generateAllThumbnails = async () => { + const generateAllThumbnails = () => { persistentStates.files.forEach(({ info }) => { const fileInfo = get(info); if (fileInfo) { - requestFileThumbnailGeneration(fileInfo); + requestThumbnailGeneration(fileInfo); } }); }; @@ -38,31 +41,37 @@ - - {#if persistentStates.files.length > 0} -
-
-

- {persistentStates.files.length}개 파일의 썸네일이 존재하지 않아요. -

-
-
- {#each persistentStates.files as { info, status }} - goto(`/file/${id}`)} - onGenerateThumbnailClick={requestFileThumbnailGeneration} - /> - {/each} -
+ +
+
+ + 저장된 썸네일 모두 삭제하기 +
- + {#if persistentStates.files.length > 0} +
+

썸네일이 누락된 파일

+
+

+ {persistentStates.files.length}개 파일의 썸네일이 존재하지 않아요. +

+
+ {#each persistentStates.files as { info, status }} + goto(`/file/${id}`)} + onGenerateThumbnailClick={requestThumbnailGeneration} + /> + {/each} +
+
+
+ {/if} +
+ {#if persistentStates.files.length > 0} + - {:else} -
-

모든 파일의 썸네일이 존재해요.

-
{/if}
diff --git a/src/routes/(fullscreen)/settings/thumbnails/File.svelte b/src/routes/(fullscreen)/settings/thumbnails/File.svelte index a9530e1..8d413ec 100644 --- a/src/routes/(fullscreen)/settings/thumbnails/File.svelte +++ b/src/routes/(fullscreen)/settings/thumbnails/File.svelte @@ -32,7 +32,7 @@ onclick($info)} - actionButtonIcon={IconCamera} + actionButtonIcon={!$generationStatus || $generationStatus === "error" ? IconCamera : undefined} onActionButtonClick={() => onGenerateThumbnailClick($info)} actionButtonClass="text-gray-800" > diff --git a/src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts b/src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts index 66d3e18..a3f77ce 100644 --- a/src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts +++ b/src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts @@ -110,7 +110,7 @@ const requestThumbnailUpload = limitFunction( { concurrency: 4 }, ); -export const requestFileThumbnailGeneration = async (fileInfo: FileInfo) => { +export const requestThumbnailGeneration = async (fileInfo: FileInfo) => { let status = workingFiles.get(fileInfo.id); if (status && get(status) !== "error") return; From d3de06a7f97efda76854b8a8480d6d69484a9e2f Mon Sep 17 00:00:00 2001 From: static Date: Mon, 7 Jul 2025 17:48:55 +0900 Subject: [PATCH 18/38] =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EB=AA=A9=EB=A1=9D?= =?UTF-8?q?=EC=9D=B4=20=EB=9E=9C=EB=8D=94=EB=A7=81=EB=90=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=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 --- .../molecules/labels/DirectoryEntryLabel.svelte | 5 ++--- .../components/molecules/labels/IconLabel.svelte | 14 ++++++++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/lib/components/molecules/labels/DirectoryEntryLabel.svelte b/src/lib/components/molecules/labels/DirectoryEntryLabel.svelte index 57523b5..319e0df 100644 --- a/src/lib/components/molecules/labels/DirectoryEntryLabel.svelte +++ b/src/lib/components/molecules/labels/DirectoryEntryLabel.svelte @@ -31,7 +31,7 @@ {:else if type === "directory"} {:else} - + {/if}
{/snippet} @@ -41,8 +41,7 @@ {/snippet} | Snippet; + icon?: Component; iconClass?: ClassValue; + iconSnippet?: Snippet; subtext?: Snippet; textClass?: ClassValue; } @@ -16,15 +17,20 @@ class: className, icon: Icon, iconClass: iconClassName, + iconSnippet, subtext, textClass: textClassName, }: Props = $props();
-
- -
+ {#if iconSnippet} + {@render iconSnippet()} + {:else if Icon} +
+ +
+ {/if}

{@render children()} From 40a87aa81ff250371c7d03078a5c77157338ed98 Mon Sep 17 00:00:00 2001 From: static Date: Mon, 7 Jul 2025 18:29:04 +0900 Subject: [PATCH 19/38] =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=8D=B8=EB=84=A4?= =?UTF-8?q?=EC=9D=BC=EC=9D=B4=20=EC=BA=90=EC=8B=9C=EB=90=98=EB=8A=94=20OPF?= =?UTF-8?q?S=EC=9D=98=20=EA=B2=BD=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/organisms/Category/File.svelte | 6 +--- src/lib/modules/file/cache.ts | 34 ++++++++++++++++++- src/lib/modules/file/index.ts | 1 - src/lib/modules/file/thumbnail.ts | 34 ------------------- src/lib/services/file.ts | 13 +++++-- .../settings/thumbnails/+page.svelte | 4 +-- .../(fullscreen)/settings/thumbnails/+page.ts | 2 +- .../settings/thumbnails/service.svelte.ts | 4 +-- .../[[id]]/DirectoryEntries/File.svelte | 6 +--- .../(main)/directory/[[id]]/service.svelte.ts | 10 +++--- 10 files changed, 56 insertions(+), 58 deletions(-) delete mode 100644 src/lib/modules/file/thumbnail.ts diff --git a/src/lib/components/organisms/Category/File.svelte b/src/lib/components/organisms/Category/File.svelte index 23465c1..7d49cf3 100644 --- a/src/lib/components/organisms/Category/File.svelte +++ b/src/lib/components/organisms/Category/File.svelte @@ -2,7 +2,6 @@ import type { Writable } from "svelte/store"; import { ActionEntryButton } from "$lib/components/atoms"; import { DirectoryEntryLabel } from "$lib/components/molecules"; - import { getFileThumbnail } from "$lib/modules/file"; import type { FileInfo } from "$lib/modules/filesystem"; import { requestFileThumbnailDownload, type SelectedFile } from "./service"; @@ -34,10 +33,7 @@ $effect(() => { if ($info?.dataKey) { - getFileThumbnail($info.id) - .then( - (thumbnailUrl) => thumbnailUrl || requestFileThumbnailDownload($info.id, $info.dataKey!), - ) + requestFileThumbnailDownload($info.id, $info.dataKey) .then((thumbnailUrl) => { thumbnail = thumbnailUrl ?? undefined; }) diff --git a/src/lib/modules/file/cache.ts b/src/lib/modules/file/cache.ts index fe3c66c..31eac28 100644 --- a/src/lib/modules/file/cache.ts +++ b/src/lib/modules/file/cache.ts @@ -1,12 +1,15 @@ +import { LRUCache } from "lru-cache"; import { getFileCacheIndex as getFileCacheIndexFromIndexedDB, storeFileCacheIndex, deleteFileCacheIndex, type FileCacheIndex, } from "$lib/indexedDB"; -import { readFile, writeFile, deleteFile } from "$lib/modules/opfs"; +import { readFile, writeFile, deleteFile, deleteDirectory } from "$lib/modules/opfs"; +import { getThumbnailUrl } from "$lib/modules/thumbnail"; const fileCacheIndex = new Map(); +const loadedThumbnails = new LRUCache({ max: 100 }); export const prepareFileCache = async () => { for (const cache of await getFileCacheIndexFromIndexedDB()) { @@ -48,3 +51,32 @@ export const deleteFileCache = async (fileId: number) => { await deleteFile(`/cache/${fileId}`); await deleteFileCacheIndex(fileId); }; + +export const getFileThumbnailCache = async (fileId: number) => { + const thumbnail = loadedThumbnails.get(fileId); + if (thumbnail) { + return thumbnail; + } + + const thumbnailBuffer = await readFile(`/thumbnail/file/${fileId}`); + if (!thumbnailBuffer) return null; + + const thumbnailUrl = getThumbnailUrl(thumbnailBuffer); + loadedThumbnails.set(fileId, thumbnailUrl); + return thumbnailUrl; +}; + +export const storeFileThumbnailCache = async (fileId: number, thumbnailBuffer: ArrayBuffer) => { + await writeFile(`/thumbnail/file/${fileId}`, thumbnailBuffer); + loadedThumbnails.set(fileId, getThumbnailUrl(thumbnailBuffer)); +}; + +export const deleteFileThumbnailCache = async (fileId: number) => { + loadedThumbnails.delete(fileId); + await deleteFile(`/thumbnail/file/${fileId}`); +}; + +export const deleteAllFileThumbnailCaches = async () => { + loadedThumbnails.clear(); + await deleteDirectory("/thumbnail/file"); +}; diff --git a/src/lib/modules/file/index.ts b/src/lib/modules/file/index.ts index dc708ac..42a5613 100644 --- a/src/lib/modules/file/index.ts +++ b/src/lib/modules/file/index.ts @@ -1,4 +1,3 @@ export * from "./cache"; export * from "./download"; -export * from "./thumbnail"; export * from "./upload"; diff --git a/src/lib/modules/file/thumbnail.ts b/src/lib/modules/file/thumbnail.ts deleted file mode 100644 index 6757ffc..0000000 --- a/src/lib/modules/file/thumbnail.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { LRUCache } from "lru-cache"; -import { readFile, writeFile, deleteFile, deleteDirectory } from "$lib/modules/opfs"; -import { getThumbnailUrl } from "$lib/modules/thumbnail"; - -const loadedThumbnails = new LRUCache({ max: 100 }); - -export const getFileThumbnail = async (fileId: number) => { - const thumbnail = loadedThumbnails.get(fileId); - if (thumbnail) { - return thumbnail; - } - - const thumbnailBuffer = await readFile(`/thumbnails/${fileId}`); - if (!thumbnailBuffer) return null; - - const thumbnailUrl = getThumbnailUrl(thumbnailBuffer); - loadedThumbnails.set(fileId, thumbnailUrl); - return thumbnailUrl; -}; - -export const storeFileThumbnail = async (fileId: number, thumbnailBuffer: ArrayBuffer) => { - await writeFile(`/thumbnails/${fileId}`, thumbnailBuffer); - loadedThumbnails.set(fileId, getThumbnailUrl(thumbnailBuffer)); -}; - -export const deleteFileThumbnail = async (fileId: number) => { - loadedThumbnails.delete(fileId); - await deleteFile(`/thumbnails/${fileId}`); -}; - -export const deleteAllFileThumbnails = async () => { - loadedThumbnails.clear(); - await deleteDirectory("/thumbnails"); -}; diff --git a/src/lib/services/file.ts b/src/lib/services/file.ts index 57a0749..1a95538 100644 --- a/src/lib/services/file.ts +++ b/src/lib/services/file.ts @@ -1,6 +1,12 @@ import { callGetApi } from "$lib/hooks"; import { decryptData } from "$lib/modules/crypto"; -import { getFileCache, storeFileCache, downloadFile, storeFileThumbnail } from "$lib/modules/file"; +import { + getFileCache, + storeFileCache, + getFileThumbnailCache, + storeFileThumbnailCache, + downloadFile, +} from "$lib/modules/file"; import { getThumbnailUrl } from "$lib/modules/thumbnail"; import type { FileThumbnailInfoResponse } from "$lib/server/schemas"; @@ -18,6 +24,9 @@ export const requestFileDownload = async ( }; export const requestFileThumbnailDownload = async (fileId: number, dataKey: CryptoKey) => { + const cache = await getFileThumbnailCache(fileId); + if (cache) return cache; + let res = await callGetApi(`/api/file/${fileId}/thumbnail`); if (!res.ok) return null; @@ -29,6 +38,6 @@ export const requestFileThumbnailDownload = async (fileId: number, dataKey: Cryp const thumbnailEncrypted = await res.arrayBuffer(); const thumbnail = await decryptData(thumbnailEncrypted, thumbnailEncryptedIv, dataKey); - storeFileThumbnail(fileId, thumbnail); // Intended + storeFileThumbnailCache(fileId, thumbnail); // Intended return getThumbnailUrl(thumbnail); }; diff --git a/src/routes/(fullscreen)/settings/thumbnails/+page.svelte b/src/routes/(fullscreen)/settings/thumbnails/+page.svelte index a498f1c..68c6d6f 100644 --- a/src/routes/(fullscreen)/settings/thumbnails/+page.svelte +++ b/src/routes/(fullscreen)/settings/thumbnails/+page.svelte @@ -4,7 +4,7 @@ import { goto } from "$app/navigation"; import { BottomDiv, Button, FullscreenDiv } from "$lib/components/atoms"; import { IconEntryButton, TopBar } from "$lib/components/molecules"; - import { deleteAllFileThumbnails } from "$lib/modules/file"; + import { deleteAllFileThumbnailCaches } from "$lib/modules/file"; import { getFileInfo } from "$lib/modules/filesystem"; import { masterKeyStore } from "$lib/stores"; import File from "./File.svelte"; @@ -44,7 +44,7 @@

- + 저장된 썸네일 모두 삭제하기
diff --git a/src/routes/(fullscreen)/settings/thumbnails/+page.ts b/src/routes/(fullscreen)/settings/thumbnails/+page.ts index 3fc7cff..a16cb8e 100644 --- a/src/routes/(fullscreen)/settings/thumbnails/+page.ts +++ b/src/routes/(fullscreen)/settings/thumbnails/+page.ts @@ -1,6 +1,6 @@ import { error } from "@sveltejs/kit"; import { callPostApi } from "$lib/hooks"; -import type { MissingThumbnailFileScanResponse } from "$lib/server/schemas/file"; +import type { MissingThumbnailFileScanResponse } from "$lib/server/schemas"; import type { PageLoad } from "./$types"; export const load: PageLoad = async ({ fetch }) => { diff --git a/src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts b/src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts index a3f77ce..4e430d5 100644 --- a/src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts +++ b/src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts @@ -1,7 +1,7 @@ import { limitFunction } from "p-limit"; import { get, writable, type Writable } from "svelte/store"; import { encryptData } from "$lib/modules/crypto"; -import { storeFileThumbnail } from "$lib/modules/file"; +import { storeFileThumbnailCache } from "$lib/modules/file"; import type { FileInfo } from "$lib/modules/filesystem"; import { generateImageThumbnail, generateVideoThumbnail } from "$lib/modules/thumbnail"; import type { FileThumbnailUploadRequest } from "$lib/server/schemas"; @@ -104,7 +104,7 @@ const requestThumbnailUpload = limitFunction( workingFiles.delete(fileId); persistentStates.files = persistentStates.files.filter(({ id }) => id != fileId); - storeFileThumbnail(fileId, thumbnail.plaintext); // Intended + storeFileThumbnailCache(fileId, thumbnail.plaintext); // Intended return true; }, { concurrency: 4 }, diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte b/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte index 4245898..8251331 100644 --- a/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte +++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte @@ -2,7 +2,6 @@ import type { Writable } from "svelte/store"; import { ActionEntryButton } from "$lib/components/atoms"; import { DirectoryEntryLabel } from "$lib/components/molecules"; - import { getFileThumbnail } from "$lib/modules/file"; import type { FileInfo } from "$lib/modules/filesystem"; import { formatDateTime } from "$lib/modules/util"; import { requestFileThumbnailDownload } from "./service"; @@ -36,10 +35,7 @@ $effect(() => { if ($info?.dataKey) { - getFileThumbnail($info.id) - .then( - (thumbnailUrl) => thumbnailUrl || requestFileThumbnailDownload($info.id, $info.dataKey!), - ) + requestFileThumbnailDownload($info.id, $info.dataKey) .then((thumbnailUrl) => { thumbnail = thumbnailUrl ?? undefined; }) diff --git a/src/routes/(main)/directory/[[id]]/service.svelte.ts b/src/routes/(main)/directory/[[id]]/service.svelte.ts index b29630a..ba5fc4a 100644 --- a/src/routes/(main)/directory/[[id]]/service.svelte.ts +++ b/src/routes/(main)/directory/[[id]]/service.svelte.ts @@ -5,8 +5,8 @@ import { generateDataKey, wrapDataKey, unwrapHmacSecret, encryptString } from "$ import { storeFileCache, deleteFileCache, - storeFileThumbnail, - deleteFileThumbnail, + storeFileThumbnailCache, + deleteFileThumbnailCache, uploadFile, } from "$lib/modules/file"; import type { @@ -88,7 +88,7 @@ export const requestFileUpload = async ( storeFileCache(res.fileId, res.fileBuffer); // Intended if (res.thumbnailBuffer) { - storeFileThumbnail(res.fileId, res.thumbnailBuffer); // Intended + storeFileThumbnailCache(res.fileId, res.thumbnailBuffer); // Intended } return true; @@ -121,11 +121,11 @@ export const requestEntryDeletion = async (entry: SelectedEntry) => { if (entry.type === "directory") { const { deletedFiles }: DirectoryDeleteResponse = await res.json(); await Promise.all( - deletedFiles.flatMap((fileId) => [deleteFileCache(fileId), deleteFileThumbnail(fileId)]), + deletedFiles.flatMap((fileId) => [deleteFileCache(fileId), deleteFileThumbnailCache(fileId)]), ); return true; } else { - await Promise.all([deleteFileCache(entry.id), deleteFileThumbnail(entry.id)]); + await Promise.all([deleteFileCache(entry.id), deleteFileThumbnailCache(entry.id)]); return true; } }; From 5d9042d149ec0e27fc7be6dbfbc07a71b64c883e Mon Sep 17 00:00:00 2001 From: static Date: Mon, 7 Jul 2025 23:09:43 +0900 Subject: [PATCH 20/38] =?UTF-8?q?=EC=84=B8=EB=A1=9C=EB=A1=9C=20=EA=B8=B4?= =?UTF-8?q?=20=EC=8D=B8=EB=84=A4=EC=9D=BC=EC=9D=B4=20=EC=A0=95=EC=82=AC?= =?UTF-8?q?=EA=B0=81=ED=98=95=EC=9C=BC=EB=A1=9C=20=EC=A0=9C=EB=8C=80?= =?UTF-8?q?=EB=A1=9C=20=ED=91=9C=EC=8B=9C=EB=90=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=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/lib/components/molecules/labels/IconLabel.svelte | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/components/molecules/labels/IconLabel.svelte b/src/lib/components/molecules/labels/IconLabel.svelte index 158ddb2..c6cc8dc 100644 --- a/src/lib/components/molecules/labels/IconLabel.svelte +++ b/src/lib/components/molecules/labels/IconLabel.svelte @@ -25,7 +25,9 @@
{#if iconSnippet} - {@render iconSnippet()} +
+ {@render iconSnippet()} +
{:else if Icon}
From 9b1e27c20b042c07d0862afdbed04d30c9e83c87 Mon Sep 17 00:00:00 2001 From: static Date: Tue, 8 Jul 2025 02:07:54 +0900 Subject: [PATCH 21/38] =?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/modules/file/upload.ts | 28 +------------ src/lib/modules/thumbnail.ts | 33 ++++++++++++++- src/lib/server/db/media.ts | 2 +- src/lib/services/file.ts | 6 +-- .../settings/thumbnails/service.svelte.ts | 42 ++++--------------- 5 files changed, 46 insertions(+), 65 deletions(-) diff --git a/src/lib/modules/file/upload.ts b/src/lib/modules/file/upload.ts index e2652a1..2c84f93 100644 --- a/src/lib/modules/file/upload.ts +++ b/src/lib/modules/file/upload.ts @@ -11,7 +11,7 @@ import { digestMessage, signMessageHmac, } from "$lib/modules/crypto"; -import { generateImageThumbnail, generateVideoThumbnail } from "$lib/modules/thumbnail"; +import { generateThumbnail } from "$lib/modules/thumbnail"; import type { DuplicateFileScanRequest, DuplicateFileScanResponse, @@ -78,30 +78,6 @@ const extractExifDateTime = (fileBuffer: ArrayBuffer) => { return new Date(utcDate - offsetMs); }; -const generateThumbnail = async (file: File, fileType: string) => { - let url; - try { - if (fileType === "image/heic") { - const { default: heic2any } = await import("heic2any"); - url = URL.createObjectURL((await heic2any({ blob: file, toType: "image/png" })) as Blob); - return await generateImageThumbnail(url); - } else if (fileType.startsWith("image/")) { - url = URL.createObjectURL(file); - return await generateImageThumbnail(url); - } else if (fileType.startsWith("video/")) { - url = URL.createObjectURL(file); - return await generateVideoThumbnail(url); - } - return null; - } catch { - return null; - } finally { - if (url) { - URL.revokeObjectURL(url); - } - } -}; - const encryptFile = limitFunction( async ( status: Writable, @@ -132,7 +108,7 @@ const encryptFile = limitFunction( createdAt && (await encryptString(createdAt.getTime().toString(), dataKey)); const lastModifiedAtEncrypted = await encryptString(file.lastModified.toString(), dataKey); - const thumbnail = await generateThumbnail(file, fileType); + const thumbnail = await generateThumbnail(fileBuffer, fileType); const thumbnailBuffer = await thumbnail?.arrayBuffer(); const thumbnailEncrypted = thumbnailBuffer ? await encryptData(thumbnailBuffer, dataKey) : null; diff --git a/src/lib/modules/thumbnail.ts b/src/lib/modules/thumbnail.ts index 2352c65..1a24b5d 100644 --- a/src/lib/modules/thumbnail.ts +++ b/src/lib/modules/thumbnail.ts @@ -12,7 +12,7 @@ const scaleSize = (width: number, height: number, targetSize: number) => { }; }; -export const generateImageThumbnail = (imageUrl: string) => { +const generateImageThumbnail = (imageUrl: string) => { return new Promise((resolve, reject) => { const image = new Image(); image.onload = () => { @@ -42,7 +42,7 @@ export const generateImageThumbnail = (imageUrl: string) => { }); }; -export const generateVideoThumbnail = (videoUrl: string, time = 0) => { +const generateVideoThumbnail = (videoUrl: string, time = 0) => { return new Promise((resolve, reject) => { const video = document.createElement("video"); video.onloadeddata = () => { @@ -77,6 +77,35 @@ export const generateVideoThumbnail = (videoUrl: string, time = 0) => { }); }; +export const generateThumbnail = async (fileBuffer: ArrayBuffer, fileType: string) => { + let url; + try { + if (fileType === "image/heic") { + const { default: heic2any } = await import("heic2any"); + url = URL.createObjectURL( + (await heic2any({ + blob: new Blob([fileBuffer], { type: fileType }), + toType: "image/png", + })) as Blob, + ); + return await generateImageThumbnail(url); + } else if (fileType.startsWith("image/")) { + url = URL.createObjectURL(new Blob([fileBuffer], { type: fileType })); + return await generateImageThumbnail(url); + } else if (fileType.startsWith("video/")) { + url = URL.createObjectURL(new Blob([fileBuffer], { type: fileType })); + return await generateVideoThumbnail(url); + } + return null; + } catch { + return null; + } finally { + if (url) { + URL.revokeObjectURL(url); + } + } +}; + export const getThumbnailUrl = (thumbnailBuffer: ArrayBuffer) => { return `data:image/webp;base64,${encodeToBase64(thumbnailBuffer)}`; }; diff --git a/src/lib/server/db/media.ts b/src/lib/server/db/media.ts index 8386ffc..209e256 100644 --- a/src/lib/server/db/media.ts +++ b/src/lib/server/db/media.ts @@ -106,5 +106,5 @@ export const getMissingFileThumbnails = async (userId: number, limit: number = 1 ) .limit(limit) .execute(); - return files.map((file) => file.id); + return files.map(({ id }) => id); }; diff --git a/src/lib/services/file.ts b/src/lib/services/file.ts index 1a95538..9ee6e1d 100644 --- a/src/lib/services/file.ts +++ b/src/lib/services/file.ts @@ -36,8 +36,8 @@ export const requestFileThumbnailDownload = async (fileId: number, dataKey: Cryp if (!res.ok) return null; const thumbnailEncrypted = await res.arrayBuffer(); - const thumbnail = await decryptData(thumbnailEncrypted, thumbnailEncryptedIv, dataKey); + const thumbnailBuffer = await decryptData(thumbnailEncrypted, thumbnailEncryptedIv, dataKey); - storeFileThumbnailCache(fileId, thumbnail); // Intended - return getThumbnailUrl(thumbnail); + storeFileThumbnailCache(fileId, thumbnailBuffer); // Intended + return getThumbnailUrl(thumbnailBuffer); }; diff --git a/src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts b/src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts index 4e430d5..aaee616 100644 --- a/src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts +++ b/src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts @@ -3,7 +3,7 @@ import { get, writable, type Writable } from "svelte/store"; import { encryptData } from "$lib/modules/crypto"; import { storeFileThumbnailCache } from "$lib/modules/file"; import type { FileInfo } from "$lib/modules/filesystem"; -import { generateImageThumbnail, generateVideoThumbnail } from "$lib/modules/thumbnail"; +import { generateThumbnail as doGenerateThumbnail } from "$lib/modules/thumbnail"; import type { FileThumbnailUploadRequest } from "$lib/server/schemas"; import { requestFileDownload } from "$lib/services/file"; @@ -38,42 +38,18 @@ const generateThumbnail = limitFunction( fileType: string, dataKey: CryptoKey, ) => { - let url, thumbnail; status.set("generating"); - - try { - if (fileType === "image/heic") { - const { default: heic2any } = await import("heic2any"); - url = URL.createObjectURL( - (await heic2any({ - blob: new Blob([fileBuffer], { type: fileType }), - toType: "image/png", - })) as Blob, - ); - thumbnail = await generateImageThumbnail(url); - } else if (fileType.startsWith("image/")) { - url = URL.createObjectURL(new Blob([fileBuffer], { type: fileType })); - thumbnail = await generateImageThumbnail(url); - } else if (fileType.startsWith("video/")) { - url = URL.createObjectURL(new Blob([fileBuffer], { type: fileType })); - thumbnail = await generateVideoThumbnail(url); - } else { - status.set("error"); - return null; - } - - const thumbnailBuffer = await thumbnail.arrayBuffer(); - const thumbnailEncrypted = await encryptData(thumbnailBuffer, dataKey); - status.set("upload-pending"); - return { plaintext: thumbnailBuffer, ...thumbnailEncrypted }; - } catch { + const thumbnail = await doGenerateThumbnail(fileBuffer, fileType); + if (!thumbnail) { status.set("error"); return null; - } finally { - if (url) { - URL.revokeObjectURL(url); - } } + + const thumbnailBuffer = await thumbnail.arrayBuffer(); + const thumbnailEncrypted = await encryptData(thumbnailBuffer, dataKey); + + status.set("upload-pending"); + return { plaintext: thumbnailBuffer, ...thumbnailEncrypted }; }, { concurrency: 4 }, ); From a42ec281766c4d73a4846f61c4c749f71f983f7c Mon Sep 17 00:00:00 2001 From: static Date: Tue, 8 Jul 2025 02:26:51 +0900 Subject: [PATCH 22/38] =?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=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/modules/file/upload.ts | 5 ++--- src/routes/(fullscreen)/settings/thumbnails/+page.svelte | 4 ++-- .../(fullscreen)/settings/thumbnails/service.svelte.ts | 4 ++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/lib/modules/file/upload.ts b/src/lib/modules/file/upload.ts index 2c84f93..b5b00a1 100644 --- a/src/lib/modules/file/upload.ts +++ b/src/lib/modules/file/upload.ts @@ -110,7 +110,7 @@ const encryptFile = limitFunction( const thumbnail = await generateThumbnail(fileBuffer, fileType); const thumbnailBuffer = await thumbnail?.arrayBuffer(); - const thumbnailEncrypted = thumbnailBuffer ? await encryptData(thumbnailBuffer, dataKey) : null; + const thumbnailEncrypted = thumbnailBuffer && (await encryptData(thumbnailBuffer, dataKey)); status.update((value) => { value.status = "upload-pending"; @@ -126,8 +126,7 @@ const encryptFile = limitFunction( nameEncrypted, createdAtEncrypted, lastModifiedAtEncrypted, - thumbnail: thumbnail && - thumbnailEncrypted && { plaintext: thumbnailBuffer, ...thumbnailEncrypted }, + thumbnail: thumbnailEncrypted && { plaintext: thumbnailBuffer, ...thumbnailEncrypted }, }; }, { concurrency: 4 }, diff --git a/src/routes/(fullscreen)/settings/thumbnails/+page.svelte b/src/routes/(fullscreen)/settings/thumbnails/+page.svelte index 68c6d6f..d9cd692 100644 --- a/src/routes/(fullscreen)/settings/thumbnails/+page.svelte +++ b/src/routes/(fullscreen)/settings/thumbnails/+page.svelte @@ -43,7 +43,7 @@
-
+
저장된 썸네일 모두 삭제하기 @@ -70,7 +70,7 @@ {/if}
{#if persistentStates.files.length > 0} - + {/if} diff --git a/src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts b/src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts index aaee616..59e35f4 100644 --- a/src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts +++ b/src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts @@ -27,7 +27,7 @@ export const persistentStates = $state({ files: [] as File[], }); -export const getGenerationStatus = (fileId: number): Writable | undefined => { +export const getGenerationStatus = (fileId: number) => { return workingFiles.get(fileId); }; @@ -39,6 +39,7 @@ const generateThumbnail = limitFunction( dataKey: CryptoKey, ) => { status.set("generating"); + const thumbnail = await doGenerateThumbnail(fileBuffer, fileType); if (!thumbnail) { status.set("error"); @@ -47,7 +48,6 @@ const generateThumbnail = limitFunction( const thumbnailBuffer = await thumbnail.arrayBuffer(); const thumbnailEncrypted = await encryptData(thumbnailBuffer, dataKey); - status.set("upload-pending"); return { plaintext: thumbnailBuffer, ...thumbnailEncrypted }; }, From 2c7d085e6d0bbaec31f39c4e674222ee2dec717e Mon Sep 17 00:00:00 2001 From: static Date: Tue, 8 Jul 2025 02:34:14 +0900 Subject: [PATCH 23/38] =?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=203?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../settings/thumbnails/service.svelte.ts | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts b/src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts index 59e35f4..97c00d2 100644 --- a/src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts +++ b/src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts @@ -96,10 +96,19 @@ export const requestThumbnailGeneration = async (fileInfo: FileInfo) => { file.id === fileInfo.id ? { ...file, status } : file, ); - // TODO: Error Handling - const file = await requestFileDownload(fileInfo.id, fileInfo.contentIv!, fileInfo.dataKey!); - const thumbnail = await generateThumbnail(status, file, fileInfo.contentType, fileInfo.dataKey!); - if (!thumbnail) return; - - await requestThumbnailUpload(status, fileInfo.id, fileInfo.dataKeyVersion!, thumbnail); + try { + const file = await requestFileDownload(fileInfo.id, fileInfo.contentIv!, fileInfo.dataKey!); + const thumbnail = await generateThumbnail( + status, + file, + fileInfo.contentType, + fileInfo.dataKey!, + ); + if (!thumbnail) return; + if (!(await requestThumbnailUpload(status, fileInfo.id, fileInfo.dataKeyVersion!, thumbnail))) { + status.set("error"); + } + } catch { + status.set("error"); + } }; From 18660844e6382efe2af378faf84396488c1998c4 Mon Sep 17 00:00:00 2001 From: static Date: Tue, 8 Jul 2025 04:31:19 +0900 Subject: [PATCH 24/38] =?UTF-8?q?=EC=8D=B8=EB=84=A4=EC=9D=BC=EC=9D=84=20?= =?UTF-8?q?=EC=9D=BC=EA=B4=84=EC=A0=81=EC=9C=BC=EB=A1=9C=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=ED=95=98=EB=8A=94=20=EA=B2=BD=EC=9A=B0=20=EB=B0=9C?= =?UTF-8?q?=EC=83=9D=ED=95=98=EB=8D=98=20Out=20of=20Memory=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../settings/thumbnails/File.svelte | 1 + .../settings/thumbnails/service.svelte.ts | 66 +++++++++++++++++-- 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/src/routes/(fullscreen)/settings/thumbnails/File.svelte b/src/routes/(fullscreen)/settings/thumbnails/File.svelte index 8d413ec..8977fc7 100644 --- a/src/routes/(fullscreen)/settings/thumbnails/File.svelte +++ b/src/routes/(fullscreen)/settings/thumbnails/File.svelte @@ -1,5 +1,6 @@ - +

{title}

{@render children()}
- +
diff --git a/src/lib/server/db/session.ts b/src/lib/server/db/session.ts index a856755..d2baef7 100644 --- a/src/lib/server/db/session.ts +++ b/src/lib/server/db/session.ts @@ -46,17 +46,32 @@ export const refreshSession = async ( return { userId: session.user_id, clientId: session.client_id }; }; -export const upgradeSession = async (sessionId: string, clientId: number) => { +export const upgradeSession = async ( + userId: number, + sessionId: string, + clientId: number, + force: boolean, +) => { try { - const res = await db - .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"); - } + await db.transaction().execute(async (trx) => { + if (force) { + await trx + .deleteFrom("session") + .where("id", "!=", sessionId) + .where("user_id", "=", userId) + .where("client_id", "=", clientId) + .execute(); + } + const res = await trx + .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"); + } + }); } catch (e) { if (e instanceof pg.DatabaseError && e.code === "23505") { throw new IntegrityError("Session already exists"); diff --git a/src/lib/server/schemas/auth.ts b/src/lib/server/schemas/auth.ts index e90b209..b413f9d 100644 --- a/src/lib/server/schemas/auth.ts +++ b/src/lib/server/schemas/auth.ts @@ -4,28 +4,29 @@ export const passwordChangeRequest = z.object({ oldPassword: z.string().trim().nonempty(), newPassword: z.string().trim().nonempty(), }); -export type PasswordChangeRequest = z.infer; +export type PasswordChangeRequest = z.input; export const loginRequest = z.object({ email: z.string().email(), password: z.string().trim().nonempty(), }); -export type LoginRequest = z.infer; +export type LoginRequest = z.input; export const sessionUpgradeRequest = z.object({ encPubKey: z.string().base64().nonempty(), sigPubKey: z.string().base64().nonempty(), }); -export type SessionUpgradeRequest = z.infer; +export type SessionUpgradeRequest = z.input; export const sessionUpgradeResponse = z.object({ id: z.number().int().positive(), challenge: z.string().base64().nonempty(), }); -export type SessionUpgradeResponse = z.infer; +export type SessionUpgradeResponse = z.output; export const sessionUpgradeVerifyRequest = z.object({ id: z.number().int().positive(), answerSig: z.string().base64().nonempty(), + force: z.boolean().default(false), }); -export type SessionUpgradeVerifyRequest = z.infer; +export type SessionUpgradeVerifyRequest = z.input; diff --git a/src/lib/server/schemas/category.ts b/src/lib/server/schemas/category.ts index d02fa4f..55ae413 100644 --- a/src/lib/server/schemas/category.ts +++ b/src/lib/server/schemas/category.ts @@ -15,12 +15,12 @@ export const categoryInfoResponse = z.object({ .optional(), subCategories: z.number().int().positive().array(), }); -export type CategoryInfoResponse = z.infer; +export type CategoryInfoResponse = z.output; export const categoryFileAddRequest = z.object({ file: z.number().int().positive(), }); -export type CategoryFileAddRequest = z.infer; +export type CategoryFileAddRequest = z.input; export const categoryFileListResponse = z.object({ files: z.array( @@ -30,19 +30,19 @@ export const categoryFileListResponse = z.object({ }), ), }); -export type CategoryFileListResponse = z.infer; +export type CategoryFileListResponse = z.output; export const categoryFileRemoveRequest = z.object({ file: z.number().int().positive(), }); -export type CategoryFileRemoveRequest = z.infer; +export type CategoryFileRemoveRequest = z.input; export const categoryRenameRequest = z.object({ dekVersion: z.string().datetime(), name: z.string().base64().nonempty(), nameIv: z.string().base64().nonempty(), }); -export type CategoryRenameRequest = z.infer; +export type CategoryRenameRequest = z.input; export const categoryCreateRequest = z.object({ parent: categoryIdSchema, @@ -52,4 +52,4 @@ export const categoryCreateRequest = z.object({ name: z.string().base64().nonempty(), nameIv: z.string().base64().nonempty(), }); -export type CategoryCreateRequest = z.infer; +export type CategoryCreateRequest = z.input; diff --git a/src/lib/server/schemas/client.ts b/src/lib/server/schemas/client.ts index df15e39..08a76b7 100644 --- a/src/lib/server/schemas/client.ts +++ b/src/lib/server/schemas/client.ts @@ -8,29 +8,29 @@ export const clientListResponse = z.object({ }), ), }); -export type ClientListResponse = z.infer; +export type ClientListResponse = z.output; export const clientRegisterRequest = z.object({ encPubKey: z.string().base64().nonempty(), sigPubKey: z.string().base64().nonempty(), }); -export type ClientRegisterRequest = z.infer; +export type ClientRegisterRequest = z.input; export const clientRegisterResponse = z.object({ id: z.number().int().positive(), challenge: z.string().base64().nonempty(), }); -export type ClientRegisterResponse = z.infer; +export type ClientRegisterResponse = z.output; export const clientRegisterVerifyRequest = z.object({ id: z.number().int().positive(), answerSig: z.string().base64().nonempty(), }); -export type ClientRegisterVerifyRequest = z.infer; +export type ClientRegisterVerifyRequest = z.input; export const clientStatusResponse = z.object({ id: z.number().int().positive(), state: z.enum(["pending", "active"]), isInitialMekNeeded: z.boolean(), }); -export type ClientStatusResponse = z.infer; +export type ClientStatusResponse = z.output; diff --git a/src/lib/server/schemas/directory.ts b/src/lib/server/schemas/directory.ts index 016cfe8..ffd13bc 100644 --- a/src/lib/server/schemas/directory.ts +++ b/src/lib/server/schemas/directory.ts @@ -16,19 +16,19 @@ export const directoryInfoResponse = z.object({ subDirectories: z.number().int().positive().array(), files: z.number().int().positive().array(), }); -export type DirectoryInfoResponse = z.infer; +export type DirectoryInfoResponse = z.output; export const directoryDeleteResponse = z.object({ deletedFiles: z.number().int().positive().array(), }); -export type DirectoryDeleteResponse = z.infer; +export type DirectoryDeleteResponse = z.output; export const directoryRenameRequest = z.object({ dekVersion: z.string().datetime(), name: z.string().base64().nonempty(), nameIv: z.string().base64().nonempty(), }); -export type DirectoryRenameRequest = z.infer; +export type DirectoryRenameRequest = z.input; export const directoryCreateRequest = z.object({ parent: directoryIdSchema, @@ -38,4 +38,4 @@ export const directoryCreateRequest = z.object({ name: z.string().base64().nonempty(), nameIv: z.string().base64().nonempty(), }); -export type DirectoryCreateRequest = z.infer; +export type DirectoryCreateRequest = z.input; diff --git a/src/lib/server/schemas/file.ts b/src/lib/server/schemas/file.ts index d0687b7..07fd943 100644 --- a/src/lib/server/schemas/file.ts +++ b/src/lib/server/schemas/file.ts @@ -21,42 +21,42 @@ export const fileInfoResponse = z.object({ lastModifiedAtIv: z.string().base64().nonempty(), categories: z.number().int().positive().array(), }); -export type FileInfoResponse = z.infer; +export type FileInfoResponse = z.output; export const fileRenameRequest = z.object({ dekVersion: z.string().datetime(), name: z.string().base64().nonempty(), nameIv: z.string().base64().nonempty(), }); -export type FileRenameRequest = z.infer; +export type FileRenameRequest = z.input; export const fileThumbnailInfoResponse = z.object({ updatedAt: z.string().datetime(), contentIv: z.string().base64().nonempty(), }); -export type FileThumbnailInfoResponse = z.infer; +export type FileThumbnailInfoResponse = z.output; export const fileThumbnailUploadRequest = z.object({ dekVersion: z.string().datetime(), contentIv: z.string().base64().nonempty(), }); -export type FileThumbnailUploadRequest = z.infer; +export type FileThumbnailUploadRequest = z.input; export const duplicateFileScanRequest = z.object({ hskVersion: z.number().int().positive(), contentHmac: z.string().base64().nonempty(), }); -export type DuplicateFileScanRequest = z.infer; +export type DuplicateFileScanRequest = z.input; export const duplicateFileScanResponse = z.object({ files: z.number().int().positive().array(), }); -export type DuplicateFileScanResponse = z.infer; +export type DuplicateFileScanResponse = z.output; export const missingThumbnailFileScanResponse = z.object({ files: z.number().int().positive().array(), }); -export type MissingThumbnailFileScanResponse = z.infer; +export type MissingThumbnailFileScanResponse = z.output; export const fileUploadRequest = z.object({ parent: directoryIdSchema, @@ -78,9 +78,9 @@ export const fileUploadRequest = z.object({ lastModifiedAt: z.string().base64().nonempty(), lastModifiedAtIv: z.string().base64().nonempty(), }); -export type FileUploadRequest = z.infer; +export type FileUploadRequest = z.input; export const fileUploadResponse = z.object({ file: z.number().int().positive(), }); -export type FileUploadResponse = z.infer; +export type FileUploadResponse = z.output; diff --git a/src/lib/server/schemas/hsk.ts b/src/lib/server/schemas/hsk.ts index bcea3cd..6f6b428 100644 --- a/src/lib/server/schemas/hsk.ts +++ b/src/lib/server/schemas/hsk.ts @@ -10,10 +10,10 @@ export const hmacSecretListResponse = z.object({ }), ), }); -export type HmacSecretListResponse = z.infer; +export type HmacSecretListResponse = z.output; export const initialHmacSecretRegisterRequest = z.object({ mekVersion: z.number().int().positive(), hsk: z.string().base64().nonempty(), }); -export type InitialHmacSecretRegisterRequest = z.infer; +export type InitialHmacSecretRegisterRequest = z.input; diff --git a/src/lib/server/schemas/mek.ts b/src/lib/server/schemas/mek.ts index e79f810..3d6f468 100644 --- a/src/lib/server/schemas/mek.ts +++ b/src/lib/server/schemas/mek.ts @@ -10,10 +10,10 @@ export const masterKeyListResponse = z.object({ }), ), }); -export type MasterKeyListResponse = z.infer; +export type MasterKeyListResponse = z.output; export const initialMasterKeyRegisterRequest = z.object({ mek: z.string().base64().nonempty(), mekSig: z.string().base64().nonempty(), }); -export type InitialMasterKeyRegisterRequest = z.infer; +export type InitialMasterKeyRegisterRequest = z.input; diff --git a/src/lib/server/schemas/user.ts b/src/lib/server/schemas/user.ts index e5cc8c1..9aec819 100644 --- a/src/lib/server/schemas/user.ts +++ b/src/lib/server/schemas/user.ts @@ -4,9 +4,9 @@ export const userInfoResponse = z.object({ email: z.string().email(), nickname: z.string().nonempty(), }); -export type UserInfoResponse = z.infer; +export type UserInfoResponse = z.output; export const nicknameChangeRequest = z.object({ newNickname: z.string().trim().min(2).max(8), }); -export type NicknameChangeRequest = z.infer; +export type NicknameChangeRequest = z.input; diff --git a/src/lib/server/services/auth.ts b/src/lib/server/services/auth.ts index 96b7675..1c6867f 100644 --- a/src/lib/server/services/auth.ts +++ b/src/lib/server/services/auth.ts @@ -87,9 +87,11 @@ export const createSessionUpgradeChallenge = async ( export const verifySessionUpgradeChallenge = async ( sessionId: string, + userId: number, ip: string, challengeId: number, answerSig: string, + force: boolean, ) => { const challenge = await consumeSessionUpgradeChallenge(challengeId, sessionId, ip); if (!challenge) { @@ -106,13 +108,13 @@ export const verifySessionUpgradeChallenge = async ( } try { - await upgradeSession(sessionId, client.id); + await upgradeSession(userId, sessionId, client.id, force); } catch (e) { if (e instanceof IntegrityError) { if (e.message === "Session not found") { error(500, "Invalid challenge answer"); - } else if (e.message === "Session already exists") { - error(403, "Already logged in"); + } else if (!force && e.message === "Session already exists") { + error(409, "Already logged in"); } } throw e; diff --git a/src/lib/services/auth.ts b/src/lib/services/auth.ts index df49e30..d1975f4 100644 --- a/src/lib/services/auth.ts +++ b/src/lib/services/auth.ts @@ -11,12 +11,14 @@ export const requestSessionUpgrade = async ( decryptKey: CryptoKey, verifyKeyBase64: string, signKey: CryptoKey, + force = false, ) => { let res = await callPostApi("/api/auth/upgradeSession", { encPubKey: encryptKeyBase64, sigPubKey: verifyKeyBase64, }); - if (!res.ok) return false; + if (res.status === 403) return [false, "Unregistered client"] as const; + else if (!res.ok) return [false] as const; const { id, challenge }: SessionUpgradeResponse = await res.json(); const answer = await decryptChallenge(challenge, decryptKey); @@ -25,6 +27,13 @@ export const requestSessionUpgrade = async ( res = await callPostApi("/api/auth/upgradeSession/verify", { id, answerSig: encodeToBase64(answerSig), + force, }); + if (res.status === 409) return [false, "Already logged in"] as const; + else return [res.ok] as const; +}; + +export const requestLogout = async () => { + const res = await callPostApi("/api/auth/logout"); return res.ok; }; diff --git a/src/routes/(fullscreen)/auth/login/+page.svelte b/src/routes/(fullscreen)/auth/login/+page.svelte index ac3b1a5..00ee71e 100644 --- a/src/routes/(fullscreen)/auth/login/+page.svelte +++ b/src/routes/(fullscreen)/auth/login/+page.svelte @@ -3,10 +3,18 @@ import { BottomDiv, Button, FullscreenDiv, TextButton, TextInput } from "$lib/components/atoms"; import { TitledDiv } from "$lib/components/molecules"; import { clientKeyStore, masterKeyStore } from "$lib/stores"; - import { requestLogin, requestSessionUpgrade, requestMasterKeyDownload } from "./service"; + import ForceLoginModal from "./ForceLoginModal.svelte"; + import { + requestLogout, + requestLogin, + requestSessionUpgrade, + requestMasterKeyDownload, + } from "./service"; let { data } = $props(); + let isForceLoginModalOpen = $state(false); + let email = $state(""); let password = $state(""); @@ -14,6 +22,32 @@ return await goto(`${url}?redirect=${encodeURIComponent(data.redirectPath)}`); }; + const upgradeSession = async (force: boolean) => { + try { + const [upgradeRes, upgradeError] = await requestSessionUpgrade($clientKeyStore!, force); + if (!force && upgradeError === "Already logged in") { + isForceLoginModalOpen = true; + return; + } else if (!upgradeRes) { + throw new Error("Failed to upgrade session"); + } + + // TODO: Multi-user support + + if ( + $masterKeyStore || + (await requestMasterKeyDownload($clientKeyStore!.decryptKey, $clientKeyStore!.verifyKey)) + ) { + await goto(data.redirectPath); + } else { + await redirect("/client/pending"); + } + } catch (e) { + // TODO + throw e; + } + }; + const login = async () => { // TODO: Validation @@ -22,19 +56,7 @@ if (!$clientKeyStore) return await redirect("/key/generate"); - if (!(await requestSessionUpgrade($clientKeyStore))) - throw new Error("Failed to upgrade session"); - - // TODO: Multi-user support - - if ( - $masterKeyStore || - (await requestMasterKeyDownload($clientKeyStore.decryptKey, $clientKeyStore.verifyKey)) - ) { - await goto(data.redirectPath); - } else { - await redirect("/client/pending"); - } + await upgradeSession(false); } catch (e) { // TODO: Alert throw e; @@ -63,3 +85,9 @@ 계정이 없어요
+ + upgradeSession(true)} +/> diff --git a/src/routes/(fullscreen)/auth/login/ForceLoginModal.svelte b/src/routes/(fullscreen)/auth/login/ForceLoginModal.svelte new file mode 100644 index 0000000..fc88a4a --- /dev/null +++ b/src/routes/(fullscreen)/auth/login/ForceLoginModal.svelte @@ -0,0 +1,22 @@ + + + +

다른 디바이스에서는 로그아웃하고, 이 디바이스에서 로그인할까요?

+
diff --git a/src/routes/(fullscreen)/auth/login/service.ts b/src/routes/(fullscreen)/auth/login/service.ts index 2d267e1..43e7c0d 100644 --- a/src/routes/(fullscreen)/auth/login/service.ts +++ b/src/routes/(fullscreen)/auth/login/service.ts @@ -5,6 +5,7 @@ import { requestSessionUpgrade as requestSessionUpgradeInternal } from "$lib/ser import { requestClientRegistration } from "$lib/services/key"; import type { ClientKeys } from "$lib/stores"; +export { requestLogout } from "$lib/services/auth"; export { requestMasterKeyDownload } from "$lib/services/key"; export const requestLogin = async (email: string, password: string) => { @@ -12,26 +13,33 @@ export const requestLogin = async (email: string, password: string) => { return res.ok; }; -export const requestSessionUpgrade = async ({ - encryptKey, - decryptKey, - signKey, - verifyKey, -}: ClientKeys) => { +export const requestSessionUpgrade = async ( + { encryptKey, decryptKey, signKey, verifyKey }: ClientKeys, + force: boolean, +) => { const encryptKeyBase64 = await exportRSAKeyToBase64(encryptKey); const verifyKeyBase64 = await exportRSAKeyToBase64(verifyKey); - if (await requestSessionUpgradeInternal(encryptKeyBase64, decryptKey, verifyKeyBase64, signKey)) { - return true; + const [res, error] = await requestSessionUpgradeInternal( + encryptKeyBase64, + decryptKey, + verifyKeyBase64, + signKey, + force, + ); + if (error === undefined) return [res] as const; + + if ( + error === "Unregistered client" && + !(await requestClientRegistration(encryptKeyBase64, decryptKey, verifyKeyBase64, signKey)) + ) { + return [false] as const; + } else if (error === "Already logged in") { + return [false, force ? undefined : error] as const; } - if (await requestClientRegistration(encryptKeyBase64, decryptKey, verifyKeyBase64, signKey)) { - return await requestSessionUpgradeInternal( - encryptKeyBase64, - decryptKey, - verifyKeyBase64, - signKey, - ); - } else { - return false; - } + return [ + ( + await requestSessionUpgradeInternal(encryptKeyBase64, decryptKey, verifyKeyBase64, signKey) + )[0], + ] as const; }; diff --git a/src/routes/(fullscreen)/key/export/+page.svelte b/src/routes/(fullscreen)/key/export/+page.svelte index 3a4ae07..ac83504 100644 --- a/src/routes/(fullscreen)/key/export/+page.svelte +++ b/src/routes/(fullscreen)/key/export/+page.svelte @@ -59,12 +59,14 @@ await storeClientKeys($clientKeyStore); if ( - !(await requestSessionUpgrade( - data.encryptKeyBase64, - $clientKeyStore.decryptKey, - data.verifyKeyBase64, - $clientKeyStore.signKey, - )) + !( + await requestSessionUpgrade( + data.encryptKeyBase64, + $clientKeyStore.decryptKey, + data.verifyKeyBase64, + $clientKeyStore.signKey, + ) + )[0] ) throw new Error("Failed to upgrade session"); diff --git a/src/routes/(main)/menu/service.ts b/src/routes/(main)/menu/service.ts index 32dd4fd..ca63aa2 100644 --- a/src/routes/(main)/menu/service.ts +++ b/src/routes/(main)/menu/service.ts @@ -1,6 +1 @@ -import { callPostApi } from "$lib/hooks"; - -export const requestLogout = async () => { - const res = await callPostApi("/api/auth/logout"); - return res.ok; -}; +export { requestLogout } from "$lib/services/auth"; diff --git a/src/routes/api/auth/upgradeSession/verify/+server.ts b/src/routes/api/auth/upgradeSession/verify/+server.ts index 2fe4e36..bbaedca 100644 --- a/src/routes/api/auth/upgradeSession/verify/+server.ts +++ b/src/routes/api/auth/upgradeSession/verify/+server.ts @@ -5,12 +5,12 @@ import { verifySessionUpgradeChallenge } from "$lib/server/services/auth"; import type { RequestHandler } from "./$types"; export const POST: RequestHandler = async ({ locals, request }) => { - const { sessionId } = await authorize(locals, "notClient"); + const { sessionId, userId } = await authorize(locals, "notClient"); const zodRes = sessionUpgradeVerifyRequest.safeParse(await request.json()); if (!zodRes.success) error(400, "Invalid request body"); - const { id, answerSig } = zodRes.data; + const { id, answerSig, force } = zodRes.data; - await verifySessionUpgradeChallenge(sessionId, locals.ip, id, answerSig); + await verifySessionUpgradeChallenge(sessionId, userId, locals.ip, id, answerSig, force); return text("Session upgraded", { headers: { "Content-Type": "text/plain" } }); }; From eac81abe5aa2def4b500f3eeca07aa65fa2e0473 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 12 Jul 2025 01:28:44 +0900 Subject: [PATCH 29/38] =?UTF-8?q?=ED=82=A4=20=EA=B0=80=EC=A0=B8=EC=98=A4?= =?UTF-8?q?=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/modules/crypto/rsa.ts | 50 +++++++++++++ src/lib/modules/key.ts | 65 +++++++++++++++++ src/lib/services/key.ts | 56 ++++++++++++++- .../(fullscreen)/auth/login/+page.svelte | 7 +- src/routes/(fullscreen)/auth/login/service.ts | 40 ++--------- .../(fullscreen)/key/export/+page.svelte | 14 +--- src/routes/(fullscreen)/key/export/service.ts | 71 ++----------------- .../(fullscreen)/key/generate/+page.svelte | 68 +++++++++++++++++- .../key/generate/ForceLoginModal.svelte | 20 ++++++ .../(fullscreen)/key/generate/service.ts | 31 ++++++++ 10 files changed, 304 insertions(+), 118 deletions(-) create mode 100644 src/lib/modules/key.ts create mode 100644 src/routes/(fullscreen)/key/generate/ForceLoginModal.svelte diff --git a/src/lib/modules/crypto/rsa.ts b/src/lib/modules/crypto/rsa.ts index 4df8f9e..13dfd46 100644 --- a/src/lib/modules/crypto/rsa.ts +++ b/src/lib/modules/crypto/rsa.ts @@ -46,6 +46,56 @@ export const exportRSAKeyToBase64 = async (key: CryptoKey) => { return encodeToBase64((await exportRSAKey(key)).key); }; +export const importEncryptionKeyPairFromBase64 = async ( + encryptKeyBase64: string, + decryptKeyBase64: string, +) => { + const algorithm: RsaHashedImportParams = { + name: "RSA-OAEP", + hash: "SHA-256", + }; + const encryptKey = await window.crypto.subtle.importKey( + "spki", + decodeFromBase64(encryptKeyBase64), + algorithm, + true, + ["encrypt", "wrapKey"], + ); + const decryptKey = await window.crypto.subtle.importKey( + "pkcs8", + decodeFromBase64(decryptKeyBase64), + algorithm, + true, + ["decrypt", "unwrapKey"], + ); + return { encryptKey, decryptKey }; +}; + +export const importSigningKeyPairFromBase64 = async ( + signKeyBase64: string, + verifyKeyBase64: string, +) => { + const algorithm: RsaHashedImportParams = { + name: "RSA-PSS", + hash: "SHA-256", + }; + const signKey = await window.crypto.subtle.importKey( + "pkcs8", + decodeFromBase64(signKeyBase64), + algorithm, + true, + ["sign"], + ); + const verifyKey = await window.crypto.subtle.importKey( + "spki", + decodeFromBase64(verifyKeyBase64), + algorithm, + true, + ["verify"], + ); + return { signKey, verifyKey }; +}; + export const makeRSAKeyNonextractable = async (key: CryptoKey) => { const { key: exportedKey, format } = await exportRSAKey(key); return await window.crypto.subtle.importKey( diff --git a/src/lib/modules/key.ts b/src/lib/modules/key.ts new file mode 100644 index 0000000..945902c --- /dev/null +++ b/src/lib/modules/key.ts @@ -0,0 +1,65 @@ +import { z } from "zod"; +import { storeClientKey } from "$lib/indexedDB"; +import type { ClientKeys } from "$lib/stores"; + +const serializedClientKeysSchema = z.intersection( + z.object({ + generator: z.literal("ArkVault"), + exportedAt: z.string().datetime(), + }), + z.object({ + version: z.literal(1), + encryptKey: z.string().base64().nonempty(), + decryptKey: z.string().base64().nonempty(), + signKey: z.string().base64().nonempty(), + verifyKey: z.string().base64().nonempty(), + }), +); + +type SerializedClientKeys = z.infer; + +type DeserializedClientKeys = { + encryptKeyBase64: string; + decryptKeyBase64: string; + signKeyBase64: string; + verifyKeyBase64: string; +}; + +export const serializeClientKeys = ({ + encryptKeyBase64, + decryptKeyBase64, + signKeyBase64, + verifyKeyBase64, +}: DeserializedClientKeys) => { + return JSON.stringify({ + version: 1, + generator: "ArkVault", + exportedAt: new Date().toISOString(), + encryptKey: encryptKeyBase64, + decryptKey: decryptKeyBase64, + signKey: signKeyBase64, + verifyKey: verifyKeyBase64, + } satisfies SerializedClientKeys); +}; + +export const deserializeClientKeys = (serialized: string) => { + const zodRes = serializedClientKeysSchema.safeParse(JSON.parse(serialized)); + if (zodRes.success) { + return { + encryptKeyBase64: zodRes.data.encryptKey, + decryptKeyBase64: zodRes.data.decryptKey, + signKeyBase64: zodRes.data.signKey, + verifyKeyBase64: zodRes.data.verifyKey, + } satisfies DeserializedClientKeys; + } + return undefined; +}; + +export const storeClientKeys = async (clientKeys: ClientKeys) => { + await Promise.all([ + storeClientKey(clientKeys.encryptKey, "encrypt"), + storeClientKey(clientKeys.decryptKey, "decrypt"), + storeClientKey(clientKeys.signKey, "sign"), + storeClientKey(clientKeys.verifyKey, "verify"), + ]); +}; diff --git a/src/lib/services/key.ts b/src/lib/services/key.ts index a7e1c08..cecd241 100644 --- a/src/lib/services/key.ts +++ b/src/lib/services/key.ts @@ -2,18 +2,23 @@ import { callGetApi, callPostApi } from "$lib/hooks"; import { storeMasterKeys } from "$lib/indexedDB"; import { encodeToBase64, + exportRSAKeyToBase64, decryptChallenge, signMessageRSA, unwrapMasterKey, + signMasterKeyWrapped, verifyMasterKeyWrapped, } from "$lib/modules/crypto"; import type { ClientRegisterRequest, ClientRegisterResponse, ClientRegisterVerifyRequest, + InitialHmacSecretRegisterRequest, MasterKeyListResponse, + InitialMasterKeyRegisterRequest, } from "$lib/server/schemas"; -import { masterKeyStore } from "$lib/stores"; +import { requestSessionUpgrade } from "$lib/services/auth"; +import { masterKeyStore, type ClientKeys } from "$lib/stores"; export const requestClientRegistration = async ( encryptKeyBase64: string, @@ -38,6 +43,35 @@ export const requestClientRegistration = async ( return res.ok; }; +export const requestClientRegistrationAndSessionUpgrade = async ( + { encryptKey, decryptKey, signKey, verifyKey }: ClientKeys, + force: boolean, +) => { + const encryptKeyBase64 = await exportRSAKeyToBase64(encryptKey); + const verifyKeyBase64 = await exportRSAKeyToBase64(verifyKey); + const [res, error] = await requestSessionUpgrade( + encryptKeyBase64, + decryptKey, + verifyKeyBase64, + signKey, + force, + ); + if (error === undefined) return [res] as const; + + if ( + error === "Unregistered client" && + !(await requestClientRegistration(encryptKeyBase64, decryptKey, verifyKeyBase64, signKey)) + ) { + return [false] as const; + } else if (error === "Already logged in") { + return [false, force ? undefined : error] as const; + } + + return [ + (await requestSessionUpgrade(encryptKeyBase64, decryptKey, verifyKeyBase64, signKey))[0], + ] as const; +}; + export const requestMasterKeyDownload = async (decryptKey: CryptoKey, verifyKey: CryptoKey) => { const res = await callGetApi("/api/mek/list"); if (!res.ok) return false; @@ -68,3 +102,23 @@ export const requestMasterKeyDownload = async (decryptKey: CryptoKey, verifyKey: return true; }; + +export const requestInitialMasterKeyAndHmacSecretRegistration = async ( + masterKeyWrapped: string, + hmacSecretWrapped: string, + signKey: CryptoKey, +) => { + let res = await callPostApi("/api/mek/register/initial", { + mek: masterKeyWrapped, + mekSig: await signMasterKeyWrapped(masterKeyWrapped, 1, signKey), + }); + if (!res.ok) { + return res.status === 403 || res.status === 409; + } + + res = await callPostApi("/api/hsk/register/initial", { + mekVersion: 1, + hsk: hmacSecretWrapped, + }); + return res.ok; +}; diff --git a/src/routes/(fullscreen)/auth/login/+page.svelte b/src/routes/(fullscreen)/auth/login/+page.svelte index 00ee71e..0adfe98 100644 --- a/src/routes/(fullscreen)/auth/login/+page.svelte +++ b/src/routes/(fullscreen)/auth/login/+page.svelte @@ -7,7 +7,7 @@ import { requestLogout, requestLogin, - requestSessionUpgrade, + requestClientRegistrationAndSessionUpgrade, requestMasterKeyDownload, } from "./service"; @@ -24,7 +24,10 @@ const upgradeSession = async (force: boolean) => { try { - const [upgradeRes, upgradeError] = await requestSessionUpgrade($clientKeyStore!, force); + const [upgradeRes, upgradeError] = await requestClientRegistrationAndSessionUpgrade( + $clientKeyStore!, + force, + ); if (!force && upgradeError === "Already logged in") { isForceLoginModalOpen = true; return; diff --git a/src/routes/(fullscreen)/auth/login/service.ts b/src/routes/(fullscreen)/auth/login/service.ts index 43e7c0d..ada0f5f 100644 --- a/src/routes/(fullscreen)/auth/login/service.ts +++ b/src/routes/(fullscreen)/auth/login/service.ts @@ -1,45 +1,13 @@ import { callPostApi } from "$lib/hooks"; -import { exportRSAKeyToBase64 } from "$lib/modules/crypto"; import type { LoginRequest } from "$lib/server/schemas"; -import { requestSessionUpgrade as requestSessionUpgradeInternal } from "$lib/services/auth"; -import { requestClientRegistration } from "$lib/services/key"; -import type { ClientKeys } from "$lib/stores"; export { requestLogout } from "$lib/services/auth"; -export { requestMasterKeyDownload } from "$lib/services/key"; +export { + requestClientRegistrationAndSessionUpgrade, + requestMasterKeyDownload, +} from "$lib/services/key"; export const requestLogin = async (email: string, password: string) => { const res = await callPostApi("/api/auth/login", { email, password }); return res.ok; }; - -export const requestSessionUpgrade = async ( - { encryptKey, decryptKey, signKey, verifyKey }: ClientKeys, - force: boolean, -) => { - const encryptKeyBase64 = await exportRSAKeyToBase64(encryptKey); - const verifyKeyBase64 = await exportRSAKeyToBase64(verifyKey); - const [res, error] = await requestSessionUpgradeInternal( - encryptKeyBase64, - decryptKey, - verifyKeyBase64, - signKey, - force, - ); - if (error === undefined) return [res] as const; - - if ( - error === "Unregistered client" && - !(await requestClientRegistration(encryptKeyBase64, decryptKey, verifyKeyBase64, signKey)) - ) { - return [false] as const; - } else if (error === "Already logged in") { - return [false, force ? undefined : error] as const; - } - - return [ - ( - await requestSessionUpgradeInternal(encryptKeyBase64, decryptKey, verifyKeyBase64, signKey) - )[0], - ] as const; -}; diff --git a/src/routes/(fullscreen)/key/export/+page.svelte b/src/routes/(fullscreen)/key/export/+page.svelte index ac83504..80e8eee 100644 --- a/src/routes/(fullscreen)/key/export/+page.svelte +++ b/src/routes/(fullscreen)/key/export/+page.svelte @@ -3,13 +3,12 @@ import { goto } from "$app/navigation"; import { BottomDiv, Button, FullscreenDiv, TextButton } from "$lib/components/atoms"; import { TitledDiv } from "$lib/components/molecules"; + import { serializeClientKeys, storeClientKeys } from "$lib/modules/key"; import { clientKeyStore } from "$lib/stores"; import BeforeContinueBottomSheet from "./BeforeContinueBottomSheet.svelte"; import BeforeContinueModal from "./BeforeContinueModal.svelte"; import { - serializeClientKeys, requestClientRegistration, - storeClientKeys, requestSessionUpgrade, requestInitialMasterKeyAndHmacSecretRegistration, } from "./service"; @@ -22,15 +21,8 @@ let isBeforeContinueBottomSheetOpen = $state(false); const exportClientKeys = () => { - const clientKeysSerialized = serializeClientKeys( - data.encryptKeyBase64, - data.decryptKeyBase64, - data.signKeyBase64, - data.verifyKeyBase64, - ); - const clientKeysBlob = new Blob([JSON.stringify(clientKeysSerialized)], { - type: "application/json", - }); + const clientKeysSerialized = serializeClientKeys(data); + const clientKeysBlob = new Blob([clientKeysSerialized], { type: "application/json" }); FileSaver.saveAs(clientKeysBlob, "arkvault-clientkey.json"); if (!isBeforeContinueBottomSheetOpen) { diff --git a/src/routes/(fullscreen)/key/export/service.ts b/src/routes/(fullscreen)/key/export/service.ts index a9aaaee..ddc3dee 100644 --- a/src/routes/(fullscreen)/key/export/service.ts +++ b/src/routes/(fullscreen)/key/export/service.ts @@ -1,68 +1,5 @@ -import { callPostApi } from "$lib/hooks"; -import { storeClientKey } from "$lib/indexedDB"; -import { signMasterKeyWrapped } from "$lib/modules/crypto"; -import type { - InitialMasterKeyRegisterRequest, - InitialHmacSecretRegisterRequest, -} from "$lib/server/schemas"; -import type { ClientKeys } from "$lib/stores"; - export { requestSessionUpgrade } from "$lib/services/auth"; -export { requestClientRegistration } from "$lib/services/key"; - -type SerializedKeyPairs = { - generator: "ArkVault"; - exportedAt: Date; -} & { - version: 1; - encryptKey: string; - decryptKey: string; - signKey: string; - verifyKey: string; -}; - -export const serializeClientKeys = ( - encryptKeyBase64: string, - decryptKeyBase64: string, - signKeyBase64: string, - verifyKeyBase64: string, -) => { - return { - version: 1, - generator: "ArkVault", - exportedAt: new Date(), - encryptKey: encryptKeyBase64, - decryptKey: decryptKeyBase64, - signKey: signKeyBase64, - verifyKey: verifyKeyBase64, - } satisfies SerializedKeyPairs; -}; - -export const storeClientKeys = async (clientKeys: ClientKeys) => { - await Promise.all([ - storeClientKey(clientKeys.encryptKey, "encrypt"), - storeClientKey(clientKeys.decryptKey, "decrypt"), - storeClientKey(clientKeys.signKey, "sign"), - storeClientKey(clientKeys.verifyKey, "verify"), - ]); -}; - -export const requestInitialMasterKeyAndHmacSecretRegistration = async ( - masterKeyWrapped: string, - hmacSecretWrapped: string, - signKey: CryptoKey, -) => { - let res = await callPostApi("/api/mek/register/initial", { - mek: masterKeyWrapped, - mekSig: await signMasterKeyWrapped(masterKeyWrapped, 1, signKey), - }); - if (!res.ok) { - return res.status === 409; - } - - res = await callPostApi("/api/hsk/register/initial", { - mekVersion: 1, - hsk: hmacSecretWrapped, - }); - return res.ok; -}; +export { + requestClientRegistration, + requestInitialMasterKeyAndHmacSecretRegistration, +} from "$lib/services/key"; diff --git a/src/routes/(fullscreen)/key/generate/+page.svelte b/src/routes/(fullscreen)/key/generate/+page.svelte index 03d6c6d..141c085 100644 --- a/src/routes/(fullscreen)/key/generate/+page.svelte +++ b/src/routes/(fullscreen)/key/generate/+page.svelte @@ -4,18 +4,27 @@ import { BottomDiv, Button, FullscreenDiv, TextButton } from "$lib/components/atoms"; import { TitledDiv } from "$lib/components/molecules"; import { gotoStateful } from "$lib/hooks"; + import { storeClientKeys } from "$lib/modules/key"; import { clientKeyStore } from "$lib/stores"; + import ForceLoginModal from "./ForceLoginModal.svelte"; import Order from "./Order.svelte"; import { generateClientKeys, generateInitialMasterKey, generateInitialHmacSecret, + importClientKeys, + requestClientRegistrationAndSessionUpgrade, + requestInitialMasterKeyAndHmacSecretRegistration, } from "./service"; import IconKey from "~icons/material-symbols/key"; let { data } = $props(); + let fileInput: HTMLInputElement | undefined = $state(); + + let isForceLoginModalOpen = $state(false); + // TODO: Update const orders = [ { @@ -51,6 +60,53 @@ }); }; + const importKeys = async () => { + const file = fileInput?.files?.[0]; + if (!file) return; + + if (await importClientKeys(await file.text())) { + await upgradeSession(false); + } else { + // TODO: Error Handling + } + + fileInput!.value = ""; + }; + + const upgradeSession = async (force: boolean) => { + const [upgradeRes, upgradeError] = await requestClientRegistrationAndSessionUpgrade( + $clientKeyStore!, + force, + ); + if (!force && upgradeError === "Already logged in") { + isForceLoginModalOpen = true; + return; + } else if (!upgradeRes) { + // TODO: Error Handling + return; + } + + const { masterKey, masterKeyWrapped } = await generateInitialMasterKey( + $clientKeyStore!.encryptKey, + ); + const { hmacSecretWrapped } = await generateInitialHmacSecret(masterKey); + + await storeClientKeys($clientKeyStore!); + + if ( + !(await requestInitialMasterKeyAndHmacSecretRegistration( + masterKeyWrapped, + hmacSecretWrapped, + $clientKeyStore!.signKey, + )) + ) { + // TODO: Error Handling + return; + } + + await goto("/client/pending?redirect=" + encodeURIComponent(data.redirectPath)); + }; + onMount(async () => { if ($clientKeyStore) { await goto(data.redirectPath, { replaceState: true }); @@ -62,6 +118,14 @@ 암호 키 생성하기 + + {#snippet title()} @@ -83,6 +147,8 @@ - 키를 갖고 있어요 + fileInput?.click()}>키를 갖고 있어요 + + upgradeSession(true)} /> diff --git a/src/routes/(fullscreen)/key/generate/ForceLoginModal.svelte b/src/routes/(fullscreen)/key/generate/ForceLoginModal.svelte new file mode 100644 index 0000000..f488603 --- /dev/null +++ b/src/routes/(fullscreen)/key/generate/ForceLoginModal.svelte @@ -0,0 +1,20 @@ + + + +

다른 디바이스에서는 로그아웃하고, 이 디바이스에서 로그인할까요?

+
diff --git a/src/routes/(fullscreen)/key/generate/service.ts b/src/routes/(fullscreen)/key/generate/service.ts index f970f46..2faef15 100644 --- a/src/routes/(fullscreen)/key/generate/service.ts +++ b/src/routes/(fullscreen)/key/generate/service.ts @@ -2,6 +2,8 @@ import { generateEncryptionKeyPair, generateSigningKeyPair, exportRSAKeyToBase64, + importEncryptionKeyPairFromBase64, + importSigningKeyPairFromBase64, makeRSAKeyNonextractable, wrapMasterKey, generateMasterKey, @@ -9,8 +11,15 @@ import { wrapHmacSecret, generateHmacSecret, } from "$lib/modules/crypto"; +import { deserializeClientKeys } from "$lib/modules/key"; import { clientKeyStore } from "$lib/stores"; +export { requestLogout } from "$lib/services/auth"; +export { + requestClientRegistrationAndSessionUpgrade, + requestInitialMasterKeyAndHmacSecretRegistration, +} from "$lib/services/key"; + export const generateClientKeys = async () => { const { encryptKey, decryptKey } = await generateEncryptionKeyPair(); const { signKey, verifyKey } = await generateSigningKeyPair(); @@ -45,3 +54,25 @@ export const generateInitialHmacSecret = async (masterKey: CryptoKey) => { hmacSecretWrapped: await wrapHmacSecret(hmacSecret, masterKey), }; }; + +export const importClientKeys = async (clientKeysSerialized: string) => { + const clientKeys = deserializeClientKeys(clientKeysSerialized); + if (!clientKeys) return false; + + const { encryptKey, decryptKey } = await importEncryptionKeyPairFromBase64( + clientKeys.encryptKeyBase64, + clientKeys.decryptKeyBase64, + ); + const { signKey, verifyKey } = await importSigningKeyPairFromBase64( + clientKeys.signKeyBase64, + clientKeys.verifyKeyBase64, + ); + + clientKeyStore.set({ + encryptKey, + decryptKey: await makeRSAKeyNonextractable(decryptKey), + signKey: await makeRSAKeyNonextractable(signKey), + verifyKey, + }); + return true; +}; From fa7ba451c3c7182056535eb8d75b6687faed2fa3 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 12 Jul 2025 02:53:30 +0900 Subject: [PATCH 30/38] =?UTF-8?q?=EB=B9=84=EB=94=94=EC=98=A4=EC=9D=98=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20=EC=9B=90=ED=95=98=EB=8A=94=20=EC=9E=A5?= =?UTF-8?q?=EB=A9=B4=EC=9C=BC=EB=A1=9C=20=EC=8D=B8=EB=84=A4=EC=9D=BC?= =?UTF-8?q?=EC=9D=84=20=EB=B3=80=EA=B2=BD=ED=95=A0=20=EC=88=98=20=EC=9E=88?= =?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 --- src/lib/modules/thumbnail.ts | 44 +++++++++++-------- .../(fullscreen)/file/[id]/+page.svelte | 22 +++++++++- src/routes/(fullscreen)/file/[id]/service.ts | 30 ++++++++++++- 3 files changed, 74 insertions(+), 22 deletions(-) diff --git a/src/lib/modules/thumbnail.ts b/src/lib/modules/thumbnail.ts index 1a24b5d..873772e 100644 --- a/src/lib/modules/thumbnail.ts +++ b/src/lib/modules/thumbnail.ts @@ -42,6 +42,30 @@ const generateImageThumbnail = (imageUrl: string) => { }); }; +export const captureVideoThumbnail = (video: HTMLVideoElement) => { + return new Promise((resolve, reject) => { + const canvas = document.createElement("canvas"); + const { width, height } = scaleSize(video.videoWidth, video.videoHeight, 250); + + canvas.width = width; + canvas.height = height; + + const context = canvas.getContext("2d"); + if (!context) { + return reject(new Error("Failed to generate thumbnail")); + } + + context.drawImage(video, 0, 0, width, height); + canvas.toBlob((blob) => { + if (blob) { + resolve(blob); + } else { + reject(new Error("Failed to generate thumbnail")); + } + }, "image/webp"); + }); +}; + const generateVideoThumbnail = (videoUrl: string, time = 0) => { return new Promise((resolve, reject) => { const video = document.createElement("video"); @@ -49,25 +73,7 @@ const generateVideoThumbnail = (videoUrl: string, time = 0) => { video.currentTime = time; }; video.onseeked = () => { - const canvas = document.createElement("canvas"); - const { width, height } = scaleSize(video.videoWidth, video.videoHeight, 250); - - canvas.width = width; - canvas.height = height; - - const context = canvas.getContext("2d"); - if (!context) { - return reject(new Error("Failed to generate thumbnail")); - } - - context.drawImage(video, 0, 0, width, height); - canvas.toBlob((blob) => { - if (blob) { - resolve(blob); - } else { - reject(new Error("Failed to generate thumbnail")); - } - }, "image/webp"); + captureVideoThumbnail(video).then(resolve).catch(reject); }; video.onerror = reject; diff --git a/src/routes/(fullscreen)/file/[id]/+page.svelte b/src/routes/(fullscreen)/file/[id]/+page.svelte index 325122f..61465bd 100644 --- a/src/routes/(fullscreen)/file/[id]/+page.svelte +++ b/src/routes/(fullscreen)/file/[id]/+page.svelte @@ -11,15 +11,18 @@ type FileInfo, type CategoryInfo, } from "$lib/modules/filesystem"; + import { captureVideoThumbnail } from "$lib/modules/thumbnail"; import { fileDownloadStatusStore, isFileDownloading, masterKeyStore } from "$lib/stores"; import AddToCategoryBottomSheet from "./AddToCategoryBottomSheet.svelte"; import DownloadStatus from "./DownloadStatus.svelte"; import { requestFileRemovalFromCategory, requestFileDownload, + requestThumbnailUpload, requestFileAdditionToCategory, } from "./service"; + import IconCamera from "~icons/material-symbols/camera"; import IconClose from "~icons/material-symbols/close"; import IconAddCircle from "~icons/material-symbols/add-circle"; @@ -40,6 +43,7 @@ let isDownloadRequested = $state(false); let viewerType: "image" | "video" | undefined = $state(); let fileBlobUrl: string | undefined = $state(); + let videoElement: HTMLVideoElement | undefined = $state(); const updateViewer = async (buffer: ArrayBuffer, contentType: string) => { const fileBlob = new Blob([buffer], { type: contentType }); @@ -55,6 +59,11 @@ return fileBlob; }; + const updateThumbnail = async (dataKey: CryptoKey, dataKeyVersion: Date) => { + const thumbnail = await captureVideoThumbnail(videoElement!); + await requestThumbnailUpload(data.id, thumbnail, dataKey, dataKeyVersion); + }; + const addToCategory = async (categoryId: number) => { await requestFileAdditionToCategory(data.id, categoryId); isAddToCategoryBottomSheetOpen = false; @@ -133,8 +142,17 @@ {/if} {:else if viewerType === "video"} {#if fileBlobUrl} - - +
+ + + updateThumbnail($info.dataKey!, $info.dataKeyVersion!)} + class="w-full" + > + 이 장면을 썸네일로 설정하기 + +
{:else} {@render viewerLoading("비디오를 불러오고 있어요.")} {/if} diff --git a/src/routes/(fullscreen)/file/[id]/service.ts b/src/routes/(fullscreen)/file/[id]/service.ts index ccedcd1..83a57c6 100644 --- a/src/routes/(fullscreen)/file/[id]/service.ts +++ b/src/routes/(fullscreen)/file/[id]/service.ts @@ -1,9 +1,37 @@ import { callPostApi } from "$lib/hooks"; -import type { CategoryFileAddRequest } from "$lib/server/schemas"; +import { encryptData } from "$lib/modules/crypto"; +import { storeFileThumbnailCache } from "$lib/modules/file"; +import type { CategoryFileAddRequest, FileThumbnailUploadRequest } from "$lib/server/schemas"; export { requestCategoryCreation, requestFileRemovalFromCategory } from "$lib/services/category"; export { requestFileDownload } from "$lib/services/file"; +export const requestThumbnailUpload = async ( + fileId: number, + thumbnail: Blob, + dataKey: CryptoKey, + dataKeyVersion: Date, +) => { + const thumbnailBuffer = await thumbnail.arrayBuffer(); + const thumbnailEncrypted = await encryptData(thumbnailBuffer, dataKey); + + const form = new FormData(); + form.set( + "metadata", + JSON.stringify({ + dekVersion: dataKeyVersion.toISOString(), + contentIv: thumbnailEncrypted.iv, + } satisfies FileThumbnailUploadRequest), + ); + form.set("content", new Blob([thumbnailEncrypted.ciphertext])); + + const res = await fetch(`/api/file/${fileId}/thumbnail/upload`, { method: "POST", body: form }); + if (!res.ok) return false; + + storeFileThumbnailCache(fileId, thumbnailBuffer); // Intended + return true; +}; + export const requestFileAdditionToCategory = async (fileId: number, categoryId: number) => { const res = await callPostApi(`/api/category/${categoryId}/file/add`, { file: fileId, From eda5ff75705a2ee460054d8df9321cae541bb2d0 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 12 Jul 2025 03:27:49 +0900 Subject: [PATCH 31/38] =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=ED=95=A0=20?= =?UTF-8?q?=EB=95=8C=EB=A7=88=EB=8B=A4=20=EB=8B=A4=EB=A5=B8=20=EB=94=94?= =?UTF-8?q?=EB=B0=94=EC=9D=B4=EC=8A=A4=EC=97=90=EC=84=9C=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=EB=90=9C=20=ED=8C=8C=EC=9D=BC=EC=9D=84=20=EC=8A=A4?= =?UTF-8?q?=EC=BA=94=ED=95=98=EC=97=AC=20=ED=98=84=EC=9E=AC=20=EB=94=94?= =?UTF-8?q?=EB=B0=94=EC=9D=B4=EC=8A=A4=EC=97=90=EC=84=9C=EB=8F=84=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=ED=95=98=EB=8F=84=EB=A1=9D=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 --- src/lib/indexedDB/filesystem.ts | 4 ++++ src/lib/server/db/file.ts | 5 +++++ src/lib/server/schemas/file.ts | 5 +++++ src/lib/server/services/file.ts | 6 ++++++ src/lib/services/file.ts | 20 ++++++++++++++++++- .../(fullscreen)/auth/login/+page.svelte | 2 ++ src/routes/(fullscreen)/auth/login/service.ts | 1 + .../(fullscreen)/key/generate/+page.svelte | 2 ++ .../(fullscreen)/key/generate/service.ts | 1 + src/routes/api/file/list/+server.ts | 11 ++++++++++ 10 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 src/routes/api/file/list/+server.ts diff --git a/src/lib/indexedDB/filesystem.ts b/src/lib/indexedDB/filesystem.ts index 293c16d..1c2c060 100644 --- a/src/lib/indexedDB/filesystem.ts +++ b/src/lib/indexedDB/filesystem.ts @@ -55,6 +55,10 @@ export const deleteDirectoryInfo = async (id: number) => { await filesystem.directory.delete(id); }; +export const getAllFileInfos = async () => { + return await filesystem.file.toArray(); +}; + export const getFileInfos = async (parentId: DirectoryId) => { return await filesystem.file.where({ parentId }).toArray(); }; diff --git a/src/lib/server/db/file.ts b/src/lib/server/db/file.ts index db450c7..c3169fc 100644 --- a/src/lib/server/db/file.ts +++ b/src/lib/server/db/file.ts @@ -341,6 +341,11 @@ export const getAllFilesByCategory = async ( return files.map(({ file_id, depth }) => ({ id: file_id, isRecursive: depth > 0 })); }; +export const getAllFileIds = async (userId: number) => { + const files = await db.selectFrom("file").select("id").where("user_id", "=", userId).execute(); + return files.map(({ id }) => id); +}; + export const getAllFileIdsByContentHmac = async ( userId: number, hskVersion: number, diff --git a/src/lib/server/schemas/file.ts b/src/lib/server/schemas/file.ts index 07fd943..8b9cfe9 100644 --- a/src/lib/server/schemas/file.ts +++ b/src/lib/server/schemas/file.ts @@ -42,6 +42,11 @@ export const fileThumbnailUploadRequest = z.object({ }); export type FileThumbnailUploadRequest = z.input; +export const fileListResponse = z.object({ + files: z.number().int().positive().array(), +}); +export type FileListResponse = z.output; + export const duplicateFileScanRequest = z.object({ hskVersion: z.number().int().positive(), contentHmac: z.string().base64().nonempty(), diff --git a/src/lib/server/services/file.ts b/src/lib/server/services/file.ts index 0e20676..ab98dbf 100644 --- a/src/lib/server/services/file.ts +++ b/src/lib/server/services/file.ts @@ -9,6 +9,7 @@ import { v4 as uuidv4 } from "uuid"; import { IntegrityError } from "$lib/server/db/error"; import { registerFile, + getAllFileIds, getAllFileIdsByContentHmac, getFile, setFileEncName, @@ -148,6 +149,11 @@ export const uploadFileThumbnail = async ( } }; +export const getFileList = async (userId: number) => { + const fileIds = await getAllFileIds(userId); + return { files: fileIds }; +}; + export const scanDuplicateFiles = async ( userId: number, hskVersion: number, diff --git a/src/lib/services/file.ts b/src/lib/services/file.ts index 9ee6e1d..64d9fd4 100644 --- a/src/lib/services/file.ts +++ b/src/lib/services/file.ts @@ -1,14 +1,17 @@ import { callGetApi } from "$lib/hooks"; +import { getAllFileInfos } from "$lib/indexedDB/filesystem"; import { decryptData } from "$lib/modules/crypto"; import { getFileCache, storeFileCache, + deleteFileCache, getFileThumbnailCache, storeFileThumbnailCache, + deleteFileThumbnailCache, downloadFile, } from "$lib/modules/file"; import { getThumbnailUrl } from "$lib/modules/thumbnail"; -import type { FileThumbnailInfoResponse } from "$lib/server/schemas"; +import type { FileThumbnailInfoResponse, FileListResponse } from "$lib/server/schemas"; export const requestFileDownload = async ( fileId: number, @@ -41,3 +44,18 @@ export const requestFileThumbnailDownload = async (fileId: number, dataKey: Cryp storeFileThumbnailCache(fileId, thumbnailBuffer); // Intended return getThumbnailUrl(thumbnailBuffer); }; + +export const requestDeletedFilesCleanup = async () => { + const res = await callGetApi("/api/file/list"); + if (!res.ok) return; + + const { files: liveFiles }: FileListResponse = await res.json(); + const liveFilesSet = new Set(liveFiles); + const maybeCachedFiles = await getAllFileInfos(); + + await Promise.all( + maybeCachedFiles + .filter(({ id }) => !liveFilesSet.has(id)) + .flatMap(({ id }) => [deleteFileCache(id), deleteFileThumbnailCache(id)]), + ); +}; diff --git a/src/routes/(fullscreen)/auth/login/+page.svelte b/src/routes/(fullscreen)/auth/login/+page.svelte index 0adfe98..813b130 100644 --- a/src/routes/(fullscreen)/auth/login/+page.svelte +++ b/src/routes/(fullscreen)/auth/login/+page.svelte @@ -9,6 +9,7 @@ requestLogin, requestClientRegistrationAndSessionUpgrade, requestMasterKeyDownload, + requestDeletedFilesCleanup, } from "./service"; let { data } = $props(); @@ -41,6 +42,7 @@ $masterKeyStore || (await requestMasterKeyDownload($clientKeyStore!.decryptKey, $clientKeyStore!.verifyKey)) ) { + await requestDeletedFilesCleanup(); await goto(data.redirectPath); } else { await redirect("/client/pending"); diff --git a/src/routes/(fullscreen)/auth/login/service.ts b/src/routes/(fullscreen)/auth/login/service.ts index ada0f5f..88613f1 100644 --- a/src/routes/(fullscreen)/auth/login/service.ts +++ b/src/routes/(fullscreen)/auth/login/service.ts @@ -2,6 +2,7 @@ import { callPostApi } from "$lib/hooks"; import type { LoginRequest } from "$lib/server/schemas"; export { requestLogout } from "$lib/services/auth"; +export { requestDeletedFilesCleanup } from "$lib/services/file"; export { requestClientRegistrationAndSessionUpgrade, requestMasterKeyDownload, diff --git a/src/routes/(fullscreen)/key/generate/+page.svelte b/src/routes/(fullscreen)/key/generate/+page.svelte index 141c085..090651f 100644 --- a/src/routes/(fullscreen)/key/generate/+page.svelte +++ b/src/routes/(fullscreen)/key/generate/+page.svelte @@ -15,6 +15,7 @@ importClientKeys, requestClientRegistrationAndSessionUpgrade, requestInitialMasterKeyAndHmacSecretRegistration, + requestDeletedFilesCleanup, } from "./service"; import IconKey from "~icons/material-symbols/key"; @@ -104,6 +105,7 @@ return; } + await requestDeletedFilesCleanup(); await goto("/client/pending?redirect=" + encodeURIComponent(data.redirectPath)); }; diff --git a/src/routes/(fullscreen)/key/generate/service.ts b/src/routes/(fullscreen)/key/generate/service.ts index 2faef15..1ba4a4c 100644 --- a/src/routes/(fullscreen)/key/generate/service.ts +++ b/src/routes/(fullscreen)/key/generate/service.ts @@ -15,6 +15,7 @@ import { deserializeClientKeys } from "$lib/modules/key"; import { clientKeyStore } from "$lib/stores"; export { requestLogout } from "$lib/services/auth"; +export { requestDeletedFilesCleanup } from "$lib/services/file"; export { requestClientRegistrationAndSessionUpgrade, requestInitialMasterKeyAndHmacSecretRegistration, diff --git a/src/routes/api/file/list/+server.ts b/src/routes/api/file/list/+server.ts new file mode 100644 index 0000000..c1b6888 --- /dev/null +++ b/src/routes/api/file/list/+server.ts @@ -0,0 +1,11 @@ +import { json } from "@sveltejs/kit"; +import { authorize } from "$lib/server/modules/auth"; +import { fileListResponse, type FileListResponse } from "$lib/server/schemas"; +import { getFileList } from "$lib/server/services/file"; +import type { RequestHandler } from "./$types"; + +export const GET: RequestHandler = async ({ locals }) => { + const { userId } = await authorize(locals, "activeClient"); + const { files } = await getFileList(userId); + return json(fileListResponse.parse({ files } satisfies FileListResponse)); +}; From 381edce0c5017f0a331f78f9847a97a5284d63cf Mon Sep 17 00:00:00 2001 From: static Date: Sat, 12 Jul 2025 03:37:47 +0900 Subject: [PATCH 32/38] =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=EA=B0=80=20?= =?UTF-8?q?=EC=97=B4=EB=A6=B4=20=EB=95=8C=20=EC=98=81=EA=B5=AC=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=EC=86=8C=20=EC=82=AC=EC=9A=A9=EC=9D=84=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=ED=95=98=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 --- src/hooks.client.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/hooks.client.ts b/src/hooks.client.ts index ec6f620..99e11c9 100644 --- a/src/hooks.client.ts +++ b/src/hooks.client.ts @@ -4,6 +4,15 @@ import { prepareFileCache } from "$lib/modules/file"; import { prepareOpfs } from "$lib/modules/opfs"; import { clientKeyStore, masterKeyStore, hmacSecretStore } from "$lib/stores"; +const requestPersistentStorage = async () => { + const isPersistent = await navigator.storage.persist(); + if (isPersistent) { + console.log("[ArkVault] Persistent storage granted."); + } else { + console.warn("[ArkVault] Persistent storage not granted."); + } +}; + const prepareClientKeyStore = async () => { const [encryptKey, decryptKey, signKey, verifyKey] = await Promise.all([ getClientKey("encrypt"), @@ -32,6 +41,7 @@ const prepareHmacSecretStore = async () => { export const init: ClientInit = async () => { await Promise.all([ + requestPersistentStorage(), prepareFileCache(), prepareClientKeyStore(), prepareMasterKeyStore(), From 01732037a68678df570e2df5fe3817c4c2e97eeb Mon Sep 17 00:00:00 2001 From: static Date: Sat, 12 Jul 2025 03:40:28 +0900 Subject: [PATCH 33/38] =?UTF-8?q?package.json=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 --- docker-compose.dev.yaml | 4 ++-- docker-compose.yaml | 6 +++--- package.json | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docker-compose.dev.yaml b/docker-compose.dev.yaml index f6570a5..31493bf 100644 --- a/docker-compose.dev.yaml +++ b/docker-compose.dev.yaml @@ -1,7 +1,7 @@ services: database: - image: postgres:17.2 - restart: on-failure + image: postgres:17 + restart: always volumes: - database:/var/lib/postgresql/data environment: diff --git a/docker-compose.yaml b/docker-compose.yaml index eba1e94..57a423f 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,7 +1,7 @@ services: server: build: . - restart: on-failure + restart: unless-stopped depends_on: - database user: ${CONTAINER_UID:-0}:${CONTAINER_GID:-0} @@ -27,8 +27,8 @@ services: - ${PORT:-80}:3000 database: - image: postgres:17.2-alpine - restart: on-failure + image: postgres:17-alpine + restart: unless-stopped user: ${CONTAINER_UID:-0}:${CONTAINER_GID:-0} volumes: - ./data/database:/var/lib/postgresql/data diff --git a/package.json b/package.json index b4eb70b..7cee967 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "arkvault", "private": true, - "version": "0.4.0", + "version": "0.5.0", "type": "module", "scripts": { "dev": "vite dev", From 823ad7f59a9ac36195b5ce5b1f5f45f5eaa07c8b Mon Sep 17 00:00:00 2001 From: static Date: Sat, 12 Jul 2025 03:46:23 +0900 Subject: [PATCH 34/38] =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=20=EB=B2=84?= =?UTF-8?q?=EC=A0=84=20=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 --- package.json | 14 +-- pnpm-lock.yaml | 297 +++++++++++++++++++++++++------------------------ 2 files changed, 158 insertions(+), 153 deletions(-) diff --git a/package.json b/package.json index 7cee967..67eb017 100644 --- a/package.json +++ b/package.json @@ -18,12 +18,12 @@ "devDependencies": { "@eslint/compat": "^1.3.1", "@iconify-json/material-symbols": "^1.2.29", - "@sveltejs/adapter-node": "^5.2.12", - "@sveltejs/kit": "^2.22.2", + "@sveltejs/adapter-node": "^5.2.13", + "@sveltejs/kit": "^2.22.5", "@sveltejs/vite-plugin-svelte": "^4.0.4", "@types/file-saver": "^2.0.7", "@types/ms": "^0.7.34", - "@types/node-schedule": "^2.1.7", + "@types/node-schedule": "^2.1.8", "@types/pg": "^8.15.4", "autoprefixer": "^10.4.21", "axios": "^1.10.0", @@ -42,12 +42,12 @@ "p-limit": "^6.2.0", "prettier": "^3.6.2", "prettier-plugin-svelte": "^3.4.0", - "prettier-plugin-tailwindcss": "^0.6.13", - "svelte": "^5.35.2", + "prettier-plugin-tailwindcss": "^0.6.14", + "svelte": "^5.35.6", "svelte-check": "^4.2.2", "tailwindcss": "^3.4.17", "typescript": "^5.8.3", - "typescript-eslint": "^8.35.1", + "typescript-eslint": "^8.36.0", "unplugin-icons": "^22.1.0", "vite": "^5.4.19" }, @@ -59,7 +59,7 @@ "node-schedule": "^2.1.1", "pg": "^8.16.3", "uuid": "^11.1.0", - "zod": "^3.25.74" + "zod": "^3.25.76" }, "engines": { "node": "^22.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 79fec12..d0d9407 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,8 +30,8 @@ importers: specifier: ^11.1.0 version: 11.1.0 zod: - specifier: ^3.25.74 - version: 3.25.74 + specifier: ^3.25.76 + version: 3.25.76 devDependencies: '@eslint/compat': specifier: ^1.3.1 @@ -40,14 +40,14 @@ importers: specifier: ^1.2.29 version: 1.2.29 '@sveltejs/adapter-node': - specifier: ^5.2.12 - version: 5.2.12(@sveltejs/kit@2.22.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10)))(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10))) + specifier: ^5.2.13 + version: 5.2.13(@sveltejs/kit@2.22.5(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13)))(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13))) '@sveltejs/kit': - specifier: ^2.22.2 - version: 2.22.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10)))(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10)) + specifier: ^2.22.5 + version: 2.22.5(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13)))(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13)) '@sveltejs/vite-plugin-svelte': specifier: ^4.0.4 - version: 4.0.4(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10)) + version: 4.0.4(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13)) '@types/file-saver': specifier: ^2.0.7 version: 2.0.7 @@ -55,8 +55,8 @@ importers: specifier: ^0.7.34 version: 0.7.34 '@types/node-schedule': - specifier: ^2.1.7 - version: 2.1.7 + specifier: ^2.1.8 + version: 2.1.8 '@types/pg': specifier: ^8.15.4 version: 8.15.4 @@ -77,7 +77,7 @@ importers: version: 10.1.5(eslint@9.30.1(jiti@2.4.2)) eslint-plugin-svelte: specifier: ^3.10.1 - version: 3.10.1(eslint@9.30.1(jiti@2.4.2))(svelte@5.35.2) + version: 3.10.1(eslint@9.30.1(jiti@2.4.2))(svelte@5.35.6) eslint-plugin-tailwindcss: specifier: ^3.18.0 version: 3.18.0(tailwindcss@3.4.17) @@ -110,16 +110,16 @@ importers: version: 3.6.2 prettier-plugin-svelte: specifier: ^3.4.0 - version: 3.4.0(prettier@3.6.2)(svelte@5.35.2) + version: 3.4.0(prettier@3.6.2)(svelte@5.35.6) prettier-plugin-tailwindcss: - specifier: ^0.6.13 - version: 0.6.13(prettier-plugin-svelte@3.4.0(prettier@3.6.2)(svelte@5.35.2))(prettier@3.6.2) + specifier: ^0.6.14 + version: 0.6.14(prettier-plugin-svelte@3.4.0(prettier@3.6.2)(svelte@5.35.6))(prettier@3.6.2) svelte: - specifier: ^5.35.2 - version: 5.35.2 + specifier: ^5.35.6 + version: 5.35.6 svelte-check: specifier: ^4.2.2 - version: 4.2.2(picomatch@4.0.2)(svelte@5.35.2)(typescript@5.8.3) + version: 4.2.2(picomatch@4.0.2)(svelte@5.35.6)(typescript@5.8.3) tailwindcss: specifier: ^3.4.17 version: 3.4.17 @@ -127,14 +127,14 @@ importers: specifier: ^5.8.3 version: 5.8.3 typescript-eslint: - specifier: ^8.35.1 - version: 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + specifier: ^8.36.0 + version: 8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) unplugin-icons: specifier: ^22.1.0 - version: 22.1.0(svelte@5.35.2) + version: 22.1.0(svelte@5.35.6) vite: specifier: ^5.4.19 - version: 5.4.19(@types/node@24.0.10) + version: 5.4.19(@types/node@24.0.13) packages: @@ -554,13 +554,13 @@ packages: peerDependencies: acorn: ^8.9.0 - '@sveltejs/adapter-node@5.2.12': - resolution: {integrity: sha512-0bp4Yb3jKIEcZWVcJC/L1xXp9zzJS4hDwfb4VITAkfT4OVdkspSHsx7YhqJDbb2hgLl6R9Vs7VQR+fqIVOxPUQ==} + '@sveltejs/adapter-node@5.2.13': + resolution: {integrity: sha512-yS2TVFmIrxjGhYaV5/iIUrJ3mJl6zjaYn0lBD70vTLnYvJeqf3cjvLXeXCUCuYinhSBoyF4DpfGla49BnIy7sQ==} peerDependencies: '@sveltejs/kit': ^2.4.0 - '@sveltejs/kit@2.22.2': - resolution: {integrity: sha512-2MvEpSYabUrsJAoq5qCOBGAlkICjfjunrnLcx3YAk2XV7TvAIhomlKsAgR4H/4uns5rAfYmj7Wet5KRtc8dPIg==} + '@sveltejs/kit@2.22.5': + resolution: {integrity: sha512-l5i+LcDaoymD2mg5ziptnHmzzF79+c9twJiDoLWAPKq7afMEe4mvGesJ+LVtm33A92mLzd2KUHgtGSqTrvfkvg==} engines: {node: '>=18.13'} hasBin: true peerDependencies: @@ -598,11 +598,11 @@ packages: '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} - '@types/node-schedule@2.1.7': - resolution: {integrity: sha512-G7Z3R9H7r3TowoH6D2pkzUHPhcJrDF4Jz1JOQ80AX0K2DWTHoN9VC94XzFAPNMdbW9TBzMZ3LjpFi7RYdbxtXA==} + '@types/node-schedule@2.1.8': + resolution: {integrity: sha512-k00g6Yj/oUg/CDC+MeLHUzu0+OFxWbIqrFfDiLi6OPKxTujvpv29mHGM8GtKr7B+9Vv92FcK/8mRqi1DK5f3hA==} - '@types/node@24.0.10': - resolution: {integrity: sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==} + '@types/node@24.0.13': + resolution: {integrity: sha512-Qm9OYVOFHFYg3wJoTSrz80hoec5Lia/dPp84do3X7dZvLikQvM1YpmvTBEdIr/e+U8HTkFjLHLnl78K/qjf+jQ==} '@types/pg@8.15.4': resolution: {integrity: sha512-I6UNVBAoYbvuWkkU3oosC8yxqH21f4/Jc4DK71JLG3dT2mdlGe1z+ep/LQGXaKaOgcvUrsQoPRqfgtMcvZiJhg==} @@ -610,63 +610,63 @@ packages: '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} - '@typescript-eslint/eslint-plugin@8.35.1': - resolution: {integrity: sha512-9XNTlo7P7RJxbVeICaIIIEipqxLKguyh+3UbXuT2XQuFp6d8VOeDEGuz5IiX0dgZo8CiI6aOFLg4e8cF71SFVg==} + '@typescript-eslint/eslint-plugin@8.36.0': + resolution: {integrity: sha512-lZNihHUVB6ZZiPBNgOQGSxUASI7UJWhT8nHyUGCnaQ28XFCw98IfrMCG3rUl1uwUWoAvodJQby2KTs79UTcrAg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.35.1 + '@typescript-eslint/parser': ^8.36.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/parser@8.35.1': - resolution: {integrity: sha512-3MyiDfrfLeK06bi/g9DqJxP5pV74LNv4rFTyvGDmT3x2p1yp1lOd+qYZfiRPIOf/oON+WRZR5wxxuF85qOar+w==} + '@typescript-eslint/parser@8.36.0': + resolution: {integrity: sha512-FuYgkHwZLuPbZjQHzJXrtXreJdFMKl16BFYyRrLxDhWr6Qr7Kbcu2s1Yhu8tsiMXw1S0W1pjfFfYEt+R604s+Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/project-service@8.35.1': - resolution: {integrity: sha512-VYxn/5LOpVxADAuP3NrnxxHYfzVtQzLKeldIhDhzC8UHaiQvYlXvKuVho1qLduFbJjjy5U5bkGwa3rUGUb1Q6Q==} + '@typescript-eslint/project-service@8.36.0': + resolution: {integrity: sha512-JAhQFIABkWccQYeLMrHadu/fhpzmSQ1F1KXkpzqiVxA/iYI6UnRt2trqXHt1sYEcw1mxLnB9rKMsOxXPxowN/g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/scope-manager@8.35.1': - resolution: {integrity: sha512-s/Bpd4i7ht2934nG+UoSPlYXd08KYz3bmjLEb7Ye1UVob0d1ENiT3lY8bsCmik4RqfSbPw9xJJHbugpPpP5JUg==} + '@typescript-eslint/scope-manager@8.36.0': + resolution: {integrity: sha512-wCnapIKnDkN62fYtTGv2+RY8FlnBYA3tNm0fm91kc2BjPhV2vIjwwozJ7LToaLAyb1ca8BxrS7vT+Pvvf7RvqA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.35.1': - resolution: {integrity: sha512-K5/U9VmT9dTHoNowWZpz+/TObS3xqC5h0xAIjXPw+MNcKV9qg6eSatEnmeAwkjHijhACH0/N7bkhKvbt1+DXWQ==} + '@typescript-eslint/tsconfig-utils@8.36.0': + resolution: {integrity: sha512-Nhh3TIEgN18mNbdXpd5Q8mSCBnrZQeY9V7Ca3dqYvNDStNIGRmJA6dmrIPMJ0kow3C7gcQbpsG2rPzy1Ks/AnA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/type-utils@8.35.1': - resolution: {integrity: sha512-HOrUBlfVRz5W2LIKpXzZoy6VTZzMu2n8q9C2V/cFngIC5U1nStJgv0tMV4sZPzdf4wQm9/ToWUFPMN9Vq9VJQQ==} + '@typescript-eslint/type-utils@8.36.0': + resolution: {integrity: sha512-5aaGYG8cVDd6cxfk/ynpYzxBRZJk7w/ymto6uiyUFtdCozQIsQWh7M28/6r57Fwkbweng8qAzoMCPwSJfWlmsg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/types@8.35.1': - resolution: {integrity: sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ==} + '@typescript-eslint/types@8.36.0': + resolution: {integrity: sha512-xGms6l5cTJKQPZOKM75Dl9yBfNdGeLRsIyufewnxT4vZTrjC0ImQT4fj8QmtJK84F58uSh5HVBSANwcfiXxABQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.35.1': - resolution: {integrity: sha512-Vvpuvj4tBxIka7cPs6Y1uvM7gJgdF5Uu9F+mBJBPY4MhvjrjWGK4H0lVgLJd/8PWZ23FTqsaJaLEkBCFUk8Y9g==} + '@typescript-eslint/typescript-estree@8.36.0': + resolution: {integrity: sha512-JaS8bDVrfVJX4av0jLpe4ye0BpAaUW7+tnS4Y4ETa3q7NoZgzYbN9zDQTJ8kPb5fQ4n0hliAt9tA4Pfs2zA2Hg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/utils@8.35.1': - resolution: {integrity: sha512-lhnwatFmOFcazAsUm3ZnZFpXSxiwoa1Lj50HphnDe1Et01NF4+hrdXONSUHIcbVu2eFb1bAf+5yjXkGVkXBKAQ==} + '@typescript-eslint/utils@8.36.0': + resolution: {integrity: sha512-VOqmHu42aEMT+P2qYjylw6zP/3E/HvptRwdn/PZxyV27KhZg2IOszXod4NcXisWzPAGSS4trE/g4moNj6XmH2g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/visitor-keys@8.35.1': - resolution: {integrity: sha512-VRwixir4zBWCSTP/ljEo091lbpypz57PoeAQ9imjG+vbeof9LplljsL1mos4ccG6H9IjfrVGM359RozUnuFhpw==} + '@typescript-eslint/visitor-keys@8.36.0': + resolution: {integrity: sha512-vZrhV2lRPWDuGoxcmrzRZyxAggPL+qp3WzUrlZD+slFueDiYHxeBa34dUXPuC0RmGKzl4lS5kFJYvKCq9cnNDA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@xmldom/xmldom@0.9.8': @@ -782,8 +782,8 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} - caniuse-lite@1.0.30001726: - resolution: {integrity: sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==} + caniuse-lite@1.0.30001727: + resolution: {integrity: sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==} chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} @@ -901,8 +901,8 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - electron-to-chromium@1.5.179: - resolution: {integrity: sha512-UWKi/EbBopgfFsc5k61wFpV7WrnnSlSzW/e2XcBmS6qKYTivZlLtoll5/rdqRTxGglGHkmkW0j0pFNJG10EUIQ==} + electron-to-chromium@1.5.182: + resolution: {integrity: sha512-Lv65Btwv9W4J9pyODI6EWpdnhfvrve/us5h1WspW8B2Fb0366REPtY3hX7ounk1CkV/TBjWCEvCBBbYbmV0qCA==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1297,8 +1297,8 @@ packages: resolution: {integrity: sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==} engines: {node: 20 || >=22} - luxon@3.6.1: - resolution: {integrity: sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==} + luxon@3.7.1: + resolution: {integrity: sha512-RkRWjA926cTvz5rAb1BqyWkKbbjzCGchDUIKMCUvNi17j6f6j8uHGDV82Aqcqtzd+icoYpELmG3ksgGiFNNcNg==} engines: {node: '>=12'} magic-string@0.30.17: @@ -1611,11 +1611,13 @@ packages: prettier: ^3.0.0 svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0 - prettier-plugin-tailwindcss@0.6.13: - resolution: {integrity: sha512-uQ0asli1+ic8xrrSmIOaElDu0FacR4x69GynTh2oZjFY10JUt6EEumTQl5tB4fMeD6I1naKd+4rXQQ7esT2i1g==} + prettier-plugin-tailwindcss@0.6.14: + resolution: {integrity: sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg==} engines: {node: '>=14.21.3'} peerDependencies: '@ianvs/prettier-plugin-sort-imports': '*' + '@prettier/plugin-hermes': '*' + '@prettier/plugin-oxc': '*' '@prettier/plugin-pug': '*' '@shopify/prettier-plugin-liquid': '*' '@trivago/prettier-plugin-sort-imports': '*' @@ -1635,6 +1637,10 @@ packages: peerDependenciesMeta: '@ianvs/prettier-plugin-sort-imports': optional: true + '@prettier/plugin-hermes': + optional: true + '@prettier/plugin-oxc': + optional: true '@prettier/plugin-pug': optional: true '@shopify/prettier-plugin-liquid': @@ -1811,8 +1817,8 @@ packages: svelte: optional: true - svelte@5.35.2: - resolution: {integrity: sha512-uW/rRXYrhZ7Dh4UQNZ0t+oVGL1dEM+95GavCO8afAk1IY2cPq9BcZv9C3um5aLIya2y8lIeLPxLII9ASGg9Dzw==} + svelte@5.35.6: + resolution: {integrity: sha512-p7PVLQYrvCxJuxzGfOv/l71hVuHC6EZk5UDjbt/bndMYaBcUV5sFjDsj+PSIYvz1vcfbG6inX83/xIUeik1xGA==} engines: {node: '>=18'} tailwindcss@3.4.17: @@ -1854,8 +1860,8 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - typescript-eslint@8.35.1: - resolution: {integrity: sha512-xslJjFzhOmHYQzSB/QTeASAHbjmxOGEP6Coh93TXmUBFQoJ1VU35UHIDmG06Jd6taf3wqqC1ntBnCMeymy5Ovw==} + typescript-eslint@8.36.0: + resolution: {integrity: sha512-fTCqxthY+h9QbEgSIBfL9iV6CvKDFuoxg6bHPNpJ9HIUzS+jy2lCEyCmGyZRWEBSaykqcDPf1SJ+BfCI8DRopA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -1998,8 +2004,8 @@ packages: zimmerframe@1.1.2: resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==} - zod@3.25.74: - resolution: {integrity: sha512-J8poo92VuhKjNknViHRAIuuN6li/EwFbAC8OedzI8uxpEPGiXHGQu9wemIAioIpqgfB4SySaJhdk0mH5Y4ICBg==} + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} snapshots: @@ -2314,18 +2320,18 @@ snapshots: dependencies: acorn: 8.15.0 - '@sveltejs/adapter-node@5.2.12(@sveltejs/kit@2.22.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10)))(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10)))': + '@sveltejs/adapter-node@5.2.13(@sveltejs/kit@2.22.5(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13)))(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13)))': dependencies: '@rollup/plugin-commonjs': 28.0.6(rollup@4.44.2) '@rollup/plugin-json': 6.1.0(rollup@4.44.2) '@rollup/plugin-node-resolve': 16.0.1(rollup@4.44.2) - '@sveltejs/kit': 2.22.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10)))(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10)) + '@sveltejs/kit': 2.22.5(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13)))(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13)) rollup: 4.44.2 - '@sveltejs/kit@2.22.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10)))(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10))': + '@sveltejs/kit@2.22.5(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13)))(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13))': dependencies: '@sveltejs/acorn-typescript': 1.0.5(acorn@8.15.0) - '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10)) + '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13)) '@types/cookie': 0.6.0 acorn: 8.15.0 cookie: 0.6.0 @@ -2337,29 +2343,28 @@ snapshots: sade: 1.8.1 set-cookie-parser: 2.7.1 sirv: 3.0.1 - svelte: 5.35.2 - vite: 5.4.19(@types/node@24.0.10) - vitefu: 1.1.1(vite@5.4.19(@types/node@24.0.10)) + svelte: 5.35.6 + vite: 5.4.19(@types/node@24.0.13) - '@sveltejs/vite-plugin-svelte-inspector@3.0.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10)))(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10))': + '@sveltejs/vite-plugin-svelte-inspector@3.0.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13)))(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13))': dependencies: - '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10)) + '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13)) debug: 4.4.1 - svelte: 5.35.2 - vite: 5.4.19(@types/node@24.0.10) + svelte: 5.35.6 + vite: 5.4.19(@types/node@24.0.13) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10))': + '@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 3.0.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10)))(svelte@5.35.2)(vite@5.4.19(@types/node@24.0.10)) + '@sveltejs/vite-plugin-svelte-inspector': 3.0.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13)))(svelte@5.35.6)(vite@5.4.19(@types/node@24.0.13)) debug: 4.4.1 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.17 - svelte: 5.35.2 - vite: 5.4.19(@types/node@24.0.10) - vitefu: 1.1.1(vite@5.4.19(@types/node@24.0.10)) + svelte: 5.35.6 + vite: 5.4.19(@types/node@24.0.13) + vitefu: 1.1.1(vite@5.4.19(@types/node@24.0.13)) transitivePeerDependencies: - supports-color @@ -2373,30 +2378,30 @@ snapshots: '@types/ms@0.7.34': {} - '@types/node-schedule@2.1.7': + '@types/node-schedule@2.1.8': dependencies: - '@types/node': 24.0.10 + '@types/node': 24.0.13 - '@types/node@24.0.10': + '@types/node@24.0.13': dependencies: undici-types: 7.8.0 '@types/pg@8.15.4': dependencies: - '@types/node': 24.0.10 + '@types/node': 24.0.13 pg-protocol: 1.10.3 pg-types: 2.2.0 '@types/resolve@1.20.2': {} - '@typescript-eslint/eslint-plugin@8.35.1(@typescript-eslint/parser@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': + '@typescript-eslint/eslint-plugin@8.36.0(@typescript-eslint/parser@8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/scope-manager': 8.35.1 - '@typescript-eslint/type-utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/visitor-keys': 8.35.1 + '@typescript-eslint/parser': 8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.36.0 + '@typescript-eslint/type-utils': 8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/utils': 8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.36.0 eslint: 9.30.1(jiti@2.4.2) graphemer: 1.4.0 ignore: 7.0.5 @@ -2406,40 +2411,40 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': + '@typescript-eslint/parser@8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': dependencies: - '@typescript-eslint/scope-manager': 8.35.1 - '@typescript-eslint/types': 8.35.1 - '@typescript-eslint/typescript-estree': 8.35.1(typescript@5.8.3) - '@typescript-eslint/visitor-keys': 8.35.1 + '@typescript-eslint/scope-manager': 8.36.0 + '@typescript-eslint/types': 8.36.0 + '@typescript-eslint/typescript-estree': 8.36.0(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.36.0 debug: 4.4.1 eslint: 9.30.1(jiti@2.4.2) typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.35.1(typescript@5.8.3)': + '@typescript-eslint/project-service@8.36.0(typescript@5.8.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.35.1(typescript@5.8.3) - '@typescript-eslint/types': 8.35.1 + '@typescript-eslint/tsconfig-utils': 8.36.0(typescript@5.8.3) + '@typescript-eslint/types': 8.36.0 debug: 4.4.1 typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.35.1': + '@typescript-eslint/scope-manager@8.36.0': dependencies: - '@typescript-eslint/types': 8.35.1 - '@typescript-eslint/visitor-keys': 8.35.1 + '@typescript-eslint/types': 8.36.0 + '@typescript-eslint/visitor-keys': 8.36.0 - '@typescript-eslint/tsconfig-utils@8.35.1(typescript@5.8.3)': + '@typescript-eslint/tsconfig-utils@8.36.0(typescript@5.8.3)': dependencies: typescript: 5.8.3 - '@typescript-eslint/type-utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': + '@typescript-eslint/type-utils@8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': dependencies: - '@typescript-eslint/typescript-estree': 8.35.1(typescript@5.8.3) - '@typescript-eslint/utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 8.36.0(typescript@5.8.3) + '@typescript-eslint/utils': 8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) debug: 4.4.1 eslint: 9.30.1(jiti@2.4.2) ts-api-utils: 2.1.0(typescript@5.8.3) @@ -2447,14 +2452,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.35.1': {} + '@typescript-eslint/types@8.36.0': {} - '@typescript-eslint/typescript-estree@8.35.1(typescript@5.8.3)': + '@typescript-eslint/typescript-estree@8.36.0(typescript@5.8.3)': dependencies: - '@typescript-eslint/project-service': 8.35.1(typescript@5.8.3) - '@typescript-eslint/tsconfig-utils': 8.35.1(typescript@5.8.3) - '@typescript-eslint/types': 8.35.1 - '@typescript-eslint/visitor-keys': 8.35.1 + '@typescript-eslint/project-service': 8.36.0(typescript@5.8.3) + '@typescript-eslint/tsconfig-utils': 8.36.0(typescript@5.8.3) + '@typescript-eslint/types': 8.36.0 + '@typescript-eslint/visitor-keys': 8.36.0 debug: 4.4.1 fast-glob: 3.3.3 is-glob: 4.0.3 @@ -2465,20 +2470,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': + '@typescript-eslint/utils@8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@2.4.2)) - '@typescript-eslint/scope-manager': 8.35.1 - '@typescript-eslint/types': 8.35.1 - '@typescript-eslint/typescript-estree': 8.35.1(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.36.0 + '@typescript-eslint/types': 8.36.0 + '@typescript-eslint/typescript-estree': 8.36.0(typescript@5.8.3) eslint: 9.30.1(jiti@2.4.2) typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.35.1': + '@typescript-eslint/visitor-keys@8.36.0': dependencies: - '@typescript-eslint/types': 8.35.1 + '@typescript-eslint/types': 8.36.0 eslint-visitor-keys: 4.2.1 '@xmldom/xmldom@0.9.8': @@ -2531,7 +2536,7 @@ snapshots: autoprefixer@10.4.21(postcss@8.5.6): dependencies: browserslist: 4.25.1 - caniuse-lite: 1.0.30001726 + caniuse-lite: 1.0.30001727 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.1 @@ -2567,8 +2572,8 @@ snapshots: browserslist@4.25.1: dependencies: - caniuse-lite: 1.0.30001726 - electron-to-chromium: 1.5.179 + caniuse-lite: 1.0.30001727 + electron-to-chromium: 1.5.182 node-releases: 2.0.19 update-browserslist-db: 1.1.3(browserslist@4.25.1) @@ -2596,7 +2601,7 @@ snapshots: camelcase-css@2.0.1: {} - caniuse-lite@1.0.30001726: {} + caniuse-lite@1.0.30001727: {} chalk@4.1.2: dependencies: @@ -2651,7 +2656,7 @@ snapshots: cron-parser@4.9.0: dependencies: - luxon: 3.6.1 + luxon: 3.7.1 cross-spawn@7.0.6: dependencies: @@ -2693,7 +2698,7 @@ snapshots: eastasianwidth@0.2.0: {} - electron-to-chromium@1.5.179: {} + electron-to-chromium@1.5.182: {} emoji-regex@8.0.0: {} @@ -2748,7 +2753,7 @@ snapshots: dependencies: eslint: 9.30.1(jiti@2.4.2) - eslint-plugin-svelte@3.10.1(eslint@9.30.1(jiti@2.4.2))(svelte@5.35.2): + eslint-plugin-svelte@3.10.1(eslint@9.30.1(jiti@2.4.2))(svelte@5.35.6): dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@2.4.2)) '@jridgewell/sourcemap-codec': 1.5.4 @@ -2760,9 +2765,9 @@ snapshots: postcss-load-config: 3.1.4(postcss@8.5.6) postcss-safe-parser: 7.0.1(postcss@8.5.6) semver: 7.7.2 - svelte-eslint-parser: 1.2.0(svelte@5.35.2) + svelte-eslint-parser: 1.2.0(svelte@5.35.6) optionalDependencies: - svelte: 5.35.2 + svelte: 5.35.6 transitivePeerDependencies: - ts-node @@ -3108,7 +3113,7 @@ snapshots: lru-cache@11.1.0: {} - luxon@3.6.1: {} + luxon@3.7.1: {} magic-string@0.30.17: dependencies: @@ -3372,16 +3377,16 @@ snapshots: prelude-ls@1.2.1: {} - prettier-plugin-svelte@3.4.0(prettier@3.6.2)(svelte@5.35.2): + prettier-plugin-svelte@3.4.0(prettier@3.6.2)(svelte@5.35.6): dependencies: prettier: 3.6.2 - svelte: 5.35.2 + svelte: 5.35.6 - prettier-plugin-tailwindcss@0.6.13(prettier-plugin-svelte@3.4.0(prettier@3.6.2)(svelte@5.35.2))(prettier@3.6.2): + prettier-plugin-tailwindcss@0.6.14(prettier-plugin-svelte@3.4.0(prettier@3.6.2)(svelte@5.35.6))(prettier@3.6.2): dependencies: prettier: 3.6.2 optionalDependencies: - prettier-plugin-svelte: 3.4.0(prettier@3.6.2)(svelte@5.35.2) + prettier-plugin-svelte: 3.4.0(prettier@3.6.2)(svelte@5.35.6) prettier@3.6.2: {} @@ -3516,19 +3521,19 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-check@4.2.2(picomatch@4.0.2)(svelte@5.35.2)(typescript@5.8.3): + svelte-check@4.2.2(picomatch@4.0.2)(svelte@5.35.6)(typescript@5.8.3): dependencies: '@jridgewell/trace-mapping': 0.3.29 chokidar: 4.0.3 fdir: 6.4.6(picomatch@4.0.2) picocolors: 1.1.1 sade: 1.8.1 - svelte: 5.35.2 + svelte: 5.35.6 typescript: 5.8.3 transitivePeerDependencies: - picomatch - svelte-eslint-parser@1.2.0(svelte@5.35.2): + svelte-eslint-parser@1.2.0(svelte@5.35.6): dependencies: eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -3537,9 +3542,9 @@ snapshots: postcss-scss: 4.0.9(postcss@8.5.6) postcss-selector-parser: 7.1.0 optionalDependencies: - svelte: 5.35.2 + svelte: 5.35.6 - svelte@5.35.2: + svelte@5.35.6: dependencies: '@ampproject/remapping': 2.3.0 '@jridgewell/sourcemap-codec': 1.5.4 @@ -3611,11 +3616,11 @@ snapshots: dependencies: prelude-ls: 1.2.1 - typescript-eslint@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3): + typescript-eslint@8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.35.1(@typescript-eslint/parser@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/parser': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/eslint-plugin': 8.36.0(@typescript-eslint/parser@8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/parser': 8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/utils': 8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) eslint: 9.30.1(jiti@2.4.2) typescript: 5.8.3 transitivePeerDependencies: @@ -3627,7 +3632,7 @@ snapshots: undici-types@7.8.0: {} - unplugin-icons@22.1.0(svelte@5.35.2): + unplugin-icons@22.1.0(svelte@5.35.6): dependencies: '@antfu/install-pkg': 1.1.0 '@iconify/utils': 2.3.0 @@ -3635,7 +3640,7 @@ snapshots: local-pkg: 1.1.1 unplugin: 2.3.5 optionalDependencies: - svelte: 5.35.2 + svelte: 5.35.6 transitivePeerDependencies: - supports-color @@ -3659,18 +3664,18 @@ snapshots: uuid@11.1.0: {} - vite@5.4.19(@types/node@24.0.10): + vite@5.4.19(@types/node@24.0.13): dependencies: esbuild: 0.21.5 postcss: 8.5.6 rollup: 4.44.2 optionalDependencies: - '@types/node': 24.0.10 + '@types/node': 24.0.13 fsevents: 2.3.3 - vitefu@1.1.1(vite@5.4.19(@types/node@24.0.10)): + vitefu@1.1.1(vite@5.4.19(@types/node@24.0.13)): optionalDependencies: - vite: 5.4.19(@types/node@24.0.10) + vite: 5.4.19(@types/node@24.0.13) webpack-virtual-modules@0.6.2: {} @@ -3704,4 +3709,4 @@ snapshots: zimmerframe@1.1.2: {} - zod@3.25.74: {} + zod@3.25.76: {} From 1304cc3868dbc478b387b00797190d9b14abe804 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 12 Jul 2025 04:22:26 +0900 Subject: [PATCH 35/38] =?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 --- .../settings/{thumbnails => thumbnail}/+page.svelte | 0 .../(fullscreen)/settings/{thumbnails => thumbnail}/+page.ts | 0 .../(fullscreen)/settings/{thumbnails => thumbnail}/File.svelte | 0 .../settings/{thumbnails => thumbnail}/service.svelte.ts | 0 src/routes/(main)/menu/+page.svelte | 2 +- src/routes/api/file/scanMissingThumbnails/+server.ts | 1 - 6 files changed, 1 insertion(+), 2 deletions(-) rename src/routes/(fullscreen)/settings/{thumbnails => thumbnail}/+page.svelte (100%) rename src/routes/(fullscreen)/settings/{thumbnails => thumbnail}/+page.ts (100%) rename src/routes/(fullscreen)/settings/{thumbnails => thumbnail}/File.svelte (100%) rename src/routes/(fullscreen)/settings/{thumbnails => thumbnail}/service.svelte.ts (100%) diff --git a/src/routes/(fullscreen)/settings/thumbnails/+page.svelte b/src/routes/(fullscreen)/settings/thumbnail/+page.svelte similarity index 100% rename from src/routes/(fullscreen)/settings/thumbnails/+page.svelte rename to src/routes/(fullscreen)/settings/thumbnail/+page.svelte diff --git a/src/routes/(fullscreen)/settings/thumbnails/+page.ts b/src/routes/(fullscreen)/settings/thumbnail/+page.ts similarity index 100% rename from src/routes/(fullscreen)/settings/thumbnails/+page.ts rename to src/routes/(fullscreen)/settings/thumbnail/+page.ts diff --git a/src/routes/(fullscreen)/settings/thumbnails/File.svelte b/src/routes/(fullscreen)/settings/thumbnail/File.svelte similarity index 100% rename from src/routes/(fullscreen)/settings/thumbnails/File.svelte rename to src/routes/(fullscreen)/settings/thumbnail/File.svelte diff --git a/src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts b/src/routes/(fullscreen)/settings/thumbnail/service.svelte.ts similarity index 100% rename from src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts rename to src/routes/(fullscreen)/settings/thumbnail/service.svelte.ts diff --git a/src/routes/(main)/menu/+page.svelte b/src/routes/(main)/menu/+page.svelte index 6a52128..40f4a26 100644 --- a/src/routes/(main)/menu/+page.svelte +++ b/src/routes/(main)/menu/+page.svelte @@ -35,7 +35,7 @@ 캐시 goto("/settings/thumbnails")} + onclick={() => goto("/settings/thumbnail")} icon={IconImage} iconColor="text-blue-500" > diff --git a/src/routes/api/file/scanMissingThumbnails/+server.ts b/src/routes/api/file/scanMissingThumbnails/+server.ts index c7ceef2..bf2a2a6 100644 --- a/src/routes/api/file/scanMissingThumbnails/+server.ts +++ b/src/routes/api/file/scanMissingThumbnails/+server.ts @@ -9,7 +9,6 @@ import type { RequestHandler } from "./$types"; export const POST: RequestHandler = async ({ locals }) => { const { userId } = await authorize(locals, "activeClient"); - const { files } = await scanMissingFileThumbnails(userId); return json( missingThumbnailFileScanResponse.parse({ files } satisfies MissingThumbnailFileScanResponse), From 0d35f0b60730d730be0c29d8cb4ab8dd4a0f28c5 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 12 Jul 2025 04:57:15 +0900 Subject: [PATCH 36/38] =?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=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../organisms/modals}/ForceLoginModal.svelte | 2 +- src/lib/components/organisms/modals/index.ts | 1 + src/lib/services/file.ts | 24 ++++++++++++++++++- .../(fullscreen)/auth/login/+page.svelte | 2 +- src/routes/(fullscreen)/file/[id]/service.ts | 16 +++---------- .../(fullscreen)/key/generate/+page.svelte | 2 +- .../key/generate/ForceLoginModal.svelte | 20 ---------------- .../settings/thumbnail/service.svelte.ts | 15 ++---------- 8 files changed, 32 insertions(+), 50 deletions(-) rename src/{routes/(fullscreen)/auth/login => lib/components/organisms/modals}/ForceLoginModal.svelte (95%) delete mode 100644 src/routes/(fullscreen)/key/generate/ForceLoginModal.svelte diff --git a/src/routes/(fullscreen)/auth/login/ForceLoginModal.svelte b/src/lib/components/organisms/modals/ForceLoginModal.svelte similarity index 95% rename from src/routes/(fullscreen)/auth/login/ForceLoginModal.svelte rename to src/lib/components/organisms/modals/ForceLoginModal.svelte index fc88a4a..e6aa82b 100644 --- a/src/routes/(fullscreen)/auth/login/ForceLoginModal.svelte +++ b/src/lib/components/organisms/modals/ForceLoginModal.svelte @@ -3,7 +3,7 @@ interface Props { isOpen: boolean; - oncancel: () => void; + oncancel?: () => void; onLoginClick: () => void; } diff --git a/src/lib/components/organisms/modals/index.ts b/src/lib/components/organisms/modals/index.ts index 2fa9422..56c64a1 100644 --- a/src/lib/components/organisms/modals/index.ts +++ b/src/lib/components/organisms/modals/index.ts @@ -1,3 +1,4 @@ export { default as CategoryCreateModal } from "./CategoryCreateModal.svelte"; +export { default as ForceLoginModal } from "./ForceLoginModal.svelte"; export { default as RenameModal } from "./RenameModal.svelte"; export { default as TextInputModal } from "./TextInputModal.svelte"; diff --git a/src/lib/services/file.ts b/src/lib/services/file.ts index 64d9fd4..11742c8 100644 --- a/src/lib/services/file.ts +++ b/src/lib/services/file.ts @@ -11,7 +11,11 @@ import { downloadFile, } from "$lib/modules/file"; import { getThumbnailUrl } from "$lib/modules/thumbnail"; -import type { FileThumbnailInfoResponse, FileListResponse } from "$lib/server/schemas"; +import type { + FileThumbnailInfoResponse, + FileThumbnailUploadRequest, + FileListResponse, +} from "$lib/server/schemas"; export const requestFileDownload = async ( fileId: number, @@ -26,6 +30,24 @@ export const requestFileDownload = async ( return fileBuffer; }; +export const requestFileThumbnailUpload = async ( + fileId: number, + dataKeyVersion: Date, + thumbnailEncrypted: { ciphertext: ArrayBuffer; iv: string }, +) => { + const form = new FormData(); + form.set( + "metadata", + JSON.stringify({ + dekVersion: dataKeyVersion.toISOString(), + contentIv: thumbnailEncrypted.iv, + } satisfies FileThumbnailUploadRequest), + ); + form.set("content", new Blob([thumbnailEncrypted.ciphertext])); + + return await fetch(`/api/file/${fileId}/thumbnail/upload`, { method: "POST", body: form }); +}; + export const requestFileThumbnailDownload = async (fileId: number, dataKey: CryptoKey) => { const cache = await getFileThumbnailCache(fileId); if (cache) return cache; diff --git a/src/routes/(fullscreen)/auth/login/+page.svelte b/src/routes/(fullscreen)/auth/login/+page.svelte index 813b130..b4f3d32 100644 --- a/src/routes/(fullscreen)/auth/login/+page.svelte +++ b/src/routes/(fullscreen)/auth/login/+page.svelte @@ -2,8 +2,8 @@ import { goto } from "$app/navigation"; import { BottomDiv, Button, FullscreenDiv, TextButton, TextInput } from "$lib/components/atoms"; import { TitledDiv } from "$lib/components/molecules"; + import { ForceLoginModal } from "$lib/components/organisms"; import { clientKeyStore, masterKeyStore } from "$lib/stores"; - import ForceLoginModal from "./ForceLoginModal.svelte"; import { requestLogout, requestLogin, diff --git a/src/routes/(fullscreen)/file/[id]/service.ts b/src/routes/(fullscreen)/file/[id]/service.ts index 83a57c6..00614d6 100644 --- a/src/routes/(fullscreen)/file/[id]/service.ts +++ b/src/routes/(fullscreen)/file/[id]/service.ts @@ -1,7 +1,8 @@ import { callPostApi } from "$lib/hooks"; import { encryptData } from "$lib/modules/crypto"; import { storeFileThumbnailCache } from "$lib/modules/file"; -import type { CategoryFileAddRequest, FileThumbnailUploadRequest } from "$lib/server/schemas"; +import type { CategoryFileAddRequest } from "$lib/server/schemas"; +import { requestFileThumbnailUpload } from "$lib/services/file"; export { requestCategoryCreation, requestFileRemovalFromCategory } from "$lib/services/category"; export { requestFileDownload } from "$lib/services/file"; @@ -14,18 +15,7 @@ export const requestThumbnailUpload = async ( ) => { const thumbnailBuffer = await thumbnail.arrayBuffer(); const thumbnailEncrypted = await encryptData(thumbnailBuffer, dataKey); - - const form = new FormData(); - form.set( - "metadata", - JSON.stringify({ - dekVersion: dataKeyVersion.toISOString(), - contentIv: thumbnailEncrypted.iv, - } satisfies FileThumbnailUploadRequest), - ); - form.set("content", new Blob([thumbnailEncrypted.ciphertext])); - - const res = await fetch(`/api/file/${fileId}/thumbnail/upload`, { method: "POST", body: form }); + const res = await requestFileThumbnailUpload(fileId, dataKeyVersion, thumbnailEncrypted); if (!res.ok) return false; storeFileThumbnailCache(fileId, thumbnailBuffer); // Intended diff --git a/src/routes/(fullscreen)/key/generate/+page.svelte b/src/routes/(fullscreen)/key/generate/+page.svelte index 090651f..1155577 100644 --- a/src/routes/(fullscreen)/key/generate/+page.svelte +++ b/src/routes/(fullscreen)/key/generate/+page.svelte @@ -3,10 +3,10 @@ import { goto } from "$app/navigation"; import { BottomDiv, Button, FullscreenDiv, TextButton } from "$lib/components/atoms"; import { TitledDiv } from "$lib/components/molecules"; + import { ForceLoginModal } from "$lib/components/organisms"; import { gotoStateful } from "$lib/hooks"; import { storeClientKeys } from "$lib/modules/key"; import { clientKeyStore } from "$lib/stores"; - import ForceLoginModal from "./ForceLoginModal.svelte"; import Order from "./Order.svelte"; import { generateClientKeys, diff --git a/src/routes/(fullscreen)/key/generate/ForceLoginModal.svelte b/src/routes/(fullscreen)/key/generate/ForceLoginModal.svelte deleted file mode 100644 index f488603..0000000 --- a/src/routes/(fullscreen)/key/generate/ForceLoginModal.svelte +++ /dev/null @@ -1,20 +0,0 @@ - - - -

다른 디바이스에서는 로그아웃하고, 이 디바이스에서 로그인할까요?

-
diff --git a/src/routes/(fullscreen)/settings/thumbnail/service.svelte.ts b/src/routes/(fullscreen)/settings/thumbnail/service.svelte.ts index 79a4022..6bb37b1 100644 --- a/src/routes/(fullscreen)/settings/thumbnail/service.svelte.ts +++ b/src/routes/(fullscreen)/settings/thumbnail/service.svelte.ts @@ -4,8 +4,7 @@ import { encryptData } from "$lib/modules/crypto"; import { storeFileThumbnailCache } from "$lib/modules/file"; import type { FileInfo } from "$lib/modules/filesystem"; import { generateThumbnail as doGenerateThumbnail } from "$lib/modules/thumbnail"; -import type { FileThumbnailUploadRequest } from "$lib/server/schemas"; -import { requestFileDownload } from "$lib/services/file"; +import { requestFileDownload, requestFileThumbnailUpload } from "$lib/services/file"; export type GenerationStatus = | "queued" @@ -68,17 +67,7 @@ const requestThumbnailUpload = limitFunction( ) => { status.set("uploading"); - const form = new FormData(); - form.set( - "metadata", - JSON.stringify({ - dekVersion: dataKeyVersion.toISOString(), - contentIv: thumbnail.iv, - } satisfies FileThumbnailUploadRequest), - ); - form.set("content", new Blob([thumbnail.ciphertext])); - - const res = await fetch(`/api/file/${fileId}/thumbnail/upload`, { method: "POST", body: form }); + const res = await requestFileThumbnailUpload(fileId, dataKeyVersion, thumbnail); if (!res.ok) return false; status.set("uploaded"); From 4679b1d6bdefe9bde7c10f72e93748319e2a4555 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 12 Jul 2025 05:39:39 +0900 Subject: [PATCH 37/38] =?UTF-8?q?=EB=8F=99=EC=98=81=EC=83=81=EC=9D=98=20?= =?UTF-8?q?=EC=8D=B8=EB=84=A4=EC=9D=BC=EC=9D=B4=20=EA=B0=80=EB=81=94=20?= =?UTF-8?q?=ED=9D=B0=EC=83=89=EC=9C=BC=EB=A1=9C=20=EC=9E=98=EB=AA=BB=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=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/file/cache.ts | 4 +- src/lib/modules/thumbnail.ts | 77 +++++++++++++++++------------------ 2 files changed, 38 insertions(+), 43 deletions(-) diff --git a/src/lib/modules/file/cache.ts b/src/lib/modules/file/cache.ts index 31eac28..ccb187e 100644 --- a/src/lib/modules/file/cache.ts +++ b/src/lib/modules/file/cache.ts @@ -54,9 +54,7 @@ export const deleteFileCache = async (fileId: number) => { export const getFileThumbnailCache = async (fileId: number) => { const thumbnail = loadedThumbnails.get(fileId); - if (thumbnail) { - return thumbnail; - } + if (thumbnail) return thumbnail; const thumbnailBuffer = await readFile(`/thumbnail/file/${fileId}`); if (!thumbnailBuffer) return null; diff --git a/src/lib/modules/thumbnail.ts b/src/lib/modules/thumbnail.ts index 873772e..9c009bd 100644 --- a/src/lib/modules/thumbnail.ts +++ b/src/lib/modules/thumbnail.ts @@ -12,50 +12,25 @@ const scaleSize = (width: number, height: number, targetSize: number) => { }; }; -const generateImageThumbnail = (imageUrl: string) => { - return new Promise((resolve, reject) => { - const image = new Image(); - image.onload = () => { - const canvas = document.createElement("canvas"); - const { width, height } = scaleSize(image.width, image.height, 250); - - canvas.width = width; - canvas.height = height; - - const context = canvas.getContext("2d"); - if (!context) { - return reject(new Error("Failed to generate thumbnail")); - } - - context.drawImage(image, 0, 0, width, height); - canvas.toBlob((blob) => { - if (blob) { - resolve(blob); - } else { - reject(new Error("Failed to generate thumbnail")); - } - }, "image/webp"); - }; - image.onerror = reject; - - image.src = imageUrl; - }); -}; - -export const captureVideoThumbnail = (video: HTMLVideoElement) => { +const capture = ( + width: number, + height: number, + drawer: (context: CanvasRenderingContext2D, width: number, height: number) => void, + targetSize = 250, +) => { return new Promise((resolve, reject) => { const canvas = document.createElement("canvas"); - const { width, height } = scaleSize(video.videoWidth, video.videoHeight, 250); + const { width: scaledWidth, height: scaledHeight } = scaleSize(width, height, targetSize); - canvas.width = width; - canvas.height = height; + canvas.width = scaledWidth; + canvas.height = scaledHeight; const context = canvas.getContext("2d"); if (!context) { return reject(new Error("Failed to generate thumbnail")); } - context.drawImage(video, 0, 0, width, height); + drawer(context, scaledWidth, scaledHeight); canvas.toBlob((blob) => { if (blob) { resolve(blob); @@ -66,14 +41,36 @@ export const captureVideoThumbnail = (video: HTMLVideoElement) => { }); }; +const generateImageThumbnail = (imageUrl: string) => { + return new Promise((resolve, reject) => { + const image = new Image(); + image.onload = () => { + capture(image.width, image.height, (context, width, height) => { + context.drawImage(image, 0, 0, width, height); + }) + .then(resolve) + .catch(reject); + }; + image.onerror = reject; + + image.src = imageUrl; + }); +}; + +export const captureVideoThumbnail = (video: HTMLVideoElement) => { + return capture(video.videoWidth, video.videoHeight, (context, width, height) => { + context.drawImage(video, 0, 0, width, height); + }); +}; + const generateVideoThumbnail = (videoUrl: string, time = 0) => { return new Promise((resolve, reject) => { const video = document.createElement("video"); - video.onloadeddata = () => { - video.currentTime = time; - }; - video.onseeked = () => { - captureVideoThumbnail(video).then(resolve).catch(reject); + video.onloadedmetadata = () => { + video.currentTime = Math.min(time, video.duration); + video.requestVideoFrameCallback(() => { + captureVideoThumbnail(video).then(resolve).catch(reject); + }); }; video.onerror = reject; From 89921ef1df877b86f9eeba95c14a52d4cf0781d0 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 12 Jul 2025 05:58:35 +0900 Subject: [PATCH 38/38] =?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=203?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(fullscreen)/auth/login/+page.svelte | 6 ++--- .../(fullscreen)/key/generate/+page.svelte | 26 +++++++++---------- .../settings/thumbnail/service.svelte.ts | 19 +++++++------- 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/routes/(fullscreen)/auth/login/+page.svelte b/src/routes/(fullscreen)/auth/login/+page.svelte index b4f3d32..8ec1742 100644 --- a/src/routes/(fullscreen)/auth/login/+page.svelte +++ b/src/routes/(fullscreen)/auth/login/+page.svelte @@ -5,20 +5,20 @@ import { ForceLoginModal } from "$lib/components/organisms"; import { clientKeyStore, masterKeyStore } from "$lib/stores"; import { - requestLogout, requestLogin, requestClientRegistrationAndSessionUpgrade, requestMasterKeyDownload, requestDeletedFilesCleanup, + requestLogout, } from "./service"; let { data } = $props(); - let isForceLoginModalOpen = $state(false); - let email = $state(""); let password = $state(""); + let isForceLoginModalOpen = $state(false); + const redirect = async (url: string) => { return await goto(`${url}?redirect=${encodeURIComponent(data.redirectPath)}`); }; diff --git a/src/routes/(fullscreen)/key/generate/+page.svelte b/src/routes/(fullscreen)/key/generate/+page.svelte index 1155577..6f7609c 100644 --- a/src/routes/(fullscreen)/key/generate/+page.svelte +++ b/src/routes/(fullscreen)/key/generate/+page.svelte @@ -61,19 +61,6 @@ }); }; - const importKeys = async () => { - const file = fileInput?.files?.[0]; - if (!file) return; - - if (await importClientKeys(await file.text())) { - await upgradeSession(false); - } else { - // TODO: Error Handling - } - - fileInput!.value = ""; - }; - const upgradeSession = async (force: boolean) => { const [upgradeRes, upgradeError] = await requestClientRegistrationAndSessionUpgrade( $clientKeyStore!, @@ -109,6 +96,19 @@ await goto("/client/pending?redirect=" + encodeURIComponent(data.redirectPath)); }; + const importKeys = async () => { + const file = fileInput?.files?.[0]; + if (!file) return; + + if (await importClientKeys(await file.text())) { + await upgradeSession(false); + } else { + // TODO: Error Handling + } + + fileInput!.value = ""; + }; + onMount(async () => { if ($clientKeyStore) { await goto(data.redirectPath, { replaceState: true }); diff --git a/src/routes/(fullscreen)/settings/thumbnail/service.svelte.ts b/src/routes/(fullscreen)/settings/thumbnail/service.svelte.ts index 6bb37b1..d8f288c 100644 --- a/src/routes/(fullscreen)/settings/thumbnail/service.svelte.ts +++ b/src/routes/(fullscreen)/settings/thumbnail/service.svelte.ts @@ -25,7 +25,7 @@ const workingFiles = new Map>(); let queue: (() => void)[] = []; let memoryUsage = 0; -const MEMORY_LIMIT = 100 * 1024 * 1024; // 100 MiB +const memoryLimit = 100 * 1024 * 1024; // 100 MiB export const persistentStates = $state({ files: [] as File[], @@ -45,10 +45,7 @@ const generateThumbnail = limitFunction( status.set("generating"); const thumbnail = await doGenerateThumbnail(fileBuffer, fileType); - if (!thumbnail) { - status.set("error"); - return null; - } + if (!thumbnail) return null; const thumbnailBuffer = await thumbnail.arrayBuffer(); const thumbnailEncrypted = await encryptData(thumbnailBuffer, dataKey); @@ -101,7 +98,7 @@ const enqueue = async ( }); if (priority) { - queue = [() => resolver!(), ...queue]; + queue = [resolver!, ...queue]; } else { queue.push(resolver!); } @@ -116,7 +113,7 @@ export const requestThumbnailGeneration = async (fileInfo: FileInfo) => { if (workingFiles.values().some((status) => get(status) !== "error")) { await enqueue(status, fileInfo); } - while (memoryUsage >= MEMORY_LIMIT) { + while (memoryUsage >= memoryLimit) { await enqueue(status, fileInfo, true); } @@ -136,7 +133,7 @@ export const requestThumbnailGeneration = async (fileInfo: FileInfo) => { fileSize = file.byteLength; memoryUsage += fileSize; - if (memoryUsage < MEMORY_LIMIT) { + if (memoryUsage < memoryLimit) { queue.shift()?.(); } @@ -146,8 +143,10 @@ export const requestThumbnailGeneration = async (fileInfo: FileInfo) => { fileInfo.contentType, fileInfo.dataKey!, ); - if (!thumbnail) return; - if (!(await requestThumbnailUpload(status, fileInfo.id, fileInfo.dataKeyVersion!, thumbnail))) { + if ( + !thumbnail || + !(await requestThumbnailUpload(status, fileInfo.id, fileInfo.dataKeyVersion!, thumbnail)) + ) { status.set("error"); } } catch {