First commit

This commit is contained in:
2026-03-18 22:42:33 +09:00
commit 50657066a6
64 changed files with 5290 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
import type { FastifyInstance } from "fastify";
import Setup from "./setup";
import Primary from "./primary";
import Me from "./me";
export default async function Routes(fastify: FastifyInstance) {
await fastify.register(Setup, {
prefix: "/setup",
});
await fastify.register(Primary, {
prefix: "/primary",
});
await fastify.register(Me, {
prefix: "/me",
});
}
+11
View File
@@ -0,0 +1,11 @@
import type { FastifyInstance } from "fastify";
export default async function Me(fastify: FastifyInstance) {
fastify.post("/", async (req, res) => {
if ("error" in req.token)
return res.code(400).send(req.token);
const { password, email, ...safeUser } = req.token.user;
return res.send(safeUser);
});
}
@@ -0,0 +1,13 @@
import type { FastifyInstance } from "fastify";
import SignUp from "./signup";
import SignIn from "./signin";
export default async function Primary(fastify: FastifyInstance) {
await fastify.register(SignUp, {
prefix: "/signup",
});
await fastify.register(SignIn, {
prefix: "/signin",
});
}
@@ -0,0 +1,46 @@
import { DatabaseError, ErrorBase, InputError } from "@/errors";
import logger from "@/lib/logger";
import { TokenEntity } from "@/modules/entities/Token";
import { UserEntity } from "@/modules/entities/User";
import { UserRepository } from "@/modules/repositories/User";
import type { FastifyInstance } from "fastify";
export default function SignIn(fastify: FastifyInstance) {
fastify.post("/", async (req, res) => {
res.header("Content-Type", "application/json");
const schema = UserRepository.schema.pick({
userid: true,
password: true,
});
const result = schema.safeParse(req.body);
if (!result.success) {
return res.code(400).send(InputError(result.error.issues));
}
try {
const user = await fastify.orm.em.getRepository(UserEntity).authUser(result.data);
if (typeof user === "string") {
return res.code(400).send(ErrorBase({
bad: "client",
code: "auth_input_wrong",
message: "ユーザー名かパスワードが違います。",
}));
}
const token = await fastify.orm.em.getRepository(TokenEntity).createToken(user, true);
return res.send({
success: true,
token: token,
});
} catch (err) {
logger.error("Database Error: User auth or token create failed.", err);
return res.code(500).send(DatabaseError());
}
});
}
@@ -0,0 +1,30 @@
import { UserEntity } from "@/modules/entities/User";
import logger from "@/lib/logger";
import type { FastifyInstance } from "fastify";
import { UserRepository } from "@/modules/repositories/User";
import { DatabaseError, InputError } from "@/errors";
export default function SignUp(fastify: FastifyInstance) {
fastify.post("/", async (req, res) => {
res.header("Content-Type", "application/json");
const result = UserRepository.schema.safeParse(req.body);
if (!result.success) {
return res.code(400).send(InputError(result.error.issues));
}
try {
await fastify.orm.em.getRepository(UserEntity).createUser(result.data);
return res.code(200).send({
success: true,
});
} catch (err) {
logger.error("Database Error: User create failed.", err);
return res.code(500).send(DatabaseError());
}
});
}
+18
View File
@@ -0,0 +1,18 @@
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,
});
});
}
+67
View File
@@ -0,0 +1,67 @@
import { UserEntity } from "@/modules/entities/User";
import logger from "@/lib/logger";
import type { FastifyInstance } from "fastify";
import { UserRepository } from "@/modules/repositories/User";
import { DatabaseError, ErrorBase, InputError } from "@/errors";
import { ConfigEntity } from "@/modules/entities/Config";
export default function CreateAdmin(fastify: FastifyInstance) {
fastify.post("/", async (req, res) => {
res.header("Content-Type", "application/json");
const result = UserRepository.schema.omit({ isAdmin: true }).safeParse(req.body);
if (!result.success) {
return res.code(400).send(InputError(result.error.issues));
}
try {
const configCount = await fastify.orm.em.count(ConfigEntity);
if (configCount === 0) {
return res.code(409).send(ErrorBase({
bad: "client",
code: "yet_initialization",
message: "初期設定が行われていません。",
}));
}
} catch (err) {
logger.error("Database Error: Could not check if already initialization:", err);
return res.code(500).send(DatabaseError());
}
try {
const userCount = await fastify.orm.em.getRepository(UserEntity).count();
if (userCount > 0) {
return res.code(409).send(ErrorBase({
bad: "client",
code: "first_admin_already_exists",
message: "最初の管理者ユーザーは既に存在します。",
}));
}
} catch (err) {
logger.error("Database Error: Could not check if administrator exists:", err);
return res.code(500).send(DatabaseError());
}
try {
await fastify.orm.em.getRepository(UserEntity).createUser({
...result.data,
isAdmin: true,
});
logger.warn("First administrator account has been created.")
return res.code(200).send({
success: true,
});
} catch (err) {
logger.error("Database Error: Admin user create failed.", err);
return res.code(500).send(DatabaseError());
}
});
}
+13
View File
@@ -0,0 +1,13 @@
import type { FastifyInstance } from "fastify";
import CreateAdmin from "./create-admin";
import Initialization from "./initialization";
export default async function Setup(fastify: FastifyInstance) {
await fastify.register(Initialization, {
prefix: "/initialization",
});
await fastify.register(CreateAdmin, {
prefix: "/create-admin",
});
}
@@ -0,0 +1,70 @@
import { DatabaseError, ErrorBase, InputError } from "@/errors";
import logger from "@/lib/logger";
import { ConfigEntity } from "@/modules/entities/Config";
import type { FastifyInstance } from "fastify";
import z from "zod/v3";
export default function Initialization(fastify: FastifyInstance) {
fastify.post("/", async (req, res) => {
res.header("Content-Type", "application/json");
const bodySchema = z.object({
name: z.string().trim().min(1).max(20),
description: z.string().trim().min(1),
requiredInvitationCode: z.boolean(),
force: z.literal("use_force_initialization").refine(() => process.env.NODE_ENV !== "production").optional(),
});
const result = bodySchema.safeParse(req.body);
if (!result.success) {
return res.code(400).send(InputError(result.error.issues));
}
try {
if (result.data.force) {
await fastify.orm.em.nativeDelete(ConfigEntity, {});
fastify.orm.em.clear();
}
const configCount = await fastify.orm.em.count(ConfigEntity);
if (configCount > 0) {
return res.code(409).send(ErrorBase({
bad: "client",
code: "already_initialization",
message: "既に初期設定が行われています。",
}));
}
} catch (err) {
logger.error("Database Error: Could not check if yet initialization:", err);
return res.code(500).send(DatabaseError());
}
try {
const entries = Object.entries(result.data).filter(([key]) => key !== "force");
for (const [key, value] of entries) {
const entity = fastify.orm.em.create(ConfigEntity, {
name: key,
value: typeof value === "string"
? value
: String(value),
});
fastify.orm.em.persist(entity);
}
await fastify.orm.em.flush();
return res.code(200).send({
success: true,
});
} catch (err) {
logger.error("Database Error: Server initialization failed:", err);
return res.code(500).send(DatabaseError());
}
});
}