feat: topology, and improves handling

This commit is contained in:
2026-02-17 19:17:13 +07:00
parent e8dbefde43
commit d14f043e7c
145 changed files with 4213 additions and 2861 deletions
@@ -0,0 +1,57 @@
import type { User } from "@minikura/db";
import type { Elysia } from "elysia";
import { ForbiddenError, UnauthorizedError } from "../domain/errors/base.error";
export const requireAuth = (app: Elysia) => {
return app.derive((ctx: any) => {
const { user, isSuspended } = ctx as {
user: User | null;
isSuspended: boolean;
};
if (!user) {
throw new UnauthorizedError();
}
if (isSuspended) {
throw new ForbiddenError("Account is suspended");
}
return { user };
});
};
export const requireAdmin = (app: Elysia) => {
return app.derive((ctx: any) => {
const { user, isSuspended } = ctx as {
user: User | null;
isSuspended: boolean;
};
if (!user) {
throw new UnauthorizedError();
}
if (isSuspended) {
throw new ForbiddenError("Account is suspended");
}
if (user.role !== "admin") {
throw new ForbiddenError("Admin access required");
}
return { user };
});
};
export const requireRole = (role: string) => (app: Elysia) => {
return app.derive((ctx: any) => {
const { user, isSuspended } = ctx as {
user: User | null;
isSuspended: boolean;
};
if (!user) {
throw new UnauthorizedError();
}
if (isSuspended) {
throw new ForbiddenError("Account is suspended");
}
if (user.role !== role) {
throw new ForbiddenError(`${role} access required`);
}
return { user };
});
};
@@ -0,0 +1,44 @@
import { isUserSuspended } from "@minikura/db";
import { Elysia } from "elysia";
import { auth } from "./auth";
async function getSessionFromHeaders(headers: Headers | Record<string, string>) {
const headersObj =
headers instanceof Headers ? headers : new Headers(headers as Record<string, string>);
return auth.api.getSession({
headers: headersObj,
});
}
export const authPlugin = new Elysia({ name: "auth" })
.mount(auth.handler)
.derive({ as: "scoped" }, async ({ request }) => {
const session = await getSessionFromHeaders(request.headers);
if (
session?.user &&
isUserSuspended(
session.user as unknown as Pick<
{ isSuspended: boolean; suspendedUntil: Date | null },
"isSuspended" | "suspendedUntil"
>
)
) {
return {
user: null,
session: null,
isAuthenticated: false,
isSuspended: true,
};
}
return {
user: session?.user || null,
session: session?.session || null,
isAuthenticated: Boolean(session?.user),
isSuspended: false,
};
});
export type AuthPlugin = typeof authPlugin;
+20
View File
@@ -0,0 +1,20 @@
import { prisma } from "@minikura/db";
import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";
import { admin, openAPI } from "better-auth/plugins";
export const auth = betterAuth({
database: prismaAdapter(prisma, {
provider: "postgresql",
usePlural: false,
}),
emailAndPassword: { enabled: true },
plugins: [admin(), openAPI()],
trustedOrigins: [process.env.WEB_URL || "http://localhost:3001"],
session: {
cookieCache: { enabled: true, maxAge: 60 * 5 },
},
basePath: "/auth",
});
export type Auth = typeof auth;
@@ -0,0 +1,36 @@
import type { Elysia } from "elysia";
import { DomainError } from "../domain/errors/base.error";
import { logger } from "../infrastructure/logger";
export const errorHandler = (app: Elysia) => {
return app.onError(({ error, set }) => {
if (error instanceof DomainError) {
logger.warn({ code: error.code, message: error.message }, "Domain error occurred");
set.status = error.statusCode;
return {
success: false,
code: error.code,
message: error.message,
};
}
if (error instanceof Error && (error.name === "ValidationError" || error.name === "ZodError")) {
logger.warn({ err: error, message: error.message }, "Validation error");
set.status = 400;
return {
success: false,
code: "VALIDATION_ERROR",
message: error.message,
};
}
logger.error({ err: error }, "Unhandled error in API request");
set.status = 500;
return {
success: false,
code: "INTERNAL_SERVER_ERROR",
message: "An unexpected error occurred",
};
});
};
@@ -0,0 +1,25 @@
import type { z } from "zod";
type ErrorHandler = (code: number, value: unknown) => never;
export function validateBody<T extends z.ZodType>(
schema: T,
body: unknown,
error: ErrorHandler
): z.infer<T> {
const result = schema.safeParse(body);
if (!result.success) {
const firstError = result.error.issues[0];
const message = `${firstError.path.join(".")}: ${firstError.message}`;
throw error(400, { message });
}
return result.data;
}
export function zodValidate<T extends z.ZodType>(schema: T) {
return (context: { body: unknown; error: ErrorHandler }) => {
return validateBody(schema, context.body, context.error);
};
}