Chg: exactOptionalPropertyTypesをfalseに変更 / Chg(Security): トークンが不正な場合のエラーを全てtoken_invalidに変更 / New: channelテーブル・リポジトリ / Chg: configテーブルのvalueをstringに / Chg: configテーブルのlengthを4096に / New: messageテーブル / Chg: 安全のためuserテーブルのOptionalPropsにidを追加 / New: channel/createエンドポイント / New: channel/listエンドポイント / New: channel/editエンドポイント / Enhance: primary/signupエンドポイントの重複エラーの実装で末尾カンマなどの改善 / Chg: setup/initializationのdescriptionに最大文字数4096を制定 / Chg: serverInfoをdefault exportからexportに変更 / New: フロントエンドでmeを読み込み / New: フロントエンドでchannelを読み込み / New: client.tsでトークンがある場合はトークンを指定 / Chg: clientをrefに / Del: IndexedDBからserverテーブルを削除 / Fix: Dexieのclassに命名 / Feat: フロントエンドでのサインインページ / Fix: L.jsで任意のbodyがあるエンドポイントが定義できない問題を修正 / Del: L.jsのserver-infoでの不要なimportを削除 / Fix: L.jsでトークンのエラーを追加 / Fix: L.jsのUserSchemaにlastUsedAtを追加

This commit is contained in:
2026-03-30 11:37:57 +09:00
parent 6b54ae4306
commit d129c95aa4
33 changed files with 683 additions and 39 deletions
@@ -0,0 +1,55 @@
import { DatabaseError, InputError } from "@/errors";
import Logger from "@/lib/logger";
import { ChannelEntity } from "@/modules/entities/Channel";
import { ChannelRepository } from "@/modules/repositories/Channel";
import type { FastifyInstance } from "fastify";
export default async function ChannelCreate(fastify: FastifyInstance) {
const logger = new Logger("Endpoint | channel/create");
fastify.post("/", async (req, res) => {
res.header("Content-Type", "application/json");
if ("error" in req.token)
return res.code(400).send(req.token);
const result = ChannelRepository.schema.safeParse({
...req.body as any,
userid: req.token.user.userid,
});
if (!result.success) {
return res.code(400).send(InputError(result.error.issues));
}
try {
const channelRepo = fastify.orm.em.getRepository(ChannelEntity);
const id = await channelRepo.createChannel(result.data);
return res.send({
success: true,
id,
});
} catch (err: any) {
if (err.name === "UniqueConstraintViolationException") {
const duplicate = err.constraint.replace("channel_", "").replace("_unique", "");
if (duplicate !== "id") {
return res.code(400).send(InputError([
{
validation: "regex",
code: "invalid_string",
message: "Duplicate",
path: [duplicate],
},
]));
}
}
logger.error("Database Error:", err);
return res.code(500).send(DatabaseError());
}
});
}
@@ -0,0 +1,52 @@
import { DatabaseError, ErrorBase, InputError } from "@/errors";
import Logger from "@/lib/logger";
import { ChannelEntity } from "@/modules/entities/Channel";
import { ChannelRepository } from "@/modules/repositories/Channel";
import type { FastifyInstance } from "fastify";
import z from "zod/v3";
export default async function ChannelEdit(fastify: FastifyInstance) {
const logger = new Logger("Endpoint | channel/edit");
fastify.post("/", async (req, res) => {
res.header("Content-Type", "application/json");
if ("error" in req.token)
return res.code(400).send(req.token);
const result = ChannelRepository.schema
.omit({ userid: true }).partial()
.merge(z.object({ id: z.string().length(10) }))
.refine(data =>
!Object.keys(data).length
).safeParse(req.body);
if (!result.success) {
return res.code(400).send(InputError(result.error.issues));
}
try {
const channelRepo = fastify.orm.em.getRepository(ChannelEntity);
const itChannel = await channelRepo.findOne({ id: result.data.id });
if (!itChannel) {
return res.code(400).send(ErrorBase({
bad: "client",
code: "channel_not_found",
message: "対象のチャンネルが見つかりませんでした。",
}));
}
const id = await channelRepo.editChannel(itChannel, result.data);
return res.send({
success: true,
id,
});
} catch (err) {
logger.error("Database Error:", err);
return res.code(500).send(DatabaseError());
}
});
}
@@ -0,0 +1,18 @@
import type { FastifyInstance } from "fastify";
import ChannelList from "./list";
import ChannelCreate from "./create";
import ChannelEdit from "./edit";
export default async function Setup(fastify: FastifyInstance) {
await fastify.register(ChannelList, {
prefix: "/list",
});
await fastify.register(ChannelCreate, {
prefix: "/create",
});
await fastify.register(ChannelEdit, {
prefix: "/edit",
});
}
@@ -0,0 +1,49 @@
import { DatabaseError, InputError } from "@/errors";
import Logger from "@/lib/logger";
import { ChannelEntity } from "@/modules/entities/Channel";
import type { FastifyInstance } from "fastify";
import z from "zod/v3";
export default async function ChannelList(fastify: FastifyInstance) {
const logger = new Logger("Endpoint | channel/list");
fastify.post("/", async (req, res) => {
res.header("Content-Type", "application/json");
if ("error" in req.token)
return res.code(400).send(req.token);
const bodySchema = z.object({
limit: z.number().optional(),
since: z.string().optional(),
});
const result = bodySchema.safeParse(req.body);
if (!result.success) {
return res.code(400).send(InputError(result.error.issues));
}
try {
const channelRepo = fastify.orm.em.getRepository(ChannelEntity);
const findResult = await channelRepo.findChannel(
result.data.limit,
result.data.since,
) ?? [];
if ("error" in findResult) {
return res.code(400).send(findResult);
}
return res.send({
success: true,
channels: findResult,
});
} catch (err) {
logger.error("Database Error:", err);
return res.code(500).send(DatabaseError());
}
});
}
@@ -40,9 +40,9 @@ export default function PrimarySignUp(fastify: FastifyInstance) {
validation: "regex",
code: "invalid_string",
message: "Duplicate",
path: [duplicate]
}
]))
path: [duplicate],
},
]));
}
}
@@ -13,7 +13,7 @@ export default function SetupInitialization(fastify: FastifyInstance) {
const bodySchema = z.object({
name: z.string().trim().min(1).max(20),
description: z.string().trim().min(1),
description: z.string().trim().min(1).max(4096),
requiredInvitationCode: z.boolean(),
force: z.literal("use_force_initialization").refine(() => process.env.NODE_ENV !== "production").optional(),
});