Feat: server-infoエンドポイント / Fix: logger.tsを追跡対象に / Feat: lynqchat-jsを利用可能に / Fix: lynqchat-jsに不足していたエンドポイントを追加

This commit is contained in:
2026-03-19 15:12:26 +09:00
parent bb0bfc1dfd
commit 9e106c14f5
24 changed files with 544 additions and 94 deletions
+1
View File
@@ -1,5 +1,6 @@
{
"name": "backend",
"private": true,
"main": "dist/index.js",
"type": "module",
"scripts": {
+34
View File
@@ -0,0 +1,34 @@
import { appendFileSync } from "node:fs";
import { EOL } from "node:os";
const createLog = (
nativeLogger: (...args: any[]) => void,
type: string,
...args: any[]
) => {
if (args[0] instanceof Array) {
args = args[0];
}
const content = `${new Date().toLocaleString()} [${type}] ${args.join("\n").replaceAll("\n", "\n ")}`;
nativeLogger(content);
try {
appendFileSync(
`${import.meta.dirname}/../../../../.log/system.log`,
`${content.toString().replace(/\x1B\[[0-9;]*m/g, '')}${EOL}`
);
} catch (err) {
console.error(`${new Date().toLocaleString()} Logger: Failed to write to file.`);
}
}
const logger = {
log: (...args: any[]) => createLog(console.log, "LOG", args),
info: (...args: any[]) => createLog(console.info, "INFO", args),
error: (...args: any[]) => createLog(console.error, "ERROR", args),
warn: (...args: any[]) => createLog(console.warn, "WARN", args),
}
export default logger;
+5
View File
@@ -2,6 +2,7 @@ import type { FastifyInstance } from "fastify";
import Setup from "./setup";
import Primary from "./primary";
import Me from "./me";
import ServerInfo from "./server-info";
export default async function Routes(fastify: FastifyInstance) {
await fastify.register(Setup, {
@@ -12,6 +13,10 @@ export default async function Routes(fastify: FastifyInstance) {
prefix: "/primary",
});
await fastify.register(ServerInfo, {
prefix: "/server-info",
});
await fastify.register(Me, {
prefix: "/me",
});
@@ -0,0 +1,26 @@
import { DatabaseError } from "@/errors";
import logger from "@/lib/logger";
import { ConfigEntity } from "@/modules/entities/Config";
import { UserEntity } from "@/modules/entities/User";
import type { FastifyInstance } from "fastify";
export default async function ServerInfo(fastify: FastifyInstance) {
fastify.post("/", async (req, res) => {
try {
const config = fastify.orm.em.getRepository(ConfigEntity);
const user = fastify.orm.em.getRepository(UserEntity);
const configCount = await config.count();
const userCount = await user.count();
return res.send({
isInitialized: configCount > 0,
isFirstAdminExists: userCount > 0,
userCount,
});
} catch (err) {
logger.error("Database error:", err);
return res.code(500).send(DatabaseError());
}
});
}
-18
View File
@@ -1,18 +0,0 @@
import { ConfigEntity } from "@/modules/entities/Config";
import { UserEntity } from "@/modules/entities/User";
import type { FastifyInstance } from "fastify";
export default async function ServerInfo(fastify: FastifyInstance) {
fastify.post("/", async (req, res) => {
const config = fastify.orm.em.getRepository(ConfigEntity);
const user = fastify.orm.em.getRepository(UserEntity);
const configCount = await config.count();
const userCount = await user.count();
return res.send({
isInitialized: configCount > 0,
isFirstAdminExists: userCount > 0,
userCount,
});
});
}
+5 -4
View File
@@ -11,15 +11,16 @@
"dependencies": {
"dexie": "^4.3.0",
"vue": "^3.5.24",
"vue-router": "^5.0.2"
},
"devDependencies": {
"@types/node": "^24.10.1",
"vue-router": "^5.0.2",
"lynqchat-js": "workspace:*",
"@vitejs/plugin-vue": "^6.0.1",
"@vue/tsconfig": "^0.8.1",
"typescript": "~5.9.3",
"vite": "^8.0.0",
"vue-tsc": "^3.1.4"
},
"devDependencies": {
"@types/node": "^24.10.1"
},
"packageManager": "pnpm@10.29.1"
}
+6
View File
@@ -24,4 +24,10 @@
import { RouterView } from "vue-router";
import routerStatus from "@/lib/router";
import Progress from "@/components/Progress.vue";
import LynqChat from "lynqchat-js";
import type ApiMap from "lynqchat-js/1.0.0-alpha.0/map"
const client = new LynqChat<ApiMap>({
origin: window.origin,
});
</script>
+29 -2
View File
@@ -1,10 +1,37 @@
{
"name": "lynqchat",
"name": "lynqchat-js",
"description": "A LynqChat official JavaScript SDK.",
"private": true,
"type": "module",
"scripts": {},
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"license": "AGPL-3.0-only",
"author": {
"name": "Last2014",
"email": "info@last2014.com",
"url": "https://about.last2014.com"
},
"scripts": {
"build": "tsc && tsc-alias && copyfiles -u 1 \"src/**/*.d.ts\" dist",
"prepare": "tsc && tsc-alias && copyfiles -u 1 \"src/**/*.d.ts\" dist"
},
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./*": {
"import": "./dist/*.js",
"types": "./dist/*.d.ts"
},
"./*/*": {
"import": "./dist/*/*.js",
"types": "./dist/*/*.d.ts"
}
},
"packageManager": "pnpm@10.29.1",
"dependencies": {
"copyfiles": "^2.4.1",
"tsc-alias": "^1.8.16",
"typescript": "^5.9.3"
}
-8
View File
@@ -1,8 +0,0 @@
import SetupCreateAdmin from "./setup/create-admin";
import SetupInitilization from "./setup/initialization";
type ApiMap =
SetupInitilization &
SetupCreateAdmin;
export default ApiMap;
@@ -0,0 +1,12 @@
import { InputError, InputNoneError } from "../../modules/error/input";
import ErrorBase from "../../modules/error";
import DatabaseError from "../../modules/error/database";
import Success from "../../modules/response/success";
import YetInitializationError from "../../modules/error/yet_init";
export default interface Me {
"me": {
body: never;
response: Success | DatabaseError | InputError | InputNoneError | YetInitializationError;
};
}
@@ -0,0 +1,19 @@
import { InputError, InputNoneError } from "../../modules/error/input";
import ErrorBase from "../../modules/error";
import DatabaseError from "../../modules/error/database";
import Success from "../../modules/response/success";
import YetInitializationError from "../../modules/error/yet_init";
import { UserSchema } from "./signup";
export default interface PrimarySignin {
"primary/signin": {
body: Pick<UserSchema, "userid" | "password">;
response: (Success & {
token: string;
}) | DatabaseError | ErrorBase<{
bad: "client",
code: "auth_input_wrong",
message: "ユーザー名かパスワードが違います。",
}> | InputError | InputNoneError | YetInitializationError;
};
}
@@ -0,0 +1,19 @@
import { InputError, InputNoneError } from "../../modules/error/input";
import ErrorBase from "../../modules/error";
import DatabaseError from "../../modules/error/database";
import Success from "../../modules/response/success";
import YetInitializationError from "../../modules/error/yet_init";
export interface UserSchema {
userid: string;
username: string;
email: string;
password: string;
}
export default interface PrimarySignup {
"primary/signup": {
body: UserSchema;
response: Success | DatabaseError | InputError | InputNoneError | YetInitializationError;
};
}
@@ -0,0 +1,15 @@
import { InputError, InputNoneError } from "../../modules/error/input";
import ErrorBase from "../../modules/error";
import DatabaseError from "../../modules/error/database";
export default interface ServerInfo {
"server-info": {
body: {
name: string;
description: string;
requiredInvitationCode: boolean;
force?: "use_force_initialization";
};
response: Success | DatabaseError;
};
}
@@ -1,23 +1,17 @@
import { InputError, InputNoneError } from "../../modules/error/input";
import ErrorBase from "../../modules/error";
import DatabaseError from "../../modules/error/database";
import Success from "../../modules/response/success";
import YetInitializationError from "../../modules/error/yet_init";
import { UserSchema } from "../primary/signup";
export default interface SetupCreateAdmin {
"setup/create-admin": {
body: {
userid: string;
username: string;
email: string;
password: string;
};
body: UserSchema;
response: Success | DatabaseError | ErrorBase<{
bad: "client",
code: "yet_initialization",
message: "初期設定が行われていません。",
}> | ErrorBase<{
bad: "client",
code: "first_admin_already_exists",
message: "最初の管理者ユーザーは既に存在します。",
}> | InputError | InputNoneError;
}> | InputError | InputNoneError | YetInitializationError;
};
}
@@ -1,6 +1,7 @@
import { InputError, InputNoneError } from "../../modules/error/input";
import ErrorBase from "../../modules/error";
import DatabaseError from "../../modules/error/database";
import Success from "../../modules/response/success";
export default interface SetupInitilization {
"setup/initilization": {
+16
View File
@@ -0,0 +1,16 @@
import Me from "./api/me";
import PrimarySignin from "./api/primary/signin";
import PrimarySignup from "./api/primary/signup";
import ServerInfo from "./api/server-info";
import SetupCreateAdmin from "./api/setup/create-admin";
import SetupInitilization from "./api/setup/initialization";
type ApiMap =
SetupCreateAdmin &
SetupInitilization &
ServerInfo &
PrimarySignin &
PrimarySignup &
Me;
export default ApiMap;
@@ -5,9 +5,9 @@ type ErrorType<R = unknown> = {
reason?: R,
}
type ErrorBase<E extends ErrorType<any>> = {
type ErrorBase<E extends ErrorType<any> = ErrorType> = {
success: false;
error: E;
}
};
export default ErrorBase;
@@ -1,3 +1,5 @@
import ErrorBase from ".";
export type InputError = ErrorBase<{
bad: "client";
code: "input_wrong";
@@ -0,0 +1,7 @@
type YetInitializationError = ErrorBase<{
bad: "client";
code: "yet_initialization";
message: "初期設定が行われていません。";
}>;
export default YetInitializationError;
+7 -12
View File
@@ -14,7 +14,7 @@ type BodyArgs<M, E extends keyof M> =
[];
export default class LynqChat<
M extends { [K in keyof M]: { body?: any; response: any } }
M extends { [K in keyof M]: { body: any; response: any } }
> {
readonly origin: string;
readonly retry: number;
@@ -44,25 +44,20 @@ export default class LynqChat<
public async request<E extends keyof M>(
endpoint: E,
...args: BodyArgs<M, E>
): Promise<M[E]["response"]>;
public async request(
endpoint: string,
body?: any
): Promise<any>;
public async request(
endpoint: string,
...args: any[]
): Promise<any> {
): Promise<M[E]["response"]> {
const body = args[0] !== undefined
? JSON.stringify(args[0])
: undefined;
const req = await lynqFetch(
this.origin,
this.retry,
this.waiting,
endpoint,
endpoint as string,
{
method: "POST",
cache: "no-store",
body: args[0],
body,
}
);
+1 -6
View File
@@ -7,16 +7,11 @@
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"sourceMap": true,
"declaration": true,
"declarationMap": true,
"declarationDir": "./dist/types",
"declarationDir": "./dist",
"strict": true,
"skipLibCheck": true,
"baseUrl": "./src/",
"typeRoots": [
"./node_modules/@types",
],
},
"tsc-alias": {
"resolveFullPaths": true,