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:
@@ -1,5 +1,10 @@
|
|||||||
# サーバー設定
|
# サーバー設定
|
||||||
server:
|
server:
|
||||||
|
# Origin
|
||||||
|
# string
|
||||||
|
# 最終的にユーザーがリクエストするOrigin
|
||||||
|
origin: http://lynqchat.example.com
|
||||||
|
|
||||||
# 配信ポート
|
# 配信ポート
|
||||||
# number
|
# number
|
||||||
# 0から65535が使用できます。
|
# 0から65535が使用できます。
|
||||||
|
|||||||
@@ -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 ", "");
|
||||||
|
|||||||
@@ -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([
|
||||||
|
|||||||
@@ -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]);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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,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;
|
||||||
|
|||||||
Reference in New Issue
Block a user