Initial commit

This commit is contained in:
fraqtal
2026-07-12 08:15:46 +00:00
commit ee0fec0691
1397 changed files with 127242 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
import { t } from "@repo/core-shared/trpc/init";
import { defineErrorMiddleware } from "@repo/core-shared/trpc/define-error-middleware";
import {
AuthenticationError,
UnauthenticatedError,
UnauthorizedError,
TooManyRequestsError,
} from "../../entities/errors/auth";
import { InputParseError } from "../../entities/errors/common";
export const authProcedure = t.procedure.use(
defineErrorMiddleware([
[InputParseError, "BAD_REQUEST"],
[AuthenticationError, "UNAUTHORIZED"],
[UnauthenticatedError, "UNAUTHORIZED"],
[UnauthorizedError, "FORBIDDEN"],
[TooManyRequestsError, "TOO_MANY_REQUESTS"],
]),
);

View File

@@ -0,0 +1,74 @@
import { describe, it, expect, beforeEach } from "vitest";
import { TRPCError } from "@trpc/server";
import { authRouter } from "@/integrations/api/router";
import { authContainer } from "@/di/container";
import { AUTH_SYMBOLS } from "@/di/symbols";
import { MockUsersRepository } from "@/infrastructure/repositories/users.repository.mock";
import { MockAuthenticationService } from "@/infrastructure/services/authentication.service.mock";
import type { IUsersRepository } from "@/application/repositories/users.repository.interface";
import type { IAuthenticationService } from "@/application/services/authentication.service.interface";
describe("authRouter", () => {
it("exposes signIn, signUp, signOut procedures", () => {
const names = Object.keys(authRouter._def.procedures);
expect(names).toContain("signIn");
expect(names).toContain("signUp");
expect(names).toContain("signOut");
});
it("signIn returns a cookie via container-resolved controller", async () => {
// The router resolves controllers via the authContainer (default mock bindings).
// MockUsersRepository is seeded with alice/password_alice by default.
const caller = authRouter.createCaller({});
const result = await caller.signIn({
username: "alice",
password: "password_alice",
});
expect(result.name).toBe("session");
});
});
describe("authRouter error mapping", () => {
beforeEach(() => {
if (authContainer.isBound(AUTH_SYMBOLS.IUsersRepository)) {
authContainer.unbind(AUTH_SYMBOLS.IUsersRepository);
}
if (authContainer.isBound(AUTH_SYMBOLS.IAuthenticationService)) {
authContainer.unbind(AUTH_SYMBOLS.IAuthenticationService);
}
const users = new MockUsersRepository();
const auth = new MockAuthenticationService(users);
authContainer
.bind<IUsersRepository>(AUTH_SYMBOLS.IUsersRepository)
.toConstantValue(users);
authContainer
.bind<IAuthenticationService>(AUTH_SYMBOLS.IAuthenticationService)
.toConstantValue(auth);
});
it("translates AuthenticationError → UNAUTHORIZED on missing user", async () => {
const caller = authRouter.createCaller({});
try {
await caller.signIn({ username: "ghost", password: "long-enough" });
throw new Error("expected throw");
} catch (e) {
expect(e).toBeInstanceOf(TRPCError);
expect((e as TRPCError).code).toBe("UNAUTHORIZED");
}
});
it("translates BAD_REQUEST when zod parse fails at the procedure boundary", async () => {
const caller = authRouter.createCaller({});
try {
await caller.signIn({ username: "ab", password: "x" } as unknown as {
username: string;
password: string;
});
throw new Error("expected throw");
} catch (e) {
expect(e).toBeInstanceOf(TRPCError);
expect((e as TRPCError).code).toBe("BAD_REQUEST");
}
});
});

View File

@@ -0,0 +1,33 @@
import { router } from "@repo/core-shared/trpc/init";
import { authContainer } from "../../di/container";
import { AUTH_SYMBOLS } from "../../di/symbols";
import { signInInputSchema } from "../../application/use-cases/sign-in.use-case";
import { signUpInputSchema } from "../../application/use-cases/sign-up.use-case";
import { signOutInputSchema } from "../../application/use-cases/sign-out.use-case";
import type { ISignInController } from "../../interface-adapters/controllers/sign-in.controller";
import type { ISignUpController } from "../../interface-adapters/controllers/sign-up.controller";
import type { ISignOutController } from "../../interface-adapters/controllers/sign-out.controller";
import { authProcedure } from "./procedures";
export const authRouter = router({
signIn: authProcedure.input(signInInputSchema).mutation(({ input }) => {
const ctrl = authContainer.get<ISignInController>(AUTH_SYMBOLS.ISignInController);
return ctrl(input);
}),
signUp: authProcedure.input(signUpInputSchema).mutation(({ input }) => {
const ctrl = authContainer.get<ISignUpController>(AUTH_SYMBOLS.ISignUpController);
return ctrl(input);
}),
signOut: authProcedure.input(signOutInputSchema).mutation(({ input }) => {
const ctrl = authContainer.get<ISignOutController>(AUTH_SYMBOLS.ISignOutController);
return ctrl(input);
}),
});
export type AuthRouter = typeof authRouter;

View File

@@ -0,0 +1,49 @@
import type { CollectionConfig } from "payload";
export const users: CollectionConfig = {
slug: "users",
auth: true,
admin: {
useAsTitle: "email",
},
custom: {
retention: {
purgeSchedule: "daily",
postDeletion: {
duration: "P30D",
trigger: "after-deletion",
action: "hard-delete",
},
},
subject: { kind: "self", field: "id" },
},
fields: [
{
name: "displayName",
type: "text",
custom: {
pii: {
category: "identification-username",
purpose: ["service-delivery"],
exportable: true,
restrictable: true,
},
},
},
{
name: "role",
type: "select",
options: [
{ label: "Admin", value: "admin" },
{ label: "Editor", value: "editor" },
{ label: "Author", value: "author" },
],
defaultValue: "author",
required: true,
},
{
name: "consentState",
type: "json",
},
],
};

View File

@@ -0,0 +1,2 @@
export { users } from "./collections/users";
// <gen:job-tasks>