diff --git a/apps/main/package.json b/apps/main/package.json
index 8fdc432..ca13d6b 100644
--- a/apps/main/package.json
+++ b/apps/main/package.json
@@ -21,19 +21,16 @@
"@opentelemetry/exporter-logs-otlp-proto": "^0.212.0",
"@opentelemetry/exporter-metrics-otlp-proto": "^0.212.0",
"@opentelemetry/exporter-trace-otlp-proto": "^0.212.0",
- "@opentelemetry/sdk-node": "^0.212.0",
"@opentelemetry/sdk-logs": "^0.212.0",
+ "@opentelemetry/sdk-node": "^0.212.0",
"@pkg/db": "workspace:*",
"@pkg/logger": "workspace:*",
"@pkg/logic": "workspace:*",
"@pkg/result": "workspace:*",
"@pkg/settings": "workspace:*",
- "@tanstack/svelte-query": "^6.0.18",
"better-auth": "^1.4.20",
"date-fns": "^4.1.0",
- "hono": "^4.11.1",
"import-in-the-middle": "^3.0.0",
- "marked": "^17.0.1",
"nanoid": "^5.1.6",
"neverthrow": "^8.2.0",
"qrcode": "^1.5.4",
diff --git a/apps/main/src/lib/api.ts b/apps/main/src/lib/api.ts
deleted file mode 100644
index 64d841e..0000000
--- a/apps/main/src/lib/api.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { usersRouter } from "@pkg/logic/domains/user/router";
-import { twofaRouter } from "@pkg/logic/domains/2fa/router";
-import { Hono } from "hono";
-
-const baseRouter = new Hono()
- .route("/users", usersRouter)
- .route("/twofactor", twofaRouter);
-
-export const apiBasePath = "/api/v1";
-
-export const api = new Hono().route(apiBasePath, baseRouter);
-
-export type Router = typeof baseRouter;
diff --git a/apps/main/src/lib/components/molecules/markdown-renderer.svelte b/apps/main/src/lib/components/molecules/markdown-renderer.svelte
deleted file mode 100644
index 6459612..0000000
--- a/apps/main/src/lib/components/molecules/markdown-renderer.svelte
+++ /dev/null
@@ -1,183 +0,0 @@
-
-
-
-
-
-
-
-
-
- {@html marked(content)}
-
-
-
diff --git a/apps/main/src/lib/core/server.utils.ts b/apps/main/src/lib/core/server.utils.ts
index 10c586c..450c7dd 100644
--- a/apps/main/src/lib/core/server.utils.ts
+++ b/apps/main/src/lib/core/server.utils.ts
@@ -1,4 +1,5 @@
import type { FlowExecCtx } from "@pkg/logic/core/flow.execution.context";
+import type { Err } from "@pkg/result";
export async function getFlowExecCtxForRemoteFuncs(
locals: App.Locals,
@@ -9,3 +10,16 @@ export async function getFlowExecCtxForRemoteFuncs(
sessionId: locals.session?.id,
};
}
+
+export function unauthorized(fctx: FlowExecCtx) {
+ return {
+ data: null,
+ error: {
+ flowId: fctx.flowId,
+ code: "UNAUTHORIZED",
+ message: "User not authenticated",
+ description: "Please log in",
+ detail: "No user ID found in session",
+ } as Err,
+ };
+}
diff --git a/apps/main/src/lib/currency.utils.ts b/apps/main/src/lib/currency.utils.ts
deleted file mode 100644
index 1dc9712..0000000
--- a/apps/main/src/lib/currency.utils.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export function formatCurrency(amount: number, currency = "EUR"): string {
- return new Intl.NumberFormat("en-US", { style: "currency", currency }).format(
- amount,
- );
-}
diff --git a/apps/main/src/lib/domains/account/account.remote.ts b/apps/main/src/lib/domains/account/account.remote.ts
index aac3c36..3dc2e38 100644
--- a/apps/main/src/lib/domains/account/account.remote.ts
+++ b/apps/main/src/lib/domains/account/account.remote.ts
@@ -1,22 +1,168 @@
-import { ensureAccountExistsSchema } from "@pkg/logic/domains/user/data";
+import {
+ banUserSchema,
+ checkUsernameSchema,
+ ensureAccountExistsSchema,
+ rotatePasswordSchema,
+} from "@pkg/logic/domains/user/data";
+import {
+ getFlowExecCtxForRemoteFuncs,
+ unauthorized,
+} from "$lib/core/server.utils";
+import { getUserController } from "@pkg/logic/domains/user/controller";
import { command, getRequestEvent, query } from "$app/server";
+import * as v from "valibot";
+
+const uc = getUserController();
export const getMyInfoSQ = query(async () => {
const event = getRequestEvent();
- // .... do stuff usually done in a router here
+ const fctx = await getFlowExecCtxForRemoteFuncs(event.locals);
- return { data: "testing" };
+ if (!fctx.userId) {
+ return unauthorized(fctx);
+ }
+
+ const res = await uc.getUserInfo(fctx, fctx.userId);
+ return res.isOk()
+ ? { data: res.value, error: null }
+ : { data: null, error: res.error };
});
-export const getUsersInfoByIdSQ = query(async () => {
- // .... do stuff usually done in a router here
- return { data: "testing" };
-});
+export const getUserInfoByIdSQ = query(
+ v.object({ userId: v.string() }),
+ async (input) => {
+ const event = getRequestEvent();
+ const fctx = await getFlowExecCtxForRemoteFuncs(event.locals);
-export const ensureAccountExistsSQ = command(
- ensureAccountExistsSchema,
- async (payload) => {
- // .... do stuff usually done in a router here
- return { data: "testing" };
+ if (!fctx.userId) {
+ return unauthorized(fctx);
+ }
+
+ const res = await uc.getUserInfo(fctx, input.userId);
+ return res.isOk()
+ ? { data: res.value, error: null }
+ : { data: null, error: res.error };
+ },
+);
+
+export const ensureAccountExistsSC = command(
+ ensureAccountExistsSchema,
+ async (payload) => {
+ const event = getRequestEvent();
+ const fctx = await getFlowExecCtxForRemoteFuncs(event.locals);
+
+ if (!fctx.userId) {
+ return unauthorized(fctx);
+ }
+
+ const res = await uc.ensureAccountExists(fctx, payload.userId);
+ return res.isOk()
+ ? { data: res.value, error: null }
+ : { data: null, error: res.error };
+ },
+);
+
+export const checkUsernameSC = command(checkUsernameSchema, async (payload) => {
+ const event = getRequestEvent();
+ const fctx = await getFlowExecCtxForRemoteFuncs(event.locals);
+
+ if (!fctx.userId) {
+ return unauthorized(fctx);
+ }
+
+ const res = await uc.isUsernameAvailable(fctx, payload.username);
+ return res.isOk()
+ ? { data: res.value, error: null }
+ : { data: null, error: res.error };
+});
+
+export const update2faVerifiedSC = command(
+ v.object({ userId: v.string() }),
+ async (payload) => {
+ const event = getRequestEvent();
+ const fctx = await getFlowExecCtxForRemoteFuncs(event.locals);
+
+ if (!fctx.userId) {
+ return unauthorized(fctx);
+ }
+
+ const res = await uc.updateLastVerified2FaAtToNow(fctx, payload.userId);
+ return res.isOk()
+ ? { data: res.value, error: null }
+ : { data: null, error: res.error };
+ },
+);
+
+export const banUserSC = command(banUserSchema, async (payload) => {
+ const event = getRequestEvent();
+ const fctx = await getFlowExecCtxForRemoteFuncs(event.locals);
+
+ if (!fctx.userId) {
+ return unauthorized(fctx);
+ }
+
+ const res = await uc.banUser(
+ fctx,
+ payload.userId,
+ payload.reason,
+ payload.banExpiresAt,
+ );
+ return res.isOk()
+ ? { data: res.value, error: null }
+ : { data: null, error: res.error };
+});
+
+export const isUserBannedSQ = query(
+ v.object({ userId: v.string() }),
+ async (input) => {
+ const event = getRequestEvent();
+ const fctx = await getFlowExecCtxForRemoteFuncs(event.locals);
+
+ if (!fctx.userId) {
+ return unauthorized(fctx);
+ }
+
+ const res = await uc.isUserBanned(fctx, input.userId);
+ return res.isOk()
+ ? { data: res.value, error: null }
+ : { data: null, error: res.error };
+ },
+);
+
+export const getBanInfoSQ = query(
+ v.object({ userId: v.string() }),
+ async (input) => {
+ const event = getRequestEvent();
+ const fctx = await getFlowExecCtxForRemoteFuncs(event.locals);
+
+ if (!fctx.userId) {
+ return unauthorized(fctx);
+ }
+
+ const res = await uc.getBanInfo(fctx, input.userId);
+ return res.isOk()
+ ? { data: res.value, error: null }
+ : { data: null, error: res.error };
+ },
+);
+
+export const rotatePasswordSC = command(
+ rotatePasswordSchema,
+ async (payload) => {
+ const event = getRequestEvent();
+ const fctx = await getFlowExecCtxForRemoteFuncs(event.locals);
+
+ if (!fctx.userId) {
+ return unauthorized(fctx);
+ }
+
+ const res = await uc.rotatePassword(
+ fctx,
+ payload.userId,
+ payload.password,
+ );
+ return res.isOk()
+ ? { data: res.value, error: null }
+ : { data: null, error: res.error };
},
);
diff --git a/apps/main/src/lib/domains/account/account.vm.svelte.ts b/apps/main/src/lib/domains/account/account.vm.svelte.ts
index 39bd03d..f17913f 100644
--- a/apps/main/src/lib/domains/account/account.vm.svelte.ts
+++ b/apps/main/src/lib/domains/account/account.vm.svelte.ts
@@ -1,10 +1,11 @@
+import { ResultAsync, errAsync, okAsync } from "neverthrow";
import type { User } from "@pkg/logic/domains/user/data";
-import { apiClient, user as userStore } from "$lib/global.stores";
+import { user as userStore } from "$lib/global.stores";
+import { rotatePasswordSC } from "./account.remote";
import { authClient } from "$lib/auth.client";
+import type { Err } from "@pkg/result";
import { toast } from "svelte-sonner";
import { get } from "svelte/store";
-import { ResultAsync, errAsync, okAsync } from "neverthrow";
-import type { Err } from "@pkg/result";
class AccountViewModel {
loading = $state(false);
@@ -20,22 +21,20 @@ class AccountViewModel {
description: "Network request failed",
detail: error instanceof Error ? error.message : String(error),
}),
- )
- .andThen((response) => {
- if (response.error) {
- return errAsync({
- code: "API_ERROR",
- message:
- response.error.message ??
- "Failed to update profile picture",
- description:
- response.error.statusText ??
- "Please try again later",
- detail: response.error.statusText ?? "Unknown error",
- });
- }
- return okAsync(response.data);
- });
+ ).andThen((response) => {
+ if (response.error) {
+ return errAsync({
+ code: "API_ERROR",
+ message:
+ response.error.message ??
+ "Failed to update profile picture",
+ description:
+ response.error.statusText ?? "Please try again later",
+ detail: response.error.statusText ?? "Unknown error",
+ });
+ }
+ return okAsync(response.data);
+ });
return result.match(
() => {
@@ -43,7 +42,8 @@ class AccountViewModel {
return true;
},
(error) => {
- this.errorMessage = error.message ?? "Failed to update profile picture";
+ this.errorMessage =
+ error.message ?? "Failed to update profile picture";
toast.error(this.errorMessage, {
description: error.description,
});
@@ -71,21 +71,19 @@ class AccountViewModel {
description: "Network request failed",
detail: error instanceof Error ? error.message : String(error),
}),
- )
- .andThen((response) => {
- if (response.error) {
- return errAsync({
- code: "API_ERROR",
- message:
- response.error.message ?? "Failed to update profile",
- description:
- response.error.statusText ??
- "Please try again later",
- detail: response.error.statusText ?? "Unknown error",
- });
- }
- return okAsync(response.data);
- });
+ ).andThen((response) => {
+ if (response.error) {
+ return errAsync({
+ code: "API_ERROR",
+ message:
+ response.error.message ?? "Failed to update profile",
+ description:
+ response.error.statusText ?? "Please try again later",
+ detail: response.error.statusText ?? "Unknown error",
+ });
+ }
+ return okAsync(response.data);
+ });
const user = result.match(
(data) => {
@@ -117,52 +115,20 @@ class AccountViewModel {
toast.error(this.errorMessage);
return false;
}
- const client = get(apiClient);
- if (!client) {
- this.passwordLoading = false;
- this.errorMessage = "API client not initialized";
- toast.error(this.errorMessage);
- return false;
- }
-
const result = await ResultAsync.fromPromise(
- client.users["rotate-password"].$put({
- json: { userId: currentUser.id, password },
- }),
+ rotatePasswordSC({ userId: currentUser.id, password }),
(error): Err => ({
code: "NETWORK_ERROR",
message: "Failed to change password",
description: "Network request failed",
detail: error instanceof Error ? error.message : String(error),
}),
- )
- .andThen((response) => {
- if (!response.ok) {
- return errAsync({
- code: "API_ERROR",
- message: "Failed to change password",
- description: `Response failed with status ${response.status}`,
- detail: `HTTP ${response.status}`,
- });
- }
- return ResultAsync.fromPromise(
- response.json(),
- (error): Err => ({
- code: "PARSING_ERROR",
- message: "Failed to parse response",
- description: "Invalid response format",
- detail:
- error instanceof Error
- ? error.message
- : String(error),
- }),
- ).andThen((apiResult: any) => {
- if (apiResult.error) {
- return errAsync(apiResult.error);
- }
- return okAsync(apiResult.data);
- });
- });
+ ).andThen((apiResult: any) => {
+ if (apiResult?.error) {
+ return errAsync(apiResult.error);
+ }
+ return okAsync(apiResult?.data);
+ });
const success = result.match(
() => {
@@ -170,7 +136,8 @@ class AccountViewModel {
return true;
},
(error) => {
- this.errorMessage = error.message ?? "Failed to change password";
+ this.errorMessage =
+ (error.message as string) ?? "Failed to change password";
toast.error(this.errorMessage, {
description: error.description,
});
diff --git a/apps/main/src/lib/domains/account/sessions/sessions.remote.ts b/apps/main/src/lib/domains/account/sessions/sessions.remote.ts
deleted file mode 100644
index e69de29..0000000
diff --git a/apps/main/src/lib/domains/notifications/notifications.remote.ts b/apps/main/src/lib/domains/notifications/notifications.remote.ts
index 0e105c8..1c8a8a4 100644
--- a/apps/main/src/lib/domains/notifications/notifications.remote.ts
+++ b/apps/main/src/lib/domains/notifications/notifications.remote.ts
@@ -3,27 +3,15 @@ import {
getNotificationsSchema,
} from "@pkg/logic/domains/notifications/data";
import { getNotificationController } from "@pkg/logic/domains/notifications/controller";
-import type { FlowExecCtx } from "@pkg/logic/core/flow.execution.context";
-import { getFlowExecCtxForRemoteFuncs } from "$lib/core/server.utils";
+import {
+ getFlowExecCtxForRemoteFuncs,
+ unauthorized,
+} from "$lib/core/server.utils";
import { command, getRequestEvent, query } from "$app/server";
-import type { Err } from "@pkg/result";
import * as v from "valibot";
const nc = getNotificationController();
-export async function unauthorized(fctx: FlowExecCtx) {
- return {
- data: null,
- error: {
- flowId: fctx.flowId,
- code: "UNAUTHORIZED",
- message: "User not authenticated",
- description: "Please log in",
- detail: "No user found in request locals",
- } as Err,
- };
-}
-
export const getNotificationsSQ = query(
getNotificationsSchema,
async (input) => {
diff --git a/apps/main/src/lib/domains/security/2fa-verify.vm.svelte.ts b/apps/main/src/lib/domains/security/2fa-verify.vm.svelte.ts
index db56d95..c5ed5ab 100644
--- a/apps/main/src/lib/domains/security/2fa-verify.vm.svelte.ts
+++ b/apps/main/src/lib/domains/security/2fa-verify.vm.svelte.ts
@@ -1,10 +1,12 @@
-import { apiClient, session, user } from "$lib/global.stores";
+import { session, user } from "$lib/global.stores";
import { authClient } from "$lib/auth.client";
+import {
+ startVerificationSessionSC,
+ verifySessionCodeSC,
+} from "$lib/domains/security/twofa.remote";
import { toast } from "svelte-sonner";
import { get } from "svelte/store";
import { page } from "$app/state";
-import { ResultAsync, errAsync, okAsync } from "neverthrow";
-import type { Err } from "@pkg/result";
class TwoFactorVerifyViewModel {
verifying = $state(false);
@@ -32,64 +34,33 @@ class TwoFactorVerifyViewModel {
return;
}
- const result = await ResultAsync.fromPromise(
- get(apiClient).twofactor["start-verification-session"].$post({
- json: { userId: uid, sessionId: sid },
- }),
- (error): Err => ({
- code: "NETWORK_ERROR",
- message: "Failed to start verification",
- description: "Network request failed",
- detail: error instanceof Error ? error.message : String(error),
- }),
- )
- .andThen((response) => {
- if (!response.ok) {
- return errAsync({
- code: "API_ERROR",
- message: "Failed to start verification",
- description: `Response failed with status ${response.status}`,
- detail: `HTTP ${response.status}`,
- });
- }
- return ResultAsync.fromPromise(
- response.json(),
- (error): Err => ({
- code: "PARSING_ERROR",
- message: "Failed to parse response",
- description: "Invalid response format",
- detail: error instanceof Error ? error.message : String(error),
- }),
- );
- })
- .andThen((apiResult) => {
- if (apiResult.error) {
- return errAsync(apiResult.error);
- }
- if (!apiResult.data?.verificationToken) {
- return errAsync({
- code: "API_ERROR",
- message: "No verification token received",
- description: "Invalid response data",
- detail: "Missing verificationToken in response",
- });
- }
- return okAsync(apiResult.data.verificationToken);
+ try {
+ const result = await startVerificationSessionSC({
+ userId: uid,
+ sessionId: sid,
});
- result.match(
- (token) => {
- this.verificationToken = token;
- },
- (error) => {
- this.errorMessage = error.message || "Failed to start verification";
+ if (result?.error || !result?.data?.verificationToken) {
+ this.errorMessage =
+ result?.error?.message || "Failed to start verification";
toast.error("Failed to start verification", {
- description: this.errorMessage || "Failed to start verification",
+ description: this.errorMessage,
});
- },
- );
+ return;
+ }
- this.startingVerification = false;
+ this.verificationToken = result.data.verificationToken;
+ } catch (error) {
+ this.errorMessage = "Failed to start verification";
+ toast.error("Failed to start verification", {
+ description:
+ error instanceof Error
+ ? error.message
+ : "Failed to start verification",
+ });
+ } finally {
+ this.startingVerification = false;
+ }
}
async verifyCode() {
@@ -106,69 +77,22 @@ class TwoFactorVerifyViewModel {
this.verifying = true;
this.errorMessage = null;
- const result = await ResultAsync.fromPromise(
- get(apiClient).twofactor["verify-session-code"].$post({
- json: {
- verificationToken: this.verificationToken,
- code: this.verificationCode,
- },
- }),
- (error): Err => ({
- code: "NETWORK_ERROR",
- message: "Verification failed",
- description: "Network request failed",
- detail: error instanceof Error ? error.message : String(error),
- }),
- )
- .andThen((response) => {
- if (!response.ok) {
- return errAsync({
- code: "API_ERROR",
- message: "Verification failed",
- description: `Response failed with status ${response.status}`,
- detail: `HTTP ${response.status}`,
- });
- }
- return ResultAsync.fromPromise(
- response.json(),
- (error): Err => ({
- code: "PARSING_ERROR",
- message: "Failed to parse response",
- description: "Invalid response format",
- detail: error instanceof Error ? error.message : String(error),
- }),
- );
- })
- .andThen((apiResult) => {
- if (apiResult.error) {
- return errAsync(apiResult.error);
- }
- if (!apiResult.data?.success) {
- return errAsync({
- code: "VALIDATION_ERROR",
- message: "Invalid verification code",
- description: "Verification failed",
- detail: "Code verification unsuccessful",
- });
- }
- return okAsync(apiResult.data);
+ try {
+ const result = await verifySessionCodeSC({
+ verificationToken: this.verificationToken,
+ code: this.verificationCode,
});
- result.match(
- () => {
- const redirectUrl = page.url.searchParams.get("redirect") || "/";
- window.location.href = redirectUrl;
- },
- (error) => {
- this.errorMessage = error.message || "Failed to verify code";
+ if (result?.error || !result?.data?.success) {
+ this.errorMessage =
+ result?.error?.message || "Failed to verify code";
- if (error.code === "BANNED") {
- authClient.signOut();
+ if (result?.error?.code === "BANNED") {
+ await authClient.signOut();
window.location.href = "/auth/login";
return;
}
- // Don't show toast for invalid codes, just show in UI
if (
this.errorMessage &&
!this.errorMessage.includes("Invalid") &&
@@ -178,10 +102,20 @@ class TwoFactorVerifyViewModel {
description: this.errorMessage,
});
}
- },
- );
+ return;
+ }
- this.verifying = false;
+ const redirectUrl = page.url.searchParams.get("redirect") || "/";
+ window.location.href = redirectUrl;
+ } catch (error) {
+ this.errorMessage = "Failed to verify code";
+ toast.error("Verification failed", {
+ description:
+ error instanceof Error ? error.message : this.errorMessage,
+ });
+ } finally {
+ this.verifying = false;
+ }
}
async handleBackupCode() {
@@ -193,65 +127,25 @@ class TwoFactorVerifyViewModel {
this.verifying = true;
this.errorMessage = null;
- const result = await ResultAsync.fromPromise(
- get(apiClient).twofactor["verify-session-code"].$post({
- json: {
- verificationToken: this.verificationToken,
- code: this.verificationCode,
- },
- }),
- (error): Err => ({
- code: "NETWORK_ERROR",
- message: "Verification failed",
- description: "Network request failed",
- detail: error instanceof Error ? error.message : String(error),
- }),
- )
- .andThen((response) => {
- if (!response.ok) {
- return errAsync({
- code: "API_ERROR",
- message: "Verification failed",
- description: `Response failed with status ${response.status}`,
- detail: `HTTP ${response.status}`,
- });
- }
- return ResultAsync.fromPromise(
- response.json(),
- (error): Err => ({
- code: "PARSING_ERROR",
- message: "Failed to parse response",
- description: "Invalid response format",
- detail: error instanceof Error ? error.message : String(error),
- }),
- );
- })
- .andThen((apiResult) => {
- if (apiResult.error) {
- return errAsync(apiResult.error);
- }
- if (!apiResult.data?.success) {
- return errAsync({
- code: "VALIDATION_ERROR",
- message: "Invalid backup code",
- description: "Verification failed",
- detail: "Backup code verification unsuccessful",
- });
- }
- return okAsync(apiResult.data);
+ try {
+ const result = await verifySessionCodeSC({
+ verificationToken: this.verificationToken,
+ code: this.verificationCode,
});
- result.match(
- () => {
- const redirectUrl = page.url.searchParams.get("redirect") || "/";
- window.location.href = redirectUrl;
- },
- (error) => {
- this.errorMessage = error.message || "Invalid backup code";
- },
- );
+ if (result?.error || !result?.data?.success) {
+ this.errorMessage = result?.error?.message || "Invalid backup code";
+ return;
+ }
- this.verifying = false;
+ const redirectUrl = page.url.searchParams.get("redirect") || "/";
+ window.location.href = redirectUrl;
+ } catch (error) {
+ this.errorMessage =
+ error instanceof Error ? error.message : "Invalid backup code";
+ } finally {
+ this.verifying = false;
+ }
}
reset() {
diff --git a/apps/main/src/lib/domains/security/2fa.vm.svelte.ts b/apps/main/src/lib/domains/security/2fa.vm.svelte.ts
index d1579db..b4c7420 100644
--- a/apps/main/src/lib/domains/security/2fa.vm.svelte.ts
+++ b/apps/main/src/lib/domains/security/2fa.vm.svelte.ts
@@ -1,9 +1,11 @@
-import { apiClient } from "$lib/global.stores";
+import {
+ disableTwoFactorSC,
+ generateBackupCodesSQ,
+ setupTwoFactorSC,
+ verifyAndEnableTwoFactorSC,
+} from "$lib/domains/security/twofa.remote";
import { toast } from "svelte-sonner";
-import { get } from "svelte/store";
import QRCode from "qrcode";
-import { ResultAsync, errAsync, okAsync } from "neverthrow";
-import type { Err } from "@pkg/result";
class TwoFactorViewModel {
twoFactorEnabled = $state(false);
@@ -20,86 +22,38 @@ class TwoFactorViewModel {
this.isLoading = true;
this.errorMessage = null;
- const result = await ResultAsync.fromPromise(
- get(apiClient).twofactor.setup.$post({
- json: { code: "" },
- }),
- (error): Err => ({
- code: "NETWORK_ERROR",
- message: "Failed to set up two-factor authentication.",
- description: "Network request failed",
- detail: error instanceof Error ? error.message : String(error),
- }),
- )
- .andThen((response) => {
- if (!response.ok) {
- return errAsync({
- code: "API_ERROR",
- message: "Failed to set up two-factor authentication.",
- description: `Response failed with status ${response.status}`,
- detail: `HTTP ${response.status}`,
- });
- }
- return ResultAsync.fromPromise(
- response.json(),
- (error): Err => ({
- code: "PARSING_ERROR",
- message: "Failed to parse response",
- description: "Invalid response format",
- detail: error instanceof Error ? error.message : String(error),
- }),
- );
- })
- .andThen((apiResult) => {
- if (apiResult.error) {
- return errAsync(apiResult.error);
- }
- if (!apiResult.data || !apiResult.data.totpURI) {
- return errAsync({
- code: "API_ERROR",
- message: "Failed to get 2FA setup information",
- description: "Invalid response data",
- detail: "Missing totpURI in response",
- });
- }
- return okAsync(apiResult.data);
- })
- .andThen((data) => {
- return ResultAsync.fromPromise(
- QRCode.toDataURL(data.totpURI, {
- width: 256,
- margin: 2,
- color: { dark: "#000000", light: "#FFFFFF" },
- }),
- (error): Err => ({
- code: "PROCESSING_ERROR",
- message: "Failed to generate QR code.",
- description: "QR code generation failed",
- detail: error instanceof Error ? error.message : String(error),
- }),
- ).map((qrCodeDataUrl) => ({
- ...data,
- qrCodeDataUrl,
- }));
+ try {
+ const result = await setupTwoFactorSC({});
+ if (result?.error || !result?.data?.totpURI) {
+ this.errorMessage =
+ result?.error?.message || "Could not enable 2FA";
+ toast.error(this.errorMessage, {
+ description:
+ result?.error?.description || "Please try again later",
+ });
+ return;
+ }
+
+ const qrCodeDataUrl = await QRCode.toDataURL(result.data.totpURI, {
+ width: 256,
+ margin: 2,
+ color: { dark: "#000000", light: "#FFFFFF" },
});
- result.match(
- ({ qrCodeDataUrl, secret, totpURI }) => {
- this.qrCodeUrl = qrCodeDataUrl;
- this.twoFactorSetupInProgress = true;
- this.twoFactorSecret = secret;
- this.twoFactorVerificationCode = "";
- toast("Setup enabled");
- },
- (error) => {
- this.errorMessage = error.message || "Could not enable 2FA";
- toast.error(this.errorMessage || "Could not enable 2FA", {
- description: error.description || "Please try again later",
- });
- },
- );
-
- this.isLoading = false;
+ this.qrCodeUrl = qrCodeDataUrl;
+ this.twoFactorSetupInProgress = true;
+ this.twoFactorSecret = result.data.secret;
+ this.twoFactorVerificationCode = "";
+ toast("Setup enabled");
+ } catch (error) {
+ this.errorMessage = "Could not enable 2FA";
+ toast.error(this.errorMessage, {
+ description:
+ error instanceof Error ? error.message : "Please try again later",
+ });
+ } finally {
+ this.isLoading = false;
+ }
}
async completeTwoFactorSetup() {
@@ -112,233 +66,109 @@ class TwoFactorViewModel {
this.isLoading = true;
this.errorMessage = null;
- const verifyResult = await ResultAsync.fromPromise(
- get(apiClient).twofactor["verify-and-enable"].$post({
- json: { code: this.twoFactorVerificationCode },
- }),
- (error): Err => ({
- code: "NETWORK_ERROR",
- message: "Failed to verify code.",
- description: "Network request failed",
- detail: error instanceof Error ? error.message : String(error),
- }),
- )
- .andThen((response) => {
- if (!response.ok) {
- return errAsync({
- code: "API_ERROR",
- message: "Failed to verify code.",
- description: `Response failed with status ${response.status}`,
- detail: `HTTP ${response.status}`,
- });
- }
- return ResultAsync.fromPromise(
- response.json(),
- (error): Err => ({
- code: "PARSING_ERROR",
- message: "Failed to parse response",
- description: "Invalid response format",
- detail: error instanceof Error ? error.message : String(error),
- }),
- );
- })
- .andThen((apiResult) => {
- if (apiResult.error) {
- return errAsync(apiResult.error);
- }
- return okAsync(apiResult.data);
+ try {
+ const verifyResult = await verifyAndEnableTwoFactorSC({
+ code: this.twoFactorVerificationCode,
});
- verifyResult.match(
- async () => {
- const backupCodesResult = await ResultAsync.fromPromise(
- get(apiClient).twofactor["generate-backup-codes"].$get(),
- (error): Err => ({
- code: "NETWORK_ERROR",
- message: "Failed to generate backup codes",
- description: "Network request failed",
- detail: error instanceof Error ? error.message : String(error),
- }),
- )
- .andThen((response) => {
- if (!response.ok) {
- return errAsync({
- code: "API_ERROR",
- message: "Failed to generate backup codes",
- description: `Response failed with status ${response.status}`,
- detail: `HTTP ${response.status}`,
- });
- }
- return ResultAsync.fromPromise(
- response.json(),
- (error): Err => ({
- code: "PARSING_ERROR",
- message: "Failed to parse response",
- description: "Invalid response format",
- detail: error instanceof Error ? error.message : String(error),
- }),
- );
- })
- .andThen((apiResult) => {
- if (apiResult.error) {
- return errAsync(apiResult.error);
- }
- return okAsync(apiResult.data || []);
- });
-
- backupCodesResult.match(
- (backupCodes) => {
- this.backupCodes = backupCodes;
- this.showingBackupCodes = true;
- },
- () => {
- toast.error(
- "2FA enabled, but failed to generate backup codes",
- {
- description: "You can generate them later in settings",
- },
- );
- },
- );
-
- this.twoFactorEnabled = true;
- this.twoFactorSetupInProgress = false;
- this.twoFactorVerificationCode = "";
- toast.success("Two-factor authentication enabled", {
- description: "Your account is now more secure",
+ if (verifyResult?.error) {
+ this.errorMessage =
+ verifyResult.error.message || "Invalid verification code";
+ toast.error(this.errorMessage, {
+ description:
+ verifyResult.error.description || "Please try again",
});
- },
- (error) => {
- this.errorMessage = error.message || "Invalid verification code";
- toast.error(this.errorMessage || "Invalid verification code", {
- description: error.description || "Please try again",
- });
- },
- );
+ return;
+ }
- this.isLoading = false;
+ const backupCodesResult = await generateBackupCodesSQ();
+ if (backupCodesResult?.error || !Array.isArray(backupCodesResult?.data)) {
+ toast.error("2FA enabled, but failed to generate backup codes", {
+ description: "You can generate them later in settings",
+ });
+ } else {
+ this.backupCodes = backupCodesResult.data;
+ this.showingBackupCodes = true;
+ }
+
+ this.twoFactorEnabled = true;
+ this.twoFactorSetupInProgress = false;
+ this.twoFactorVerificationCode = "";
+ toast.success("Two-factor authentication enabled", {
+ description: "Your account is now more secure",
+ });
+ } catch (error) {
+ this.errorMessage = "Invalid verification code";
+ toast.error(this.errorMessage, {
+ description:
+ error instanceof Error ? error.message : "Please try again",
+ });
+ } finally {
+ this.isLoading = false;
+ }
}
async disableTwoFactor() {
this.isLoading = true;
this.errorMessage = null;
- const result = await ResultAsync.fromPromise(
- get(apiClient).twofactor.disable.$delete({
- json: { code: "" },
- }),
- (error): Err => ({
- code: "NETWORK_ERROR",
- message: "Failed to disable two-factor authentication.",
- description: "Network request failed",
- detail: error instanceof Error ? error.message : String(error),
- }),
- )
- .andThen((response) => {
- if (!response.ok) {
- return errAsync({
- code: "API_ERROR",
- message: "Failed to disable two-factor authentication.",
- description: `Response failed with status ${response.status}`,
- detail: `HTTP ${response.status}`,
- });
- }
- return ResultAsync.fromPromise(
- response.json(),
- (error): Err => ({
- code: "PARSING_ERROR",
- message: "Failed to parse response",
- description: "Invalid response format",
- detail: error instanceof Error ? error.message : String(error),
- }),
- );
- })
- .andThen((apiResult) => {
- if (apiResult.error) {
- return errAsync(apiResult.error);
- }
- return okAsync(apiResult.data);
- });
-
- result.match(
- () => {
- this.twoFactorEnabled = false;
- this.backupCodes = [];
- this.qrCodeUrl = null;
- this.showingBackupCodes = false;
- this.twoFactorSecret = null;
- toast.success("Two-factor authentication disabled");
- },
- (error) => {
- this.errorMessage = error.message || "Failed to disable 2FA";
- toast.error(this.errorMessage || "Failed to disable 2FA", {
- description:
- error.description || "Please try again later",
+ try {
+ const result = await disableTwoFactorSC({ code: "" });
+ if (result?.error) {
+ this.errorMessage = result.error.message || "Failed to disable 2FA";
+ toast.error(this.errorMessage, {
+ description: result.error.description || "Please try again later",
});
- },
- );
+ return;
+ }
- this.isLoading = false;
+ this.twoFactorEnabled = false;
+ this.backupCodes = [];
+ this.qrCodeUrl = null;
+ this.showingBackupCodes = false;
+ this.twoFactorSecret = null;
+ toast.success("Two-factor authentication disabled");
+ } catch (error) {
+ this.errorMessage = "Failed to disable 2FA";
+ toast.error(this.errorMessage, {
+ description:
+ error instanceof Error ? error.message : "Please try again later",
+ });
+ } finally {
+ this.isLoading = false;
+ }
}
async generateNewBackupCodes() {
this.isLoading = true;
this.errorMessage = null;
- const result = await ResultAsync.fromPromise(
- get(apiClient).twofactor["generate-backup-codes"].$get(),
- (error): Err => ({
- code: "NETWORK_ERROR",
- message: "Failed to generate new backup codes.",
- description: "Network request failed",
- detail: error instanceof Error ? error.message : String(error),
- }),
- )
- .andThen((response) => {
- if (!response.ok) {
- return errAsync({
- code: "API_ERROR",
- message: "Failed to generate new backup codes.",
- description: `Response failed with status ${response.status}`,
- detail: `HTTP ${response.status}`,
- });
- }
- return ResultAsync.fromPromise(
- response.json(),
- (error): Err => ({
- code: "PARSING_ERROR",
- message: "Failed to parse response",
- description: "Invalid response format",
- detail: error instanceof Error ? error.message : String(error),
- }),
- );
- })
- .andThen((apiResult) => {
- if (apiResult.error) {
- return errAsync(apiResult.error);
- }
- return okAsync(apiResult.data || []);
- });
-
- result.match(
- (backupCodes) => {
- this.backupCodes = backupCodes;
- this.showingBackupCodes = true;
- toast.success("New backup codes generated", {
- description: "Your previous backup codes are now invalid",
- });
- },
- (error) => {
+ try {
+ const result = await generateBackupCodesSQ();
+ if (result?.error || !Array.isArray(result?.data)) {
this.errorMessage =
- error.message || "Failed to generate new backup codes";
- toast.error(this.errorMessage || "Failed to generate new backup codes", {
+ result?.error?.message || "Failed to generate new backup codes";
+ toast.error(this.errorMessage, {
description:
- error.description || "Please try again later",
+ result?.error?.description || "Please try again later",
});
- },
- );
+ return;
+ }
- this.isLoading = false;
+ this.backupCodes = result.data;
+ this.showingBackupCodes = true;
+ toast.success("New backup codes generated", {
+ description: "Your previous backup codes are now invalid",
+ });
+ } catch (error) {
+ this.errorMessage = "Failed to generate new backup codes";
+ toast.error(this.errorMessage, {
+ description:
+ error instanceof Error ? error.message : "Please try again later",
+ });
+ } finally {
+ this.isLoading = false;
+ }
}
copyAllBackupCodes() {
diff --git a/apps/main/src/lib/domains/security/twofa.remote.ts b/apps/main/src/lib/domains/security/twofa.remote.ts
index e69de29..a8606d7 100644
--- a/apps/main/src/lib/domains/security/twofa.remote.ts
+++ b/apps/main/src/lib/domains/security/twofa.remote.ts
@@ -0,0 +1,205 @@
+import {
+ disable2FASchema,
+ enable2FACodeSchema,
+ startVerificationSchema,
+ verifyCodeSchema,
+} from "@pkg/logic/domains/2fa/data";
+import {
+ getFlowExecCtxForRemoteFuncs,
+ unauthorized,
+} from "$lib/core/server.utils";
+import { getTwofaController } from "@pkg/logic/domains/2fa/controller";
+import { command, getRequestEvent, query } from "$app/server";
+import { auth } from "@pkg/logic/domains/auth/config.base";
+import type { User } from "@pkg/logic/domains/user/data";
+import * as v from "valibot";
+
+const tc = getTwofaController();
+
+function buildIpAddress(headers: Headers) {
+ return (
+ headers.get("x-forwarded-for") ?? headers.get("x-real-ip") ?? "unknown"
+ );
+}
+
+function buildUserAgent(headers: Headers) {
+ return headers.get("user-agent") ?? "unknown";
+}
+
+export const setupTwoFactorSC = command(v.object({}), async () => {
+ const event = getRequestEvent();
+ const fctx = await getFlowExecCtxForRemoteFuncs(event.locals);
+ const currentUser = event.locals.user;
+
+ if (!fctx.userId || !currentUser) {
+ return unauthorized(fctx);
+ }
+
+ const res = await tc.setup2FA(fctx, currentUser as User);
+ return res.isOk()
+ ? { data: res.value, error: null }
+ : { data: null, error: res.error };
+});
+
+export const verifyAndEnableTwoFactorSC = command(
+ enable2FACodeSchema,
+ async (payload) => {
+ const event = getRequestEvent();
+ const fctx = await getFlowExecCtxForRemoteFuncs(event.locals);
+ const currentUser = event.locals.user;
+
+ if (!fctx.userId || !currentUser) {
+ return unauthorized(fctx);
+ }
+
+ const res = await tc.verifyAndEnable2FA(
+ fctx,
+ currentUser as User,
+ payload.code,
+ event.request.headers,
+ );
+
+ return res.isOk()
+ ? { data: res.value, error: null }
+ : { data: null, error: res.error };
+ },
+);
+
+export const generateBackupCodesSQ = query(async () => {
+ const event = getRequestEvent();
+ const fctx = await getFlowExecCtxForRemoteFuncs(event.locals);
+ const currentUser = event.locals.user;
+
+ if (!fctx.userId || !currentUser) {
+ return unauthorized(fctx);
+ }
+
+ const res = await tc.generateBackupCodes(fctx, currentUser as User);
+ return res.isOk()
+ ? { data: res.value, error: null }
+ : { data: null, error: res.error };
+});
+
+export const disableTwoFactorSC = command(disable2FASchema, async (payload) => {
+ const event = getRequestEvent();
+ const fctx = await getFlowExecCtxForRemoteFuncs(event.locals);
+ const currentUser = event.locals.user;
+
+ if (!fctx.userId || !currentUser) {
+ return unauthorized(fctx);
+ }
+
+ const res = await tc.disable(fctx, currentUser as User, payload.code);
+ return res.isOk()
+ ? { data: res.value, error: null }
+ : { data: null, error: res.error };
+});
+
+export const requiresVerificationSQ = query(
+ v.object({ sessionId: v.string() }),
+ async (input) => {
+ const event = getRequestEvent();
+ const fctx = await getFlowExecCtxForRemoteFuncs(event.locals);
+ const currentUser = event.locals.user;
+
+ if (!fctx.userId || !currentUser) {
+ return unauthorized(fctx);
+ }
+
+ const res = await tc.requiresInitialVerification(
+ fctx,
+ currentUser as User,
+ input.sessionId,
+ );
+
+ return res.isOk()
+ ? { data: res.value, error: null }
+ : { data: null, error: res.error };
+ },
+);
+
+export const requiresSensitiveActionSQ = query(async () => {
+ const event = getRequestEvent();
+ const fctx = await getFlowExecCtxForRemoteFuncs(event.locals);
+ const currentUser = event.locals.user;
+
+ if (!fctx.userId || !currentUser) {
+ return unauthorized(fctx);
+ }
+
+ const res = await tc.requiresSensitiveActionVerification(
+ fctx,
+ currentUser as User,
+ );
+
+ return res.isOk()
+ ? { data: res.value, error: null }
+ : { data: null, error: res.error };
+});
+
+export const startVerificationSessionSC = command(
+ startVerificationSchema,
+ async (payload) => {
+ const event = getRequestEvent();
+ const fctx = await getFlowExecCtxForRemoteFuncs(event.locals);
+
+ if (!fctx.userId) {
+ return unauthorized(fctx);
+ }
+
+ const res = await tc.startVerification(fctx, {
+ userId: payload.userId,
+ sessionId: payload.sessionId,
+ ipAddress: buildIpAddress(event.request.headers),
+ userAgent: buildUserAgent(event.request.headers),
+ });
+
+ return res.isOk()
+ ? { data: res.value, error: null }
+ : { data: null, error: res.error };
+ },
+);
+
+export const verifySessionCodeSC = command(
+ verifyCodeSchema,
+ async (payload) => {
+ const event = getRequestEvent();
+ const fctx = await getFlowExecCtxForRemoteFuncs(event.locals);
+
+ let currentUser = event.locals.user;
+
+ if (!currentUser) {
+ const sess = await auth.api.getSession({
+ headers: event.request.headers,
+ });
+ currentUser = sess?.user as User | undefined;
+ }
+
+ const res = await tc.verifyCode(
+ fctx,
+ {
+ verificationSessToken: payload.verificationToken,
+ code: payload.code,
+ },
+ currentUser as User | undefined,
+ );
+
+ return res.isOk()
+ ? { data: res.value, error: null }
+ : { data: null, error: res.error };
+ },
+);
+
+export const cleanupExpiredSessionsSC = command(v.object({}), async () => {
+ const event = getRequestEvent();
+ const fctx = await getFlowExecCtxForRemoteFuncs(event.locals);
+
+ if (!fctx.userId) {
+ return unauthorized(fctx);
+ }
+
+ const res = await tc.cleanupExpiredSessions(fctx);
+ return res.isOk()
+ ? { data: res.value, error: null }
+ : { data: null, error: res.error };
+});
diff --git a/apps/main/src/routes/(main)/+page.svelte b/apps/main/src/routes/(main)/+page.svelte
index 93b9d02..dcb920b 100644
--- a/apps/main/src/routes/(main)/+page.svelte
+++ b/apps/main/src/routes/(main)/+page.svelte
@@ -2,13 +2,7 @@
import { goto } from "$app/navigation";
import { mainNavTree } from "$lib/core/constants";
import { breadcrumbs } from "$lib/global.stores";
- import { makeClient } from "$lib/make-client.js";
import { onMount } from "svelte";
- import type { PageData } from "./$types";
-
- let { data }: { data: PageData } = $props();
-
- const client = makeClient(fetch);
breadcrumbs.set([mainNavTree[0]]);
diff --git a/apps/main/src/routes/+layout.svelte b/apps/main/src/routes/+layout.svelte
index 345a779..a374c05 100644
--- a/apps/main/src/routes/+layout.svelte
+++ b/apps/main/src/routes/+layout.svelte
@@ -1,25 +1,11 @@
@@ -31,6 +17,4 @@
-
- {@render children()}
-
+{@render children()}
diff --git a/packages/logic/domains/2fa/router.ts b/packages/logic/domains/2fa/router.ts
deleted file mode 100644
index d682399..0000000
--- a/packages/logic/domains/2fa/router.ts
+++ /dev/null
@@ -1,170 +0,0 @@
-import {
- disable2FASchema,
- enable2FACodeSchema,
- startVerificationSchema,
- verifyCodeSchema,
-} from "./data";
-import { sValidator } from "@hono/standard-validator";
-import { HonoContext } from "@/core/hono.helpers";
-import { getTwofaController } from "./controller";
-import { auth } from "@domains/auth/config.base";
-import { Hono } from "hono";
-
-const twofaController = getTwofaController();
-
-export const twofaRouter = new Hono()
- .post("/setup", async (c) => {
- const res = await twofaController.setup2FA(
- c.env.locals.fCtx,
- c.env.locals.user,
- );
- return c.json(
- res.isOk()
- ? { data: res.value, error: null }
- : { data: null, error: res.error },
- res.isOk() ? 200 : 400,
- );
- })
- .post(
- "/verify-and-enable",
- sValidator("json", enable2FACodeSchema),
- async (c) => {
- const data = c.req.valid("json");
- const res = await twofaController.verifyAndEnable2FA(
- c.env.locals.fCtx,
- c.env.locals.user,
- data.code,
- c.req.raw.headers,
- );
- return c.json(
- res.isOk()
- ? { data: res.value, error: null }
- : { data: null, error: res.error },
- res.isOk() ? 200 : 400,
- );
- },
- )
- .get("/generate-backup-codes", async (c) => {
- const res = await twofaController.generateBackupCodes(
- c.env.locals.fCtx,
- c.env.locals.user,
- );
- return c.json(
- res.isOk()
- ? { data: res.value, error: null }
- : { data: null, error: res.error },
- res.isOk() ? 200 : 400,
- );
- })
- .delete("/disable", sValidator("json", disable2FASchema), async (c) => {
- const data = c.req.valid("json");
- const res = await twofaController.disable(
- c.env.locals.fCtx,
- c.env.locals.user,
- data.code,
- );
- return c.json(
- res.isOk()
- ? { data: res.value, error: null }
- : { data: null, error: res.error },
- res.isOk() ? 200 : 400,
- );
- })
- .get("/requires-verification", async (c) => {
- const user = c.env.locals.user;
- const sessionId = c.req.query("sessionId")?.toString() ?? "";
- const res = await twofaController.requiresInitialVerification(
- c.env.locals.fCtx,
- user,
- sessionId,
- );
- return c.json(
- res.isOk()
- ? { data: res.value, error: null }
- : { data: null, error: res.error },
- res.isOk() ? 200 : 400,
- );
- })
- .get("/requires-sensitive-action", async (c) => {
- const res = await twofaController.requiresSensitiveActionVerification(
- c.env.locals.fCtx,
- c.env.locals.user,
- );
- return c.json(
- res.isOk()
- ? { data: res.value, error: null }
- : { data: null, error: res.error },
- res.isOk() ? 200 : 400,
- );
- })
- .post(
- "/start-verification-session",
- sValidator("json", startVerificationSchema),
- async (c) => {
- const data = c.req.valid("json");
-
- const ipAddress =
- c.req.header("x-forwarded-for") ||
- c.req.header("x-real-ip") ||
- "unknown";
- const userAgent = c.req.header("user-agent") || "unknown";
-
- const res = await twofaController.startVerification(
- c.env.locals.fCtx,
- {
- userId: data.userId,
- sessionId: data.sessionId,
- ipAddress,
- userAgent,
- },
- );
-
- return c.json(
- res.isOk()
- ? { data: res.value, error: null }
- : { data: null, error: res.error },
- );
- },
- )
- .post(
- "/verify-session-code",
- sValidator("json", verifyCodeSchema),
- async (c) => {
- const data = c.req.valid("json");
-
- let user = c.env.locals.user;
- if (!user) {
- const out = await auth.api.getSession({
- headers: c.req.raw.headers,
- });
- user = out?.user as any;
- }
-
- const res = await twofaController.verifyCode(
- c.env.locals.fCtx,
- {
- verificationSessToken: data.verificationToken,
- code: data.code,
- },
- user,
- );
-
- return c.json(
- res.isOk()
- ? { data: res.value, error: null }
- : { data: null, error: res.error },
- res.isOk() ? 200 : 400,
- );
- },
- )
- .post("/cleanup-expired-sessions", async (c) => {
- const res = await twofaController.cleanupExpiredSessions(
- c.env.locals.fCtx,
- );
- return c.json(
- res.isOk()
- ? { data: res.value, error: null }
- : { data: null, error: res.error },
- res.isOk() ? 200 : 400,
- );
- });
diff --git a/packages/logic/domains/user/router.ts b/packages/logic/domains/user/router.ts
deleted file mode 100644
index 6e9e91e..0000000
--- a/packages/logic/domains/user/router.ts
+++ /dev/null
@@ -1,165 +0,0 @@
-import {
- banUserSchema,
- checkUsernameSchema,
- ensureAccountExistsSchema,
- rotatePasswordSchema,
-} from "./data";
-import { HonoContext } from "@core/hono.helpers";
-import { sValidator } from "@hono/standard-validator";
-import { getUserController } from "./controller";
-import { Hono } from "hono";
-
-const uc = getUserController();
-
-export const usersRouter = new Hono()
- // Get current user info
- .get("/me", async (c) => {
- const fctx = c.env.locals.fCtx;
- const userId = c.env.locals.user?.id;
-
- if (!userId) {
- return c.json(
- {
- error: {
- code: "UNAUTHORIZED",
- message: "User not authenticated",
- description: "Please log in",
- detail: "No user ID found in session",
- },
- },
- 401,
- );
- }
-
- const res = await uc.getUserInfo(fctx, userId);
- return c.json(
- res.isOk()
- ? { data: res.value, error: null }
- : { data: null, error: res.error },
- res.isOk() ? 200 : 400,
- );
- })
-
- // Get user info by ID
- .get("/:userId", async (c) => {
- const fctx = c.env.locals.fCtx;
- const userId = c.req.param("userId");
-
- const res = await uc.getUserInfo(fctx, userId);
- return c.json(
- res.isOk()
- ? { data: res.value, error: null }
- : { data: null, error: res.error },
- res.isOk() ? 200 : 400,
- );
- })
-
- // Ensure account exists
- .put(
- "/ensure-account-exists",
- sValidator("json", ensureAccountExistsSchema),
- async (c) => {
- const fctx = c.env.locals.fCtx;
- const data = c.req.valid("json");
-
- const res = await uc.ensureAccountExists(fctx, data.userId);
- return c.json(
- res.isOk()
- ? { data: res.value, error: null }
- : { data: null, error: res.error },
- res.isOk() ? 200 : 400,
- );
- },
- )
-
- // Check username availability
- .post(
- "/check-username",
- sValidator("json", checkUsernameSchema),
- async (c) => {
- const fctx = c.env.locals.fCtx;
- const data = c.req.valid("json");
-
- const res = await uc.isUsernameAvailable(fctx, data.username);
- return c.json(
- res.isOk()
- ? { data: res.value, error: null }
- : { data: null, error: res.error },
- res.isOk() ? 200 : 400,
- );
- },
- )
-
- // Update last 2FA verification time
- .put("/update-2fa-verified/:userId", async (c) => {
- const fctx = c.env.locals.fCtx;
- const userId = c.req.param("userId");
-
- const res = await uc.updateLastVerified2FaAtToNow(fctx, userId);
- return c.json(
- res.isOk()
- ? { data: res.value, error: null }
- : { data: null, error: res.error },
- res.isOk() ? 200 : 400,
- );
- })
-
- // Ban user
- .post("/ban", sValidator("json", banUserSchema), async (c) => {
- const fctx = c.env.locals.fCtx;
- const data = c.req.valid("json");
-
- const res = await uc.banUser(fctx, data.userId, data.reason, data.banExpiresAt);
- return c.json(
- res.isOk()
- ? { data: res.value, error: null }
- : { data: null, error: res.error },
- res.isOk() ? 200 : 400,
- );
- })
-
- // Check if user is banned
- .get("/:userId/is-banned", async (c) => {
- const fctx = c.env.locals.fCtx;
- const userId = c.req.param("userId");
-
- const res = await uc.isUserBanned(fctx, userId);
- return c.json(
- res.isOk()
- ? { data: res.value, error: null }
- : { data: null, error: res.error },
- res.isOk() ? 200 : 400,
- );
- })
-
- // Get ban info
- .get("/:userId/ban-info", async (c) => {
- const fctx = c.env.locals.fCtx;
- const userId = c.req.param("userId");
-
- const res = await uc.getBanInfo(fctx, userId);
- return c.json(
- res.isOk()
- ? { data: res.value, error: null }
- : { data: null, error: res.error },
- res.isOk() ? 200 : 400,
- );
- })
-
- // Rotate password
- .put(
- "/rotate-password",
- sValidator("json", rotatePasswordSchema),
- async (c) => {
- const fctx = c.env.locals.fCtx;
- const data = c.req.valid("json");
-
- const res = await uc.rotatePassword(fctx, data.userId, data.password);
- return c.json(
- res.isOk()
- ? { data: res.value, error: null }
- : { data: null, error: res.error },
- res.isOk() ? 200 : 400,
- );
- },
- );
diff --git a/packages/logic/package.json b/packages/logic/package.json
index 9d3acd7..0aeeca0 100644
--- a/packages/logic/package.json
+++ b/packages/logic/package.json
@@ -4,14 +4,13 @@
"auth:schemagen": "pnpm dlx @better-auth/cli generate --config ./domains/auth/config.base.ts --output ../../packages/db/schema/better.auth.schema.ts"
},
"dependencies": {
- "@hono/standard-validator": "^0.2.1",
"@opentelemetry/api": "^1.9.0",
"@otplib/plugin-base32-scure": "^13.3.0",
"@otplib/plugin-crypto-noble": "^13.3.0",
"@otplib/totp": "^13.3.0",
"@pkg/db": "workspace:*",
- "@pkg/logger": "workspace:*",
"@pkg/keystore": "workspace:*",
+ "@pkg/logger": "workspace:*",
"@pkg/result": "workspace:*",
"@pkg/settings": "workspace:*",
"@types/pdfkit": "^0.14.0",
@@ -19,17 +18,13 @@
"better-auth": "^1.4.7",
"date-fns-tz": "^3.2.0",
"dotenv": "^16.5.0",
- "hono": "^4.11.1",
"imapflow": "^1.0.188",
"mailparser": "^3.7.3",
"nanoid": "^5.1.5",
"neverthrow": "^8.2.0",
"otplib": "^13.3.0",
- "pdfkit": "^0.17.1",
- "tmp": "^0.2.3",
"uuid": "^11.1.0",
- "valibot": "^1.2.0",
- "xlsx": "^0.18.5"
+ "valibot": "^1.2.0"
},
"devDependencies": {
"@types/bun": "latest",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1f3429c..fd1ebd2 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -65,24 +65,15 @@ importers:
'@pkg/settings':
specifier: workspace:*
version: link:../../packages/settings
- '@tanstack/svelte-query':
- specifier: ^6.0.18
- version: 6.0.18(svelte@5.53.6)
better-auth:
specifier: ^1.4.20
version: 1.4.20(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))
date-fns:
specifier: ^4.1.0
version: 4.1.0
- hono:
- specifier: ^4.11.1
- version: 4.12.3
import-in-the-middle:
specifier: ^3.0.0
version: 3.0.0
- marked:
- specifier: ^17.0.1
- version: 17.0.3
nanoid:
specifier: ^5.1.6
version: 5.1.6
@@ -289,9 +280,6 @@ importers:
packages/logic:
dependencies:
- '@hono/standard-validator':
- specifier: ^0.2.1
- version: 0.2.2(@standard-schema/spec@1.1.0)(hono@4.12.3)
'@opentelemetry/api':
specifier: ^1.9.0
version: 1.9.0
@@ -334,9 +322,6 @@ importers:
dotenv:
specifier: ^16.5.0
version: 16.6.1
- hono:
- specifier: ^4.11.1
- version: 4.12.3
imapflow:
specifier: ^1.0.188
version: 1.2.10
@@ -352,12 +337,6 @@ importers:
otplib:
specifier: ^13.3.0
version: 13.3.0
- pdfkit:
- specifier: ^0.17.1
- version: 0.17.2
- tmp:
- specifier: ^0.2.3
- version: 0.2.5
typescript:
specifier: ^5.9.3
version: 5.9.3
@@ -367,9 +346,6 @@ importers:
valibot:
specifier: ^1.2.0
version: 1.2.0(typescript@5.9.3)
- xlsx:
- specifier: ^0.18.5
- version: 0.18.5
devDependencies:
'@types/bun':
specifier: latest
@@ -930,12 +906,6 @@ packages:
peerDependencies:
hono: ^4
- '@hono/standard-validator@0.2.2':
- resolution: {integrity: sha512-mJ7W84Bt/rSvoIl63Ynew+UZOHAzzRAoAXb3JaWuxAkM/Lzg+ZHTCUiz77KOtn2e623WNN8LkD57Dk0szqUrIw==}
- peerDependencies:
- '@standard-schema/spec': ^1.0.0
- hono: '>=3.9.0'
-
'@iconify/json@2.2.444':
resolution: {integrity: sha512-z0UwFaVtaN/h/iWZ1kzEjqFU3sp0rRy93tzOtpepZU89DY39WsNeYZv2mxtft/2La6Bz2b4z1C/HkU5Cqv3gbw==}
@@ -1872,14 +1842,6 @@ packages:
peerDependencies:
vite: ^5.2.0 || ^6 || ^7
- '@tanstack/query-core@5.90.20':
- resolution: {integrity: sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==}
-
- '@tanstack/svelte-query@6.0.18':
- resolution: {integrity: sha512-iGS8osfrIVUW5pkV4Ig6pspNIMtiNjGnVTNJKDas0m/QaNDFFIKbgg74rCzcjwrTIvO38tMpzb4VUKklvAmjxw==}
- peerDependencies:
- svelte: ^5.25.0
-
'@tanstack/table-core@8.21.3':
resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==}
engines: {node: '>=12'}
@@ -2030,10 +1992,6 @@ packages:
engines: {node: '>=0.4.0'}
hasBin: true
- adler-32@1.3.1:
- resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==}
- engines: {node: '>=0.8'}
-
agent-base@7.1.4:
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
engines: {node: '>= 14'}
@@ -2086,13 +2044,6 @@ packages:
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
- base64-js@0.0.8:
- resolution: {integrity: sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==}
- engines: {node: '>= 0.4'}
-
- base64-js@1.5.1:
- resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
-
better-auth@1.4.20:
resolution: {integrity: sha512-cUQaUhZ/EZwb7xgoL9wHl78yWp0eaxC/L++B/r8RJxk23L766Tk7fLjWG6bQK8eAHDDpfQNwXsJowiei8tJWJw==}
peerDependencies:
@@ -2176,9 +2127,6 @@ packages:
brace-expansion@2.0.2:
resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
- brotli@1.3.3:
- resolution: {integrity: sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==}
-
buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
@@ -2193,10 +2141,6 @@ packages:
resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==}
engines: {node: '>=16'}
- cfb@1.2.2:
- resolution: {integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==}
- engines: {node: '>=0.8'}
-
chai@6.2.2:
resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==}
engines: {node: '>=18'}
@@ -2218,10 +2162,6 @@ packages:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
- clone@2.1.2:
- resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==}
- engines: {node: '>=0.8'}
-
clsx@2.1.1:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'}
@@ -2230,10 +2170,6 @@ packages:
resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}
engines: {node: '>=0.10.0'}
- codepage@1.15.0:
- resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==}
- engines: {node: '>=0.8'}
-
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
@@ -2274,18 +2210,10 @@ packages:
resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==}
engines: {node: '>= 0.6'}
- crc-32@1.2.2:
- resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==}
- engines: {node: '>=0.8'}
- hasBin: true
-
cross-spawn@7.0.6:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
- crypto-js@4.2.0:
- resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
-
cssesc@3.0.0:
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
engines: {node: '>=4'}
@@ -2447,9 +2375,6 @@ packages:
devalue@5.6.3:
resolution: {integrity: sha512-nc7XjUU/2Lb+SvEFVGcWLiKkzfw8+qHI7zn8WYXKkLMgfGSHbgCEaR6bJpev8Cm6Rmrb19Gfd/tZvGqx9is3wg==}
- dfa@1.2.0:
- resolution: {integrity: sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==}
-
dijkstrajs@1.0.3:
resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
@@ -2666,9 +2591,6 @@ packages:
resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==}
engines: {node: '>=8.0.0'}
- fast-deep-equal@3.1.3:
- resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
-
fdir@6.5.0:
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
engines: {node: '>=12.0.0'}
@@ -2692,9 +2614,6 @@ packages:
fn.name@1.1.0:
resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==}
- fontkit@2.0.4:
- resolution: {integrity: sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==}
-
foreground-child@3.3.1:
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
engines: {node: '>=14'}
@@ -2713,10 +2632,6 @@ packages:
forwarded-parse@2.1.2:
resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==}
- frac@1.1.2:
- resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==}
- engines: {node: '>=0.8'}
-
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -2851,10 +2766,6 @@ packages:
jose@6.1.3:
resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==}
- jpeg-exif@1.1.4:
- resolution: {integrity: sha512-a+bKEcCjtuW5WTdgeXFzswSrdqi0jk4XlEtZlx5A94wCoBpFjfFTbo/Tra5SpNCl/YFZPvcV1dJc+TAYeg6ROQ==}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
-
json-bigint@1.0.0:
resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==}
@@ -2967,9 +2878,6 @@ packages:
resolution: {integrity: sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==}
engines: {node: '>= 12.0.0'}
- linebreak@1.1.0:
- resolution: {integrity: sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==}
-
linkify-it@5.0.0:
resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
@@ -3016,11 +2924,6 @@ packages:
mailparser@3.9.3:
resolution: {integrity: sha512-AnB0a3zROum6fLaa52L+/K2SoRJVyFDk78Ea6q1D0ofcZLxWEWDtsS1+OrVqKbV7r5dulKL/AwYQccFGAPpuYQ==}
- marked@17.0.3:
- resolution: {integrity: sha512-jt1v2ObpyOKR8p4XaUJVk3YWRJ5n+i4+rjQopxvV32rSndTJXvIzuUdWWIy/1pFQMkQmvTXawzDNqOH/CUmx6A==}
- engines: {node: '>= 20'}
- hasBin: true
-
memoize-weak@1.0.2:
resolution: {integrity: sha512-gj39xkrjEw7nCn4nJ1M5ms6+MyMlyiGmttzsqAUsAKn6bYKwuTHh/AO3cKPF8IBrTIYTxb0wWXFs3E//Y8VoWQ==}
@@ -3144,9 +3047,6 @@ packages:
package-manager-detector@1.6.0:
resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==}
- pako@0.2.9:
- resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==}
-
paneforge@1.0.2:
resolution: {integrity: sha512-KzmIXQH1wCfwZ4RsMohD/IUtEjVhteR+c+ulb/CHYJHX8SuDXoJmChtsc/Xs5Wl8NHS4L5Q7cxL8MG40gSU1bA==}
peerDependencies:
@@ -3173,9 +3073,6 @@ packages:
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
- pdfkit@0.17.2:
- resolution: {integrity: sha512-UnwF5fXy08f0dnp4jchFYAROKMNTaPqb/xgR8GtCzIcqoTnbOqtp3bwKvO4688oHI6vzEEs8Q6vqqEnC5IUELw==}
-
peberminta@0.9.0:
resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==}
@@ -3213,9 +3110,6 @@ packages:
pkg-types@2.3.0:
resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==}
- png-js@1.0.0:
- resolution: {integrity: sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==}
-
pngjs@5.0.0:
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
engines: {node: '>=10.13.0'}
@@ -3390,9 +3284,6 @@ packages:
engines: {node: '>= 0.4'}
hasBin: true
- restructure@3.0.2:
- resolution: {integrity: sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==}
-
rimraf@5.0.10:
resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==}
hasBin: true
@@ -3516,10 +3407,6 @@ packages:
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
engines: {node: '>= 10.x'}
- ssf@0.11.2:
- resolution: {integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==}
- engines: {node: '>=0.8'}
-
stack-trace@0.0.10:
resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==}
@@ -3642,9 +3529,6 @@ packages:
tiny-case@1.0.3:
resolution: {integrity: sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==}
- tiny-inflate@1.0.3:
- resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==}
-
tinybench@2.9.0:
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
@@ -3664,10 +3548,6 @@ packages:
resolution: {integrity: sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA==}
hasBin: true
- tmp@0.2.5:
- resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==}
- engines: {node: '>=14.14'}
-
toposort@2.0.2:
resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==}
@@ -3752,12 +3632,6 @@ packages:
undici-types@7.18.2:
resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
- unicode-properties@1.4.1:
- resolution: {integrity: sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==}
-
- unicode-trie@2.0.0:
- resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==}
-
unplugin-icons@23.0.1:
resolution: {integrity: sha512-rv0XEJepajKzDLvRUWASM8K+8+/CCfZn2jtogXqg6RIp7kpatRc/aFrVJn8ANQA09e++lPEEv9yX8cC9enc+QQ==}
peerDependencies:
@@ -3914,14 +3788,6 @@ packages:
resolution: {integrity: sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==}
engines: {node: '>= 12.0.0'}
- wmf@1.0.2:
- resolution: {integrity: sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==}
- engines: {node: '>=0.8'}
-
- word@0.3.0:
- resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==}
- engines: {node: '>=0.8'}
-
wrap-ansi@6.2.0:
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
engines: {node: '>=8'}
@@ -3934,11 +3800,6 @@ packages:
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
engines: {node: '>=12'}
- xlsx@0.18.5:
- resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==}
- engines: {node: '>=0.8'}
- hasBin: true
-
xtend@4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}
@@ -4301,11 +4162,6 @@ snapshots:
dependencies:
hono: 4.12.3
- '@hono/standard-validator@0.2.2(@standard-schema/spec@1.1.0)(hono@4.12.3)':
- dependencies:
- '@standard-schema/spec': 1.1.0
- hono: 4.12.3
-
'@iconify/json@2.2.444':
dependencies:
'@iconify/types': 2.0.0
@@ -5406,13 +5262,6 @@ snapshots:
tailwindcss: 4.2.1
vite: 7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)
- '@tanstack/query-core@5.90.20': {}
-
- '@tanstack/svelte-query@6.0.18(svelte@5.53.6)':
- dependencies:
- '@tanstack/query-core': 5.90.20
- svelte: 5.53.6
-
'@tanstack/table-core@8.21.3': {}
'@types/aws-lambda@8.10.161': {}
@@ -5585,8 +5434,6 @@ snapshots:
acorn@8.16.0: {}
- adler-32@1.3.1: {}
-
agent-base@7.1.4: {}
ansi-regex@5.0.1: {}
@@ -5629,10 +5476,6 @@ snapshots:
balanced-match@1.0.2: {}
- base64-js@0.0.8: {}
-
- base64-js@1.5.1: {}
-
better-auth@1.4.20(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)):
dependencies:
'@better-auth/core': 1.4.20(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1)
@@ -5680,10 +5523,6 @@ snapshots:
dependencies:
balanced-match: 1.0.2
- brotli@1.3.3:
- dependencies:
- base64-js: 1.5.1
-
buffer-from@1.1.2: {}
bun-types@1.3.9:
@@ -5695,11 +5534,6 @@ snapshots:
camelcase@8.0.0:
optional: true
- cfb@1.2.2:
- dependencies:
- adler-32: 1.3.1
- crc-32: 1.2.2
-
chai@6.2.2: {}
chokidar@4.0.3:
@@ -5727,14 +5561,10 @@ snapshots:
strip-ansi: 6.0.1
wrap-ansi: 7.0.0
- clone@2.1.2: {}
-
clsx@2.1.1: {}
cluster-key-slot@1.1.2: {}
- codepage@1.15.0: {}
-
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
@@ -5766,16 +5596,12 @@ snapshots:
cookie@0.6.0: {}
- crc-32@1.2.2: {}
-
cross-spawn@7.0.6:
dependencies:
path-key: 3.1.1
shebang-command: 2.0.0
which: 2.0.2
- crypto-js@4.2.0: {}
-
cssesc@3.0.0: {}
d3-array@2.12.1:
@@ -5911,8 +5737,6 @@ snapshots:
devalue@5.6.3: {}
- dfa@1.2.0: {}
-
dijkstrajs@1.0.3: {}
dlv@1.1.3:
@@ -6106,8 +5930,6 @@ snapshots:
pure-rand: 6.1.0
optional: true
- fast-deep-equal@3.1.3: {}
-
fdir@6.5.0(picomatch@4.0.3):
optionalDependencies:
picomatch: 4.0.3
@@ -6126,18 +5948,6 @@ snapshots:
fn.name@1.1.0: {}
- fontkit@2.0.4:
- dependencies:
- '@swc/helpers': 0.5.19
- brotli: 1.3.3
- clone: 2.1.2
- dfa: 1.2.0
- fast-deep-equal: 3.1.3
- restructure: 3.0.2
- tiny-inflate: 1.0.3
- unicode-properties: 1.4.1
- unicode-trie: 2.0.0
-
foreground-child@3.3.1:
dependencies:
cross-spawn: 7.0.6
@@ -6155,8 +5965,6 @@ snapshots:
forwarded-parse@2.1.2: {}
- frac@1.1.2: {}
-
fsevents@2.3.3:
optional: true
@@ -6325,8 +6133,6 @@ snapshots:
jose@6.1.3: {}
- jpeg-exif@1.1.4: {}
-
json-bigint@1.0.0:
dependencies:
bignumber.js: 9.3.1
@@ -6439,11 +6245,6 @@ snapshots:
lightningcss-win32-arm64-msvc: 1.31.1
lightningcss-win32-x64-msvc: 1.31.1
- linebreak@1.1.0:
- dependencies:
- base64-js: 0.0.8
- unicode-trie: 2.0.0
-
linkify-it@5.0.0:
dependencies:
uc.micro: 2.1.0
@@ -6500,8 +6301,6 @@ snapshots:
punycode.js: 2.3.1
tlds: 1.261.0
- marked@17.0.3: {}
-
memoize-weak@1.0.2: {}
memoize@10.2.0:
@@ -6599,8 +6398,6 @@ snapshots:
package-manager-detector@1.6.0: {}
- pako@0.2.9: {}
-
paneforge@1.0.2(svelte@5.53.6):
dependencies:
runed: 0.23.4(svelte@5.53.6)
@@ -6625,14 +6422,6 @@ snapshots:
pathe@2.0.3: {}
- pdfkit@0.17.2:
- dependencies:
- crypto-js: 4.2.0
- fontkit: 2.0.4
- jpeg-exif: 1.1.4
- linebreak: 1.1.0
- png-js: 1.0.0
-
peberminta@0.9.0: {}
pg-int8@1.0.1: {}
@@ -6683,8 +6472,6 @@ snapshots:
exsolve: 1.0.8
pathe: 2.0.3
- png-js@1.0.0: {}
-
pngjs@5.0.0: {}
postcss-selector-parser@6.0.10:
@@ -6814,8 +6601,6 @@ snapshots:
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
- restructure@3.0.2: {}
-
rimraf@5.0.10:
dependencies:
glob: 10.5.0
@@ -6949,10 +6734,6 @@ snapshots:
split2@4.2.0: {}
- ssf@0.11.2:
- dependencies:
- frac: 1.1.2
-
stack-trace@0.0.10: {}
stackback@0.0.2: {}
@@ -7110,8 +6891,6 @@ snapshots:
tiny-case@1.0.3:
optional: true
- tiny-inflate@1.0.3: {}
-
tinybench@2.9.0: {}
tinyexec@1.0.2: {}
@@ -7125,8 +6904,6 @@ snapshots:
tlds@1.261.0: {}
- tmp@0.2.5: {}
-
toposort@2.0.2:
optional: true
@@ -7191,16 +6968,6 @@ snapshots:
undici-types@7.18.2: {}
- unicode-properties@1.4.1:
- dependencies:
- base64-js: 1.5.1
- unicode-trie: 2.0.0
-
- unicode-trie@2.0.0:
- dependencies:
- pako: 0.2.9
- tiny-inflate: 1.0.3
-
unplugin-icons@23.0.1(svelte@5.53.6):
dependencies:
'@antfu/install-pkg': 1.1.0
@@ -7328,10 +7095,6 @@ snapshots:
triple-beam: 1.4.1
winston-transport: 4.9.0
- wmf@1.0.2: {}
-
- word@0.3.0: {}
-
wrap-ansi@6.2.0:
dependencies:
ansi-styles: 4.3.0
@@ -7350,16 +7113,6 @@ snapshots:
string-width: 5.1.2
strip-ansi: 7.2.0
- xlsx@0.18.5:
- dependencies:
- adler-32: 1.3.1
- cfb: 1.2.2
- codepage: 1.15.0
- crc-32: 1.2.2
- ssf: 0.11.2
- wmf: 1.0.2
- word: 0.3.0
-
xtend@4.0.2: {}
y18n@4.0.3: {}