feature/authentication service #9

Merged
GW_MC merged 19 commits from feature/authentication into master 2025-12-19 12:24:49 +08:00
3 changed files with 134 additions and 0 deletions
Showing only changes of commit ed4a091d6e - Show all commits

View File

@@ -9,6 +9,44 @@
"version": "0.1.0"
},
"paths": {
"/api/auth/login": {
"post": {
"tags": [
"Authentication"
],
"summary": "Login endpoint",
"description": "Authenticates a user and returns a JWT in an HttpOnly cookie.",
"operationId": "login",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LoginRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "User authenticated successfully",
"content": {
"application/json": {
"schema": {
"default": null
}
}
}
},
"401": {
"description": "Authentication failed"
},
"500": {
"description": "Internal server error"
}
}
}
},
"/api/health/info": {
"get": {
"tags": [
@@ -70,6 +108,22 @@
"description": "Application version"
}
}
},
"LoginRequest": {
"type": "object",
"description": "Login request payload",
"required": [
"username",
"password"
],
"properties": {
"password": {
"type": "string"
},
"username": {
"type": "string"
}
}
}
}
},
@@ -77,6 +131,10 @@
{
"name": "Health",
"description": "Health information API"
},
{
"name": "Authentication",
"description": "Authentication API"
}
]
}

View File

@@ -6,6 +6,7 @@ export namespace Schemas {
up_since: string;
version: string;
};
export type LoginRequest = { password: string; username: string };
// </Schemas>
}
@@ -13,6 +14,15 @@ export namespace Schemas {
export namespace Endpoints {
// <Endpoints>
export type post_Login = {
method: "POST";
path: "/api/auth/login";
requestFormat: "json";
parameters: {
body: Schemas.LoginRequest;
};
responses: { 200: unknown; 401: unknown; 500: unknown };
};
export type get_Get_health_info = {
method: "GET";
path: "/api/health/info";
@@ -26,6 +36,9 @@ export namespace Endpoints {
// <EndpointByMethod>
export type EndpointByMethod = {
post: {
"/api/auth/login": Endpoints.post_Login;
};
get: {
"/api/health/info": Endpoints.get_Get_health_info;
};
@@ -34,6 +47,7 @@ export type EndpointByMethod = {
// </EndpointByMethod>
// <EndpointByMethod.Shorthands>
export type PostEndpoints = EndpointByMethod["post"];
export type GetEndpoints = EndpointByMethod["get"];
// </EndpointByMethod.Shorthands>
@@ -267,6 +281,37 @@ export class ApiClient {
return;
};
// <ApiClient.post>
post<Path extends keyof PostEndpoints, TEndpoint extends PostEndpoints[Path]>(
path: Path,
...params: MaybeOptionalArg<
TEndpoint extends { parameters: infer UParams }
? NotNever<UParams> extends true
? UParams & { overrides?: RequestInit; withResponse?: false; throwOnStatusError?: boolean }
: { overrides?: RequestInit; withResponse?: false; throwOnStatusError?: boolean }
: { overrides?: RequestInit; withResponse?: false; throwOnStatusError?: boolean }
>
): Promise<Extract<InferResponseByStatus<TEndpoint, SuccessStatusCode>, { data: {} }>["data"]>;
post<Path extends keyof PostEndpoints, TEndpoint extends PostEndpoints[Path]>(
path: Path,
...params: MaybeOptionalArg<
TEndpoint extends { parameters: infer UParams }
? NotNever<UParams> extends true
? UParams & { overrides?: RequestInit; withResponse?: true; throwOnStatusError?: boolean }
: { overrides?: RequestInit; withResponse?: true; throwOnStatusError?: boolean }
: { overrides?: RequestInit; withResponse?: true; throwOnStatusError?: boolean }
>
): Promise<SafeApiResponse<TEndpoint>>;
post<Path extends keyof PostEndpoints, _TEndpoint extends PostEndpoints[Path]>(
path: Path,
...params: MaybeOptionalArg<any>
): Promise<any> {
return this.request("post", path, ...params);
}
// </ApiClient.post>
// <ApiClient.get>
get<Path extends keyof GetEndpoints, TEndpoint extends GetEndpoints[Path]>(
path: Path,

View File

@@ -41,6 +41,7 @@ const createQueryKey = <TOptions extends EndpointParameters>(
};
// <EndpointByMethod.Shorthands>
export type PostEndpoints = EndpointByMethod["post"];
export type GetEndpoints = EndpointByMethod["get"];
// </EndpointByMethod.Shorthands>
@@ -69,6 +70,36 @@ type InferResponseData<TEndpoint, TStatusCode> =
export class TanstackQueryApiClient {
constructor(public client: ApiClient) {}
// <ApiClient.post>
post<Path extends keyof PostEndpoints, TEndpoint extends PostEndpoints[Path]>(
path: Path,
...params: MaybeOptionalArg<TEndpoint["parameters"]>
) {
const queryKey = createQueryKey(path as string, params[0]);
const query = {
/** type-only property if you need easy access to the endpoint params */
"~endpoint": {} as TEndpoint,
queryKey,
queryFn: {} as "You need to pass .queryOptions to the useQuery hook",
queryOptions: queryOptions({
queryFn: async ({ queryKey, signal }) => {
const requestParams = {
...(params[0] || {}),
...(queryKey[0] || {}),
overrides: { signal },
withResponse: false as const,
};
const res = await this.client.post(path, requestParams as never);
return res as InferResponseData<TEndpoint, SuccessStatusCode>;
},
queryKey: queryKey,
}),
};
return query;
}
// </ApiClient.post>
// <ApiClient.get>
get<Path extends keyof GetEndpoints, TEndpoint extends GetEndpoints[Path]>(
path: Path,