New: configにoriginを追加 / Chg(Performance): /api以外の場合にトークンの認証をバイパス / New: server-infoエンドポイントにiconを追加 / New: setup/initializationでconfigテーブルにLynqChatロゴをiconとして追加する処理 / New: .content-main用の背景色を追加 / Chg: 背景色を変更 / Chg: .left-menuを更新

This commit is contained in:
2026-03-30 16:14:58 +09:00
parent d129c95aa4
commit c3383b778b
9 changed files with 89 additions and 35 deletions
+5
View File
@@ -1,5 +1,10 @@
# サーバー設定 # サーバー設定
server: server:
# Origin
# string
# 最終的にユーザーがリクエストするOrigin
origin: http://lynqchat.example.com
# 配信ポート # 配信ポート
# number # number
# 0から65535が使用できます。 # 0から65535が使用できます。
+10 -4
View File
@@ -14,9 +14,17 @@ const logger = new Logger("Lib | auth");
const Authorization: FastifyPluginCallback = (fastify) => { const Authorization: FastifyPluginCallback = (fastify) => {
fastify.addHook("onRequest", async (req, res) => { fastify.addHook("onRequest", async (req, res) => {
if (!(req.url.startsWith("/api"))) {
return req.token = ErrorBase({
bad: "client",
code: "token_invalid",
message: "トークンが不正です。",
});
}
let token = req.headers["authorization"]; let token = req.headers["authorization"];
if (typeof token !== "string") { if (typeof token !== "string") {
return ErrorBase({ return req.token = ErrorBase({
bad: "client", bad: "client",
code: "token_invalid", code: "token_invalid",
message: "トークンが不正です。", message: "トークンが不正です。",
@@ -24,13 +32,11 @@ const Authorization: FastifyPluginCallback = (fastify) => {
} }
if (!token.startsWith("Bearer ")) { if (!token.startsWith("Bearer ")) {
req.token = ErrorBase({ return req.token = ErrorBase({
bad: "client", bad: "client",
code: "token_invalid", code: "token_invalid",
message: "トークンが不正です。", message: "トークンが不正です。",
}); });
return;
} }
token = token.replace("Bearer ", ""); token = token.replace("Bearer ", "");
+1
View File
@@ -8,6 +8,7 @@ const logger = new Logger("Lib | config");
const schema = z.object({ const schema = z.object({
server: z.object({ server: z.object({
origin: z.string().refine(data => data === new URL(data).origin),
port: z.number().min(0).max(65535), port: z.number().min(0).max(65535),
host: z.string().ip(), host: z.string().ip(),
trustProxy: z.union([ trustProxy: z.union([
+9 -6
View File
@@ -1,3 +1,4 @@
import config from "@/lib/config";
import { DatabaseError } from "@/errors"; import { DatabaseError } from "@/errors";
import Logger from "@/lib/logger"; import Logger from "@/lib/logger";
import { ConfigEntity } from "@/modules/entities/Config"; import { ConfigEntity } from "@/modules/entities/Config";
@@ -9,18 +10,20 @@ export default async function ServerInfo(fastify: FastifyInstance) {
fastify.post("/", async (req, res) => { fastify.post("/", async (req, res) => {
try { try {
const config = fastify.orm.em.getRepository(ConfigEntity); const configRepo = fastify.orm.em.getRepository(ConfigEntity);
const user = fastify.orm.em.getRepository(UserEntity); const userRepo = fastify.orm.em.getRepository(UserEntity);
const configCount = await config.count(); const configCount = await configRepo.count();
const userCount = await user.count(); const userCount = await userRepo.count();
const serverName = await config.findOne({ name: "name" }); const serverName = await configRepo.findOne({ name: "name" });
const serverDescription = await config.findOne({ name: "description" }); const serverDescription = await configRepo.findOne({ name: "description" });
const serverIcon = await configRepo.findOne({ name: "icon" });
return res.send({ return res.send({
success: true, success: true,
name: serverName?.value ?? null, name: serverName?.value ?? null,
description: serverDescription?.value ?? null, description: serverDescription?.value ?? null,
icon: serverIcon?.value ?? `${config.server.origin}/assets/lynqchat.svg`,
isInitialized: configCount > 0, isInitialized: configCount > 0,
isFirstAdminExists: userCount > 0, isFirstAdminExists: userCount > 0,
userCount, userCount,
@@ -4,6 +4,7 @@ import { ConfigEntity } from "@/modules/entities/Config";
import type { FastifyInstance } from "fastify"; import type { FastifyInstance } from "fastify";
import webpush from "web-push"; import webpush from "web-push";
import z from "zod/v3"; import z from "zod/v3";
import config from "@/lib/config";
export default function SetupInitialization(fastify: FastifyInstance) { export default function SetupInitialization(fastify: FastifyInstance) {
const logger = new Logger("Endpoint | setup/initialization"); const logger = new Logger("Endpoint | setup/initialization");
@@ -48,6 +49,7 @@ export default function SetupInitialization(fastify: FastifyInstance) {
try { try {
const entries = Object.entries(result.data).filter(([key]) => key !== "force"); const entries = Object.entries(result.data).filter(([key]) => key !== "force");
entries.push(["icon", `${config.server.origin}/assets/lynqchat.svg`]);
const vapid = webpush.generateVAPIDKeys(); const vapid = webpush.generateVAPIDKeys();
entries.push(["VAPID_PUBLIC", vapid.publicKey]); entries.push(["VAPID_PUBLIC", vapid.publicKey]);
entries.push(["VAPID_PRIVATE", vapid.privateKey]); entries.push(["VAPID_PRIVATE", vapid.privateKey]);
+56 -21
View File
@@ -7,10 +7,14 @@
<div class="modals-container" /> <div class="modals-container" />
<main class="layout"> <main class="layout" v-if="serverInfo.success && account.success">
<div class="left-menu"> <div class="left-menu">
<div> <div :class='$route.path === "/" ? "isActive" : ""'>
ホーム <img :src="serverInfo.icon" />
</div>
<div :class='$route.path === "/home" ? "isActive" : ""'>
<Icon icon="material-symbols:home-rounded" />
</div> </div>
</div> </div>
@@ -37,10 +41,10 @@
<style scoped> <style scoped>
main.layout { main.layout {
display: flex; display: flex;
gap: 1rem;
width: 100dvw; width: 100dvw;
height: 100dvh; height: 100dvh;
padding: 1.5rem; padding: 0.5rem;
gap: 0.5rem;
box-sizing: border-box; box-sizing: border-box;
} }
@@ -48,39 +52,69 @@ main.layout {
display: flex; display: flex;
flex-shrink: 0; flex-shrink: 0;
flex-direction: column; flex-direction: column;
justify-content: center; padding: 0.5rem 0;
gap: 1rem; height: 100%;
overflow: scroll;
gap: 0.5rem;
box-sizing: border-box;
user-select: none; user-select: none;
-webkit-user-select: none; -webkit-user-select: none;
} }
.left-menu div { .left-menu div {
width: 6rem; position: relative;
text-align: center; z-index: 0;
font-weight: bold; border-radius: 0.5rem;
font-size: 1.4rem; width: 3rem;
border-radius: 2rem; height: 3rem;
padding: 1rem 1.5rem; padding: 0.5rem;
transition: background-color 250ms ease-out; transition: background-color 200ms ease-out;
} }
.left-menu div:hover { .left-menu div * {
font-size: 3rem;
padding: 0.25rem;
border-radius: 0.5rem;
width: 3rem;
height: 3rem;
display: block;
overflow: hidden;
object-fit: contain;
box-sizing: border-box;
}
.left-menu div.isActive::before {
content: "";
display: block;
position: absolute;
top: 50%;
left: 0.3rem;
transform: translateY(-50%);
width: 0.25rem;
height: 70%;
border-radius: 0.5rem;
background-color: var(--text-color);
}
.left-menu div:hover,
.left-menu div.isActive {
background-color: var(--border-color); background-color: var(--border-color);
} }
.content-main { .content-main {
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
border-radius: 1rem; background-color: var(--route-bg-color);
border-radius: 0.5rem;
overflow: hidden; overflow: hidden;
flex-grow: 1; flex-grow: 1;
} }
.content-header { .content-header {
padding: 1.25rem 1.5rem; padding: 1rem 1.25rem;
font-size: 1.6rem; font-size: 1.3rem;
font-weight: bold; font-weight: bold;
border-bottom-left-radius: 1rem; border-bottom-left-radius: 0.5rem;
border-bottom-right-radius: 1rem; border-bottom-right-radius: 0.5rem;
border-bottom: 1px solid var(--border-color); border-bottom: 1px solid var(--border-color);
box-shadow: 0px 1px 10px var(--border-color); box-shadow: 0px 1px 10px var(--border-color);
user-select: none; user-select: none;
@@ -124,8 +158,9 @@ main.layout {
<script lang="ts" setup> <script lang="ts" setup>
import { RouterView, useRouter } from "vue-router"; import { RouterView, useRouter } from "vue-router";
import routerStatus from "@/lib/router"; import routerStatus from "@/lib/router";
import { Icon } from "@iconify/vue";
import Progress from "@/components/Progress.vue"; import Progress from "@/components/Progress.vue";
import { serverInfo } from "@/lib/account"; import { account, serverInfo } from "@/lib/account";
const router = useRouter(); const router = useRouter();
+5 -3
View File
@@ -1,8 +1,9 @@
@import url("https://fonts.googleapis.com/css2?family=BIZ+UDPGothic&display=swap"); @import url("https://fonts.googleapis.com/css2?family=BIZ+UDPGothic&display=swap");
:root { :root {
--bg-color: #ffffff; --bg-color: #f0f0f0;
--bg-sub-color: #f1f1f1; --bg-sub-color: #f1f1f1;
--route-bg-color: #ffffff;
--text-color: #000000; --text-color: #000000;
--text-sub-color: #595959; --text-sub-color: #595959;
--border-color: #e0e0e0; --border-color: #e0e0e0;
@@ -14,8 +15,9 @@
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
:root { :root {
--bg-color: #1b1b1b; --bg-color: #181818;
--bg-sub-color: #252525; --bg-sub-color: #1e1e1e;
--route-bg-color: #242424;
--text-color: #ffffff; --text-color: #ffffff;
--text-sub-color: #a4a4a4; --text-sub-color: #a4a4a4;
--border-color: #2c2c2c; --border-color: #2c2c2c;
-1
View File
@@ -1,5 +1,4 @@
<template> <template>
<h1>Hello World</h1>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@@ -9,6 +9,7 @@ export default interface ServerInfo {
response: (Success & { response: (Success & {
name: string | null; name: string | null;
description: string | null; description: string | null;
icon: string;
isInitialized: boolean; isInitialized: boolean;
isFirstAdminExists: boolean; isFirstAdminExists: boolean;
userCount: number; userCount: number;