aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2024-07-28 02:11:30 +0900
committernsfisis <nsfisis@gmail.com>2024-07-28 02:11:30 +0900
commit7468748c141943fa000edea4098d54bf3cdff55e (patch)
treec381d9479e3542e13512c696ad0859b141f51549
parent6603d3ea5a54647e8eda8ec253835eb7a36e5eb2 (diff)
downloadiosdc-japan-2025-albatross-7468748c141943fa000edea4098d54bf3cdff55e.tar.gz
iosdc-japan-2025-albatross-7468748c141943fa000edea4098d54bf3cdff55e.tar.zst
iosdc-japan-2025-albatross-7468748c141943fa000edea4098d54bf3cdff55e.zip
frontend: openapi
-rw-r--r--Makefile4
-rw-r--r--frontend/Makefile3
-rw-r--r--frontend/app/.server/api/client.ts4
-rw-r--r--frontend/app/.server/api/schema.d.ts91
-rw-r--r--frontend/app/.server/auth.ts (renamed from frontend/app/services/auth.server.ts)34
-rw-r--r--frontend/app/.server/session.ts (renamed from frontend/app/services/session.server.ts)0
-rw-r--r--frontend/app/routes/dashboard.tsx2
-rw-r--r--frontend/app/routes/login.tsx2
-rw-r--r--frontend/package-lock.json280
-rw-r--r--frontend/package.json2
-rw-r--r--frontend/tsconfig.json3
11 files changed, 400 insertions, 25 deletions
diff --git a/Makefile b/Makefile
index fe643a8..8f145d4 100644
--- a/Makefile
+++ b/Makefile
@@ -28,6 +28,10 @@ sqldef: down build
oapi-codegen:
cd backend; make oapi-codegen
+.PHONY: openapi-typescript
+openapi-typescript:
+ cd frontend; make openapi-typescript
+
.PHONY: sqlc
sqlc:
cd backend; make sqlc
diff --git a/frontend/Makefile b/frontend/Makefile
new file mode 100644
index 0000000..17dd690
--- /dev/null
+++ b/frontend/Makefile
@@ -0,0 +1,3 @@
+.PHONY: openapi-typescript
+openapi-typescript:
+ npx --no-install openapi-typescript --path-params-as-types --output ./app/.server/api/schema.d.ts ../openapi.yaml
diff --git a/frontend/app/.server/api/client.ts b/frontend/app/.server/api/client.ts
new file mode 100644
index 0000000..12f2fc6
--- /dev/null
+++ b/frontend/app/.server/api/client.ts
@@ -0,0 +1,4 @@
+import createClient from "openapi-fetch";
+import type { paths } from "./schema";
+
+export const apiClient = createClient<paths>({ baseUrl: "http://api-server/" });
diff --git a/frontend/app/.server/api/schema.d.ts b/frontend/app/.server/api/schema.d.ts
new file mode 100644
index 0000000..815731e
--- /dev/null
+++ b/frontend/app/.server/api/schema.d.ts
@@ -0,0 +1,91 @@
+/**
+ * This file was auto-generated by openapi-typescript.
+ * Do not make direct changes to the file.
+ */
+
+export interface paths {
+ "/api/login": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ /** User login */
+ post: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": {
+ /** @example john */
+ username: string;
+ /** @example password123 */
+ password: string;
+ };
+ };
+ };
+ responses: {
+ /** @description Successfully authenticated */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ /** @example xxxxx.xxxxx.xxxxx */
+ token: string;
+ };
+ };
+ };
+ /** @description Invalid username or password */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ /** @example Invalid credentials */
+ message: string;
+ };
+ };
+ };
+ };
+ };
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+}
+export type webhooks = Record<string, never>;
+export interface components {
+ schemas: {
+ JwtPayload: {
+ /** @example 123 */
+ user_id: number;
+ /** @example john */
+ username: string;
+ /** @example John Doe */
+ display_username: string;
+ /** @example /images/john.jpg */
+ icon_path?: string | null;
+ /** @example false */
+ is_admin: boolean;
+ };
+ };
+ responses: never;
+ parameters: never;
+ requestBodies: never;
+ headers: never;
+ pathItems: never;
+}
+export type $defs = Record<string, never>;
+export type operations = Record<string, never>;
diff --git a/frontend/app/services/auth.server.ts b/frontend/app/.server/auth.ts
index 144a7cd..9696e90 100644
--- a/frontend/app/services/auth.server.ts
+++ b/frontend/app/.server/auth.ts
@@ -1,24 +1,24 @@
import { Authenticator } from "remix-auth";
import { FormStrategy } from "remix-auth-form";
-import { sessionStorage } from "./session.server";
import { jwtDecode } from "jwt-decode";
import type { Session } from "@remix-run/server-runtime";
+import { sessionStorage } from "./session";
+import { apiClient } from "./api/client";
+import { components } from "./api/schema";
export const authenticator = new Authenticator<string>(sessionStorage);
async function login(username: string, password: string): Promise<string> {
- const res = await fetch(`http://api-server/api/login`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
+ const { data, error } = await apiClient.POST("/api/login", {
+ body: {
+ username,
+ password,
},
- body: JSON.stringify({ username, password }),
});
- if (!res.ok) {
- throw new Error("Invalid username or password");
+ if (error) {
+ throw new Error(error.message);
}
- const user = await res.json();
- return user.token;
+ return data.token;
}
authenticator.use(
@@ -30,13 +30,7 @@ authenticator.use(
"default",
);
-type JwtPayload = {
- user_id: number;
- username: string;
- display_username: string;
- icon_path: string | null;
- is_admin: boolean;
-};
+type JwtPayload = components["schemas"]["JwtPayload"];
export type User = {
userId: number;
@@ -102,9 +96,8 @@ export async function isAuthenticated(
headers?: HeadersInit;
} = {},
): Promise<User | null> {
- let jwt;
-
// This function's signature should be compatible with `authenticator.isAuthenticated` but TypeScript does not infer it correctly.
+ let jwt;
const { successRedirect, failureRedirect, headers } = options;
if (successRedirect && failureRedirect) {
jwt = await authenticator.isAuthenticated(request, {
@@ -129,13 +122,12 @@ export async function isAuthenticated(
if (!jwt) {
return null;
}
- // TODO: runtime type check
const payload = jwtDecode<JwtPayload>(jwt);
return {
userId: payload.user_id,
username: payload.username,
displayUsername: payload.display_username,
- iconPath: payload.icon_path,
+ iconPath: payload.icon_path ?? null,
isAdmin: payload.is_admin,
};
}
diff --git a/frontend/app/services/session.server.ts b/frontend/app/.server/session.ts
index 2000853..2000853 100644
--- a/frontend/app/services/session.server.ts
+++ b/frontend/app/.server/session.ts
diff --git a/frontend/app/routes/dashboard.tsx b/frontend/app/routes/dashboard.tsx
index be274eb..535642c 100644
--- a/frontend/app/routes/dashboard.tsx
+++ b/frontend/app/routes/dashboard.tsx
@@ -1,5 +1,5 @@
import type { LoaderFunctionArgs } from "@remix-run/node";
-import { isAuthenticated } from "../services/auth.server";
+import { isAuthenticated } from "../.server/auth";
import { useLoaderData } from "@remix-run/react";
export async function loader({ request }: LoaderFunctionArgs) {
diff --git a/frontend/app/routes/login.tsx b/frontend/app/routes/login.tsx
index cf5be14..0da2616 100644
--- a/frontend/app/routes/login.tsx
+++ b/frontend/app/routes/login.tsx
@@ -1,6 +1,6 @@
import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node";
import { Form } from "@remix-run/react";
-import { authenticator } from "../services/auth.server";
+import { authenticator } from "../.server/auth";
export async function loader({ request }: LoaderFunctionArgs) {
return await authenticator.isAuthenticated(request, {
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 2186d40..a5e2c28 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -11,6 +11,7 @@
"@remix-run/serve": "^2.10.3",
"isbot": "^5.1.13",
"jwt-decode": "^4.0.0",
+ "openapi-fetch": "^0.10.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-use-websocket": "^4.8.1",
@@ -31,6 +32,7 @@
"eslint-plugin-jsx-a11y": "^6.9.0",
"eslint-plugin-react": "^7.35.0",
"eslint-plugin-react-hooks": "^4.6.2",
+ "openapi-typescript": "^7.1.0",
"postcss": "^8.4.40",
"prettier": "^3.3.3",
"tailwindcss": "^3.4.7",
@@ -1446,6 +1448,69 @@
"node": ">=14"
}
},
+ "node_modules/@redocly/ajv": {
+ "version": "8.11.0",
+ "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.0.tgz",
+ "integrity": "sha512-9GWx27t7xWhDIR02PA18nzBdLcKQRgc46xNQvjFkrYk4UOmvKhJ/dawwiX0cCOeetN5LcaaiqQbVOWYK62SGHw==",
+ "dev": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/@redocly/ajv/node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "dev": true
+ },
+ "node_modules/@redocly/config": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.7.0.tgz",
+ "integrity": "sha512-6GKxTo/9df0654Mtivvr4lQnMOp+pRj9neVywmI5+BwfZLTtkJnj2qB3D6d8FHTr4apsNOf6zTa5FojX0Evh4g==",
+ "dev": true
+ },
+ "node_modules/@redocly/openapi-core": {
+ "version": "1.18.1",
+ "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.18.1.tgz",
+ "integrity": "sha512-y2ZR3aaVF80XRVoFP0Dp2z5DeCOilPTuS7V4HnHIYZdBTfsqzjkO169h5JqAaifnaLsLBhe3YArdgLb7W7wW6Q==",
+ "dev": true,
+ "dependencies": {
+ "@redocly/ajv": "^8.11.0",
+ "@redocly/config": "^0.7.0",
+ "colorette": "^1.2.0",
+ "https-proxy-agent": "^7.0.4",
+ "js-levenshtein": "^1.1.6",
+ "js-yaml": "^4.1.0",
+ "lodash.isequal": "^4.5.0",
+ "minimatch": "^5.0.1",
+ "node-fetch": "^2.6.1",
+ "pluralize": "^8.0.0",
+ "yaml-ast-parser": "0.0.43"
+ },
+ "engines": {
+ "node": ">=14.19.0",
+ "npm": ">=7.0.0"
+ }
+ },
+ "node_modules/@redocly/openapi-core/node_modules/minimatch": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/@remix-run/dev": {
"version": "2.10.3",
"resolved": "https://registry.npmjs.org/@remix-run/dev/-/dev-2.10.3.tgz",
@@ -2379,6 +2444,18 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
+ "node_modules/agent-base": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
+ "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/aggregate-error": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
@@ -2408,6 +2485,15 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
+ "node_modules/ansi-colors": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
+ "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@@ -3185,6 +3271,12 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
+ "node_modules/colorette": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz",
+ "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==",
+ "dev": true
+ },
"node_modules/comma-separated-tokens": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
@@ -5460,6 +5552,19 @@
"node": ">= 0.8"
}
},
+ "node_modules/https-proxy-agent": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz",
+ "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==",
+ "dev": true,
+ "dependencies": {
+ "agent-base": "^7.0.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/human-signals": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
@@ -5555,6 +5660,18 @@
"node": ">=8"
}
},
+ "node_modules/index-to-position": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-0.1.2.tgz",
+ "integrity": "sha512-MWDKS3AS1bGCHLBA2VLImJz42f7bJh8wQsTGCzI3j519/CASStoDONUBVz2I/VID0MpiX3SGSnbOD2xUalbE5g==",
+ "dev": true,
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@@ -6176,6 +6293,15 @@
"jiti": "bin/jiti.js"
}
},
+ "node_modules/js-levenshtein": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz",
+ "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -6404,6 +6530,12 @@
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
"dev": true
},
+ "node_modules/lodash.isequal": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
+ "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
+ "dev": true
+ },
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -7673,6 +7805,26 @@
"node": ">= 0.6"
}
},
+ "node_modules/node-fetch": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "dev": true,
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
"node_modules/node-releases": {
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
@@ -7952,6 +8104,50 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/openapi-fetch": {
+ "version": "0.10.2",
+ "resolved": "https://registry.npmjs.org/openapi-fetch/-/openapi-fetch-0.10.2.tgz",
+ "integrity": "sha512-GCzgKIZchnxZRnztiOlRTKk9tQT0NHvs5MNXYFtOwG7xaj1iCJOGDsTLbn/2QUP37PjGTT890qERFjvmjxzQMg==",
+ "dependencies": {
+ "openapi-typescript-helpers": "^0.0.9"
+ }
+ },
+ "node_modules/openapi-typescript": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-7.1.0.tgz",
+ "integrity": "sha512-qxjGhIO6ODCGximE2HiozkPUNbO4y7F2OQyGa+gcn6TdZMMtmuiyDPqoKmf+Y4VlvQRfhJUTU635w8KfZVeuVA==",
+ "dev": true,
+ "dependencies": {
+ "@redocly/openapi-core": "^1.16.0",
+ "ansi-colors": "^4.1.3",
+ "parse-json": "^8.1.0",
+ "supports-color": "^9.4.0",
+ "yargs-parser": "^21.1.1"
+ },
+ "bin": {
+ "openapi-typescript": "bin/cli.js"
+ },
+ "peerDependencies": {
+ "typescript": "^5.x"
+ }
+ },
+ "node_modules/openapi-typescript-helpers": {
+ "version": "0.0.9",
+ "resolved": "https://registry.npmjs.org/openapi-typescript-helpers/-/openapi-typescript-helpers-0.0.9.tgz",
+ "integrity": "sha512-BO2TvIDAO/FPVKz1Nj2gy+pUOHfaoENdK5UP3H0Jbh0VXBf3dwYMs58ZwOjiezrbHA2LamdquoyQgahTPvIxGA=="
+ },
+ "node_modules/openapi-typescript/node_modules/supports-color": {
+ "version": "9.4.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz",
+ "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -8087,6 +8283,35 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/parse-json": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.1.0.tgz",
+ "integrity": "sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.22.13",
+ "index-to-position": "^0.1.2",
+ "type-fest": "^4.7.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parse-json/node_modules/type-fest": {
+ "version": "4.23.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.23.0.tgz",
+ "integrity": "sha512-ZiBujro2ohr5+Z/hZWHESLz3g08BBdrdLMieYFULJO+tWc437sn8kQsWLJoZErY8alNhxre9K4p3GURAG11n+w==",
+ "dev": true,
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/parse-ms": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz",
@@ -8259,6 +8484,15 @@
"pathe": "^1.1.2"
}
},
+ "node_modules/pluralize": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
+ "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/possible-typed-array-names": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
@@ -8963,6 +9197,15 @@
"remix-auth": "^3.6.0"
}
},
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/require-like": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz",
@@ -10097,6 +10340,12 @@
"integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==",
"dev": true
},
+ "node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+ "dev": true
+ },
"node_modules/trim-lines": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
@@ -11181,6 +11430,22 @@
"node": ">= 8"
}
},
+ "node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+ "dev": true
+ },
+ "node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "dev": true,
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
"node_modules/which": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz",
@@ -11431,6 +11696,21 @@
"node": ">= 14"
}
},
+ "node_modules/yaml-ast-parser": {
+ "version": "0.0.43",
+ "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz",
+ "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==",
+ "dev": true
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index c3ac2f5..ae51afd 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -16,6 +16,7 @@
"@remix-run/serve": "^2.10.3",
"isbot": "^5.1.13",
"jwt-decode": "^4.0.0",
+ "openapi-fetch": "^0.10.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-use-websocket": "^4.8.1",
@@ -36,6 +37,7 @@
"eslint-plugin-jsx-a11y": "^6.9.0",
"eslint-plugin-react": "^7.35.0",
"eslint-plugin-react-hooks": "^4.6.2",
+ "openapi-typescript": "^7.1.0",
"postcss": "^8.4.40",
"prettier": "^3.3.3",
"tailwindcss": "^3.4.7",
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
index 9d87dd3..d52b6b9 100644
--- a/frontend/tsconfig.json
+++ b/frontend/tsconfig.json
@@ -18,6 +18,7 @@
"resolveJsonModule": true,
"target": "ES2022",
"strict": true,
+ "noUncheckedIndexedAccess": true,
"allowJs": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
@@ -25,8 +26,6 @@
"paths": {
"~/*": ["./app/*"]
},
-
- // Vite takes care of building everything, not tsc.
"noEmit": true
}
}