Enable auth-service/api-gateway on production, build their images in CI

This commit is contained in:
2026-08-18 14:11:00 +07:00
parent e5afedfa2f
commit b68005be1c
70 changed files with 6781 additions and 263 deletions
+36
View File
@@ -0,0 +1,36 @@
import type { AuthUser, LoginRequest } from "@duoc-thu/shared-types";
export class AuthError extends Error {
constructor(readonly status: number) {
super(`Auth request failed (${status})`);
this.name = "AuthError";
}
}
/** Calls the app's own `/api/auth/login` route (which holds the gateway URL
* and sets the httpOnly session cookie) — mirrors `sendChatMessage`'s
* BFF-first pattern rather than calling api-gateway from the browser. */
export async function login(credentials: LoginRequest): Promise<AuthUser> {
const response = await fetch("/api/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(credentials),
});
if (!response.ok) throw new AuthError(response.status);
const data = (await response.json()) as { user: AuthUser };
return data.user;
}
export async function logout(): Promise<void> {
const response = await fetch("/api/auth/logout", { method: "POST" });
if (!response.ok) throw new AuthError(response.status);
}
/** Returns `null` on 401 (no/expired session) rather than throwing — "not
* logged in" is an expected, common state for this page, not an error. */
export async function me(): Promise<AuthUser | null> {
const response = await fetch("/api/auth/me");
if (response.status === 401) return null;
if (!response.ok) throw new AuthError(response.status);
return (await response.json()) as AuthUser;
}
+1
View File
@@ -1,3 +1,4 @@
export * from "./sendChatMessage";
export * from "./getDrugSuggestions";
export * from "./auth";
+6
View File
@@ -17,9 +17,15 @@ export function buildMockResponse(userContent: string): SendMessageResponse {
`dùng để tra cứu. Câu hỏi nhận được: "${userContent}".`,
citations: [
{
chunkId: "mock-paracetamol-lieu_dung-1",
drugName: "PARACETAMOL",
sectionType: "lieu_dung",
sourcePageRange: [412, 413],
physicalPage: 411,
// Deliberately empty, same reasoning as `content` above — a
// plausible-looking mock snippet is a plausible-looking mock dose.
snippet: "",
isQuarantined: false,
},
],
disclaimer:
+34
View File
@@ -0,0 +1,34 @@
/** Shared ESLint base for the NestJS services (`auth-service`, `api-gateway`
* today). Legacy `.eslintrc` format, not flat config — matches
* `apps/web/.eslintrc.json`'s ESLint 8 era rather than introducing a second
* config format into the repo.
*
* Consuming package.json needs its own `eslint`, `@typescript-eslint/parser`,
* `@typescript-eslint/eslint-plugin` devDependencies — pnpm's non-hoisted
* node_modules means a shared config can't lend its own plugin resolution to
* a package that doesn't declare the plugin itself.
*
* Referenced from each service's `.eslintrc.json` as a relative file path
* (`"extends": "../../packages/config/eslint-preset/index.js"`), not as
* `"@duoc-thu/config/eslint-preset"` — tried that first, and ESLint 8's
* legacy shareable-config resolution only auto-resolves package names
* shaped like `eslint-config-*` / `@scope/eslint-config[-*]`, not an
* arbitrary subpath of an arbitrarily-named package. Confirmed live: the
* package-name form failed with "couldn't find the config", the relative
* path did not. */
module.exports = {
root: true,
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint"],
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
env: { node: true, jest: true },
parserOptions: { sourceType: "module", ecmaVersion: 2022 },
rules: {
// NestJS constructor-injection params are conventionally unused by name
// (e.g. `constructor(private readonly foo: Foo)`); flag genuinely unused
// locals/imports without flagging that idiom.
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
"@typescript-eslint/no-explicit-any": "warn",
},
ignorePatterns: ["dist", "node_modules"],
};
+6 -1
View File
@@ -1,5 +1,10 @@
{
"name": "@duoc-thu/config",
"private": true,
"version": "0.0.0"
"version": "0.0.0",
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"eslint": "^8.57.0"
}
}
+25
View File
@@ -0,0 +1,25 @@
export type UserRole = "user" | "admin";
export interface AuthUser {
username: string;
role: UserRole;
}
export interface LoginRequest {
username: string;
password: string;
}
export interface LoginResponse {
user: AuthUser;
// The signed JWT. Present in the auth-service/api-gateway wire response
// only — the web BFF route that calls this consumes `token` to set an
// httpOnly cookie and does NOT forward it into its own browser-facing
// response body. Never read this field in client-side/browser code.
token: string;
}
export interface RegisterRequest {
username: string;
password: string;
}
+1
View File
@@ -1,3 +1,4 @@
export * from "./dto/auth";
export * from "./dto/chat";
export * from "./dto/session";