Enable auth-service/api-gateway on production, build their images in CI
This commit is contained in:
@@ -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,3 +1,4 @@
|
||||
export * from "./sendChatMessage";
|
||||
export * from "./getDrugSuggestions";
|
||||
export * from "./auth";
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"],
|
||||
};
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,3 +1,4 @@
|
||||
export * from "./dto/auth";
|
||||
export * from "./dto/chat";
|
||||
export * from "./dto/session";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user