色々
This commit is contained in:
+5
-1
@@ -11,7 +11,11 @@
|
|||||||
"start": "pnpm -F backend start",
|
"start": "pnpm -F backend start",
|
||||||
"dev": "pnpm -r --parallel --no-bail dev",
|
"dev": "pnpm -r --parallel --no-bail dev",
|
||||||
"build": "pnpm -r --parallel build",
|
"build": "pnpm -r --parallel build",
|
||||||
|
"build:sdk": "pnpm -F lynqchat-js build",
|
||||||
"migrator": "pnpm -F backend migrator"
|
"migrator": "pnpm -F backend migrator"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.29.1"
|
"packageManager": "pnpm@10.29.1",
|
||||||
|
"dependencies": {
|
||||||
|
"vite-plugin-pwa": "^1.3.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import generateUniqueId from "@/lib/id";
|
|||||||
import { Entity, EntityRepositoryType, Index, ManyToOne, OptionalProps, PrimaryKey, Property } from "@mikro-orm/core";
|
import { Entity, EntityRepositoryType, Index, ManyToOne, OptionalProps, PrimaryKey, Property } from "@mikro-orm/core";
|
||||||
import { UserEntity } from "@/modules/entities/User";
|
import { UserEntity } from "@/modules/entities/User";
|
||||||
import { ChannelRepository } from "@/modules/repositories/Channel";
|
import { ChannelRepository } from "@/modules/repositories/Channel";
|
||||||
|
import { CommunityEntity } from "@/modules/entities/Community";
|
||||||
|
|
||||||
@Entity({
|
@Entity({
|
||||||
tableName: "channel",
|
tableName: "channel",
|
||||||
@@ -32,6 +33,9 @@ export class ChannelEntity {
|
|||||||
})
|
})
|
||||||
description!: string;
|
description!: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => CommunityEntity)
|
||||||
|
community!: CommunityEntity;
|
||||||
|
|
||||||
@ManyToOne(() => UserEntity)
|
@ManyToOne(() => UserEntity)
|
||||||
createdBy!: UserEntity;
|
createdBy!: UserEntity;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import generateUniqueId from "@/lib/id";
|
||||||
|
import { Entity, EntityRepositoryType, Index, ManyToOne, OptionalProps, PrimaryKey, Property } from "@mikro-orm/core";
|
||||||
|
import { UserEntity } from "@/modules/entities/User";
|
||||||
|
import { CommunityRepository } from "@/modules/repositories/Community";
|
||||||
|
|
||||||
|
@Entity({
|
||||||
|
tableName: "community",
|
||||||
|
repository: () => CommunityRepository,
|
||||||
|
})
|
||||||
|
export class CommunityEntity {
|
||||||
|
[OptionalProps]?: "id" | "createdAt";
|
||||||
|
[EntityRepositoryType]?: CommunityRepository;
|
||||||
|
|
||||||
|
@PrimaryKey({
|
||||||
|
type: "string",
|
||||||
|
length: 10,
|
||||||
|
onCreate: () => generateUniqueId(),
|
||||||
|
})
|
||||||
|
id!: string;
|
||||||
|
|
||||||
|
@Property({
|
||||||
|
type: "string",
|
||||||
|
length: 20,
|
||||||
|
unique: true,
|
||||||
|
})
|
||||||
|
@Index()
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
@Property({
|
||||||
|
type: "string",
|
||||||
|
length: 4096,
|
||||||
|
})
|
||||||
|
description!: string;
|
||||||
|
|
||||||
|
@Property({
|
||||||
|
type: "string",
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
icon?: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => UserEntity)
|
||||||
|
createdBy!: UserEntity;
|
||||||
|
|
||||||
|
@Property({
|
||||||
|
type: "datetime",
|
||||||
|
onCreate: () => new Date(),
|
||||||
|
})
|
||||||
|
createdAt!: Date;
|
||||||
|
}
|
||||||
@@ -9,9 +9,10 @@ export class ChannelRepository extends EntityRepository<ChannelEntity> {
|
|||||||
name: z.string().trim().min(1).max(20),
|
name: z.string().trim().min(1).max(20),
|
||||||
description: z.string().trim().min(1).max(4096),
|
description: z.string().trim().min(1).max(4096),
|
||||||
userid: UserRepository.schema.shape.userid,
|
userid: UserRepository.schema.shape.userid,
|
||||||
|
community: z.string().length(10),
|
||||||
});
|
});
|
||||||
|
|
||||||
async findChannel(limit: number = 20, sinceData?: string) {
|
async listChannel(community: string, limit: number = 20, sinceData?: string) {
|
||||||
let since = sinceData ?? new Date();
|
let since = sinceData ?? new Date();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -32,6 +33,7 @@ export class ChannelRepository extends EntityRepository<ChannelEntity> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const findResult = await this.find({
|
const findResult = await this.find({
|
||||||
|
community,
|
||||||
createdAt: {
|
createdAt: {
|
||||||
$lt: since,
|
$lt: since,
|
||||||
},
|
},
|
||||||
@@ -57,7 +59,7 @@ export class ChannelRepository extends EntityRepository<ChannelEntity> {
|
|||||||
|
|
||||||
async editChannel(
|
async editChannel(
|
||||||
target: ChannelEntity,
|
target: ChannelEntity,
|
||||||
data: Partial<Omit<z.infer<typeof ChannelRepository.schema>, "userid">>
|
data: Partial<Omit<z.infer<typeof ChannelRepository.schema>, "userid" | "community">>
|
||||||
) {
|
) {
|
||||||
await this.nativeUpdate(target, data);
|
await this.nativeUpdate(target, data);
|
||||||
return target.id;
|
return target.id;
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { EntityRepository } from "@mikro-orm/postgresql";
|
||||||
|
import { ErrorBase } from "@/errors";
|
||||||
|
import z from "zod/v3";
|
||||||
|
import { UserRepository } from "@/modules/repositories/User";
|
||||||
|
import type { CommunityEntity } from "@/modules/entities/Community";
|
||||||
|
|
||||||
|
export class CommunityRepository extends EntityRepository<CommunityEntity> {
|
||||||
|
public static schema = z.object({
|
||||||
|
name: z.string().trim().min(1).max(20),
|
||||||
|
description: z.string().trim().min(1).max(4096),
|
||||||
|
userid: UserRepository.schema.shape.userid,
|
||||||
|
icon: z.string().url(),
|
||||||
|
});
|
||||||
|
|
||||||
|
async listCommunity(limit: number = 20, sinceData?: string) {
|
||||||
|
let since = sinceData ?? new Date();
|
||||||
|
|
||||||
|
if (
|
||||||
|
sinceData &&
|
||||||
|
!isNaN(new Date(sinceData).getTime())
|
||||||
|
) {
|
||||||
|
const itCommunity = await this.findOne({ id: sinceData });
|
||||||
|
|
||||||
|
if (!itCommunity) {
|
||||||
|
return ErrorBase({
|
||||||
|
bad: "client",
|
||||||
|
code: "community_not_found",
|
||||||
|
message: "対象のコミュニティが見つかりませんでした。",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
since = itCommunity.createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
const findResult = await this.find({
|
||||||
|
createdAt: {
|
||||||
|
$lt: since,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
orderBy: {
|
||||||
|
createdAt: "DESC",
|
||||||
|
},
|
||||||
|
limit: limit,
|
||||||
|
});
|
||||||
|
|
||||||
|
return findResult ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async createCommunity(data: Omit<z.infer<typeof CommunityRepository.schema>, "icon">) {
|
||||||
|
const community = this.create({
|
||||||
|
...data,
|
||||||
|
createdBy: data.userid,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.em.persist(community).flush();
|
||||||
|
return community.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
async editCommunity(
|
||||||
|
target: CommunityEntity,
|
||||||
|
data: Partial<Omit<z.infer<typeof CommunityRepository.schema>, "userid">>
|
||||||
|
) {
|
||||||
|
await this.nativeUpdate(target, data);
|
||||||
|
return target.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ export default async function ChannelEdit(fastify: FastifyInstance) {
|
|||||||
return res.code(400).send(req.token);
|
return res.code(400).send(req.token);
|
||||||
|
|
||||||
const result = ChannelRepository.schema
|
const result = ChannelRepository.schema
|
||||||
.omit({ userid: true }).partial()
|
.omit({ userid: true, community: true }).partial()
|
||||||
.merge(z.object({ id: z.string().length(10) }))
|
.merge(z.object({ id: z.string().length(10) }))
|
||||||
.refine(data =>
|
.refine(data =>
|
||||||
!Object.keys(data).length
|
!Object.keys(data).length
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { DatabaseError, ErrorBase, 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 ChannelGet(fastify: FastifyInstance) {
|
||||||
|
const logger = new Logger("Endpoint | channel/get");
|
||||||
|
|
||||||
|
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({
|
||||||
|
id: z.string().length(10),
|
||||||
|
});
|
||||||
|
|
||||||
|
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.find({
|
||||||
|
id: result.data.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (findResult[0] === undefined) {
|
||||||
|
return res.code(400).send(ErrorBase({
|
||||||
|
bad: "client",
|
||||||
|
code: "channel_not_found",
|
||||||
|
message: "対象のチャンネルが見つかりませんでした。",
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.send({
|
||||||
|
success: true,
|
||||||
|
channel: findResult[0],
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
logger.error("Database Error:", err);
|
||||||
|
|
||||||
|
return res.code(500).send(DatabaseError());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -2,8 +2,9 @@ import type { FastifyInstance } from "fastify";
|
|||||||
import ChannelList from "./list";
|
import ChannelList from "./list";
|
||||||
import ChannelCreate from "./create";
|
import ChannelCreate from "./create";
|
||||||
import ChannelEdit from "./edit";
|
import ChannelEdit from "./edit";
|
||||||
|
import ChannelGet from "./get";
|
||||||
|
|
||||||
export default async function Setup(fastify: FastifyInstance) {
|
export default async function Channel(fastify: FastifyInstance) {
|
||||||
await fastify.register(ChannelList, {
|
await fastify.register(ChannelList, {
|
||||||
prefix: "/list",
|
prefix: "/list",
|
||||||
});
|
});
|
||||||
@@ -15,4 +16,8 @@ export default async function Setup(fastify: FastifyInstance) {
|
|||||||
await fastify.register(ChannelEdit, {
|
await fastify.register(ChannelEdit, {
|
||||||
prefix: "/edit",
|
prefix: "/edit",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await fastify.register(ChannelGet, {
|
||||||
|
prefix: "/get",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
@@ -16,6 +16,7 @@ export default async function ChannelList(fastify: FastifyInstance) {
|
|||||||
const bodySchema = z.object({
|
const bodySchema = z.object({
|
||||||
limit: z.number().optional(),
|
limit: z.number().optional(),
|
||||||
since: z.string().optional(),
|
since: z.string().optional(),
|
||||||
|
community: z.string().length(10),
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = bodySchema.safeParse(req.body);
|
const result = bodySchema.safeParse(req.body);
|
||||||
@@ -27,10 +28,11 @@ export default async function ChannelList(fastify: FastifyInstance) {
|
|||||||
try {
|
try {
|
||||||
const channelRepo = fastify.orm.em.getRepository(ChannelEntity);
|
const channelRepo = fastify.orm.em.getRepository(ChannelEntity);
|
||||||
|
|
||||||
const findResult = await channelRepo.findChannel(
|
const findResult = await channelRepo.listChannel(
|
||||||
|
result.data.community,
|
||||||
result.data.limit,
|
result.data.limit,
|
||||||
result.data.since,
|
result.data.since,
|
||||||
) ?? [];
|
);
|
||||||
|
|
||||||
if ("error" in findResult) {
|
if ("error" in findResult) {
|
||||||
return res.code(400).send(findResult);
|
return res.code(400).send(findResult);
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import { DatabaseError, InputError } from "@/errors";
|
||||||
|
import Logger from "@/lib/logger";
|
||||||
|
import { CommunityEntity } from "@/modules/entities/Community";
|
||||||
|
import { CommunityRepository } from "@/modules/repositories/Community";
|
||||||
|
import type { FastifyInstance } from "fastify";
|
||||||
|
|
||||||
|
export default async function CommunityCreate(fastify: FastifyInstance) {
|
||||||
|
const logger = new Logger("Endpoint | community/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 = CommunityRepository.schema.omit({ icon: true }).safeParse({
|
||||||
|
...req.body as any,
|
||||||
|
userid: req.token.user.userid,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
return res.code(400).send(InputError(result.error.issues));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const communityRepo = fastify.orm.em.getRepository(CommunityEntity);
|
||||||
|
|
||||||
|
const id = await communityRepo.createCommunity(result.data);
|
||||||
|
|
||||||
|
return res.send({
|
||||||
|
success: true,
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
} catch (err: any) {
|
||||||
|
if (err.name === "UniqueConstraintViolationException") {
|
||||||
|
const duplicate = err.constraint.replace("community_", "").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 { CommunityEntity } from "@/modules/entities/Community";
|
||||||
|
import { CommunityRepository } from "@/modules/repositories/Community";
|
||||||
|
import type { FastifyInstance } from "fastify";
|
||||||
|
import z from "zod/v3";
|
||||||
|
|
||||||
|
export default async function CommunityEdit(fastify: FastifyInstance) {
|
||||||
|
const logger = new Logger("Endpoint | community/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 = CommunityRepository.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 communityRepo = fastify.orm.em.getRepository(CommunityEntity);
|
||||||
|
const itCommunity = await communityRepo.findOne({ id: result.data.id });
|
||||||
|
|
||||||
|
if (!itCommunity) {
|
||||||
|
return res.code(400).send(ErrorBase({
|
||||||
|
bad: "client",
|
||||||
|
code: "community_not_found",
|
||||||
|
message: "対象のコミュニティが見つかりませんでした。",
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = await communityRepo.editCommunity(itCommunity, result.data);
|
||||||
|
|
||||||
|
return res.send({
|
||||||
|
success: true,
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
logger.error("Database Error:", err);
|
||||||
|
|
||||||
|
return res.code(500).send(DatabaseError());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { DatabaseError, ErrorBase, InputError } from "@/errors";
|
||||||
|
import Logger from "@/lib/logger";
|
||||||
|
import { CommunityEntity } from "@/modules/entities/Community";
|
||||||
|
import type { FastifyInstance } from "fastify";
|
||||||
|
import z from "zod/v3";
|
||||||
|
|
||||||
|
export default async function CommunityGet(fastify: FastifyInstance) {
|
||||||
|
const logger = new Logger("Endpoint | community/get");
|
||||||
|
|
||||||
|
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({
|
||||||
|
id: z.string().length(10),
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = bodySchema.safeParse(req.body);
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
return res.code(400).send(InputError(result.error.issues));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const communityRepo = fastify.orm.em.getRepository(CommunityEntity);
|
||||||
|
|
||||||
|
const findResult = await communityRepo.find({
|
||||||
|
id: result.data.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (findResult[0] === undefined) {
|
||||||
|
return res.code(400).send(ErrorBase({
|
||||||
|
bad: "client",
|
||||||
|
code: "community_not_found",
|
||||||
|
message: "対象のコミュニティが見つかりませんでした。",
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.send({
|
||||||
|
success: true,
|
||||||
|
community: findResult[0],
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
logger.error("Database Error:", err);
|
||||||
|
|
||||||
|
return res.code(500).send(DatabaseError());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import type { FastifyInstance } from "fastify";
|
||||||
|
import CommunityList from "./list";
|
||||||
|
import CommunityCreate from "./create";
|
||||||
|
import CommunityEdit from "./edit";
|
||||||
|
import CommunityGet from "./get";
|
||||||
|
|
||||||
|
export default async function Community(fastify: FastifyInstance) {
|
||||||
|
await fastify.register(CommunityList, {
|
||||||
|
prefix: "/list",
|
||||||
|
});
|
||||||
|
|
||||||
|
await fastify.register(CommunityCreate, {
|
||||||
|
prefix: "/create",
|
||||||
|
});
|
||||||
|
|
||||||
|
await fastify.register(CommunityEdit, {
|
||||||
|
prefix: "/edit",
|
||||||
|
});
|
||||||
|
|
||||||
|
await fastify.register(CommunityGet, {
|
||||||
|
prefix: "/get",
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { DatabaseError, InputError } from "@/errors";
|
||||||
|
import Logger from "@/lib/logger";
|
||||||
|
import { CommunityEntity } from "@/modules/entities/Community";
|
||||||
|
import { UserEntity } from "@/modules/entities/User";
|
||||||
|
import type { FastifyInstance } from "fastify";
|
||||||
|
import z from "zod/v3";
|
||||||
|
|
||||||
|
export default async function CommunityList(fastify: FastifyInstance) {
|
||||||
|
const logger = new Logger("Endpoint | community/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 communityRepo = fastify.orm.em.getRepository(CommunityEntity);
|
||||||
|
|
||||||
|
const findResult = await communityRepo.listCommunity(
|
||||||
|
result.data.limit,
|
||||||
|
result.data.since,
|
||||||
|
);
|
||||||
|
|
||||||
|
if ("error" in findResult) {
|
||||||
|
return res.code(400).send(findResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
findResult.map((community) => ({
|
||||||
|
...community,
|
||||||
|
createdBy: community.createdBy.userid,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return res.send({
|
||||||
|
success: true,
|
||||||
|
communitys: findResult.map(community => ({
|
||||||
|
...community,
|
||||||
|
createdBy: community.createdBy.userid,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
logger.error("Database Error:", err);
|
||||||
|
|
||||||
|
return res.code(500).send(DatabaseError());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@ import Setup from "./setup";
|
|||||||
import Primary from "./primary";
|
import Primary from "./primary";
|
||||||
import Me from "./me";
|
import Me from "./me";
|
||||||
import ServerInfo from "./server-info";
|
import ServerInfo from "./server-info";
|
||||||
|
import Community from "./community";
|
||||||
|
import Channel from "./channel";
|
||||||
|
|
||||||
export default async function Routes(fastify: FastifyInstance) {
|
export default async function Routes(fastify: FastifyInstance) {
|
||||||
await fastify.register(Setup, {
|
await fastify.register(Setup, {
|
||||||
@@ -20,4 +22,12 @@ export default async function Routes(fastify: FastifyInstance) {
|
|||||||
await fastify.register(Me, {
|
await fastify.register(Me, {
|
||||||
prefix: "/me",
|
prefix: "/me",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await fastify.register(Community, {
|
||||||
|
prefix: "/community",
|
||||||
|
});
|
||||||
|
|
||||||
|
await fastify.register(Channel, {
|
||||||
|
prefix: "/channel",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,10 +19,12 @@
|
|||||||
"vue": "^3.5.24",
|
"vue": "^3.5.24",
|
||||||
"vue-router": "^5.0.2",
|
"vue-router": "^5.0.2",
|
||||||
"vue-tsc": "^3.1.4",
|
"vue-tsc": "^3.1.4",
|
||||||
"zod": "^4.3.6"
|
"zod": "^4.3.6",
|
||||||
|
"zxcvbn": "^4.4.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^24.10.1"
|
"@types/node": "^24.10.1",
|
||||||
|
"@types/zxcvbn": "^4.4.5"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.29.1"
|
"packageManager": "pnpm@10.29.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,21 +7,42 @@
|
|||||||
|
|
||||||
<div class="modals-container" />
|
<div class="modals-container" />
|
||||||
|
|
||||||
<main class="layout" v-if="serverInfo.success && account.success">
|
<main class="layout">
|
||||||
<div class="left-menu">
|
<div class="left-menu">
|
||||||
<div :class='$route.path === "/" ? "isActive" : ""'>
|
<RouterLink
|
||||||
<img :src="serverInfo.icon" />
|
to="/"
|
||||||
</div>
|
:class='$route.path === "/"
|
||||||
|
? "isActive"
|
||||||
|
: ""'
|
||||||
|
>
|
||||||
|
<img :src='"icon" in serverInfo
|
||||||
|
? serverInfo.icon
|
||||||
|
: "/assets/lynqchat.svg"'
|
||||||
|
/>
|
||||||
|
</RouterLink>
|
||||||
|
|
||||||
<div :class='$route.path === "/home" ? "isActive" : ""'>
|
<RouterLink
|
||||||
<Icon icon="material-symbols:home-rounded" />
|
v-for="community of communitys"
|
||||||
</div>
|
:to="`/community/${community.id}`"
|
||||||
|
:class='$route.path === `/community/${community.id}`
|
||||||
|
? "isActive"
|
||||||
|
: ""'
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-if="community.icon"
|
||||||
|
:src="community.icon"
|
||||||
|
/>
|
||||||
|
<Icon
|
||||||
|
v-else
|
||||||
|
icon="material-symbols:groups-rounded"
|
||||||
|
/>
|
||||||
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content-main">
|
<div class="content-main">
|
||||||
<div class="content-header">
|
<div class="content-header">
|
||||||
{{
|
{{
|
||||||
$route.meta.title
|
title
|
||||||
?? (serverInfo.success
|
?? (serverInfo.success
|
||||||
? serverInfo.name
|
? serverInfo.name
|
||||||
: null)
|
: null)
|
||||||
@@ -61,7 +82,8 @@ main.layout {
|
|||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.left-menu div {
|
.left-menu a {
|
||||||
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
@@ -71,19 +93,20 @@ main.layout {
|
|||||||
transition: background-color 200ms ease-out;
|
transition: background-color 200ms ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.left-menu div * {
|
.left-menu a * {
|
||||||
font-size: 3rem;
|
font-size: 3rem;
|
||||||
padding: 0.25rem;
|
padding: 0.25rem;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
width: 3rem;
|
width: 3rem;
|
||||||
height: 3rem;
|
height: 3rem;
|
||||||
|
color: var(--text-color);
|
||||||
display: block;
|
display: block;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.left-menu div.isActive::before {
|
.left-menu a.isActive::before {
|
||||||
content: "";
|
content: "";
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -96,8 +119,8 @@ main.layout {
|
|||||||
background-color: var(--text-color);
|
background-color: var(--text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.left-menu div:hover,
|
.left-menu a:hover,
|
||||||
.left-menu div.isActive {
|
.left-menu a.isActive {
|
||||||
background-color: var(--border-color);
|
background-color: var(--border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,6 +145,7 @@ main.layout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.route-main {
|
.route-main {
|
||||||
|
display: flex;
|
||||||
padding: 1.25rem;
|
padding: 1.25rem;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
overflow: scroll;
|
overflow: scroll;
|
||||||
@@ -156,16 +180,25 @@ main.layout {
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { RouterView, useRouter } from "vue-router";
|
import { RouterView, RouterLink, useRouter, useRoute } from "vue-router";
|
||||||
import routerStatus from "@/lib/router";
|
import routerStatus, { title } from "@/lib/router";
|
||||||
import { Icon } from "@iconify/vue";
|
import { Icon } from "@iconify/vue";
|
||||||
import Progress from "@/components/Progress.vue";
|
import Progress from "@/components/Progress.vue";
|
||||||
import { account, serverInfo } from "@/lib/account";
|
import { communitys, serverInfo } from "@/lib/account";
|
||||||
|
import { onBeforeUnmount, onMounted, watch } from "vue";
|
||||||
|
import { createModal } from "@/lib/modal";
|
||||||
|
import ErrorModal from "@/components/Modal/Error.vue";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
watch(route, () => {
|
||||||
|
if (typeof route.meta.title === "string")
|
||||||
|
title.value = route.meta.title;
|
||||||
|
});
|
||||||
|
|
||||||
if (!serverInfo.value.success) {
|
if (!serverInfo.value.success) {
|
||||||
throw new Error();
|
throw new Error("サーバー情報の取得に失敗しました。");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!serverInfo.value.isInitialized) {
|
if (!serverInfo.value.isInitialized) {
|
||||||
@@ -175,4 +208,45 @@ if (!serverInfo.value.isInitialized) {
|
|||||||
if (!serverInfo.value.isFirstAdminExists) {
|
if (!serverInfo.value.isFirstAdminExists) {
|
||||||
router.replace("/setup/create-admin");
|
router.replace("/setup/create-admin");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleError(event: ErrorEvent | PromiseRejectionEvent) {
|
||||||
|
let content = event instanceof PromiseRejectionEvent
|
||||||
|
? event.reason
|
||||||
|
: event;
|
||||||
|
|
||||||
|
if (content instanceof Error) {
|
||||||
|
content = content.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
createModal({
|
||||||
|
component: ErrorModal,
|
||||||
|
props: {
|
||||||
|
error: content ?? "不明なエラー",
|
||||||
|
canClose: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
window.addEventListener("error", handleError);
|
||||||
|
window.addEventListener("unhandledrejection", handleError);
|
||||||
|
|
||||||
|
if ("serviceWorker" in navigator) {
|
||||||
|
const swFile = import.meta.env.MODE === "production"
|
||||||
|
? "/sw.js"
|
||||||
|
: "/dev-sw.js?dev-sw";
|
||||||
|
|
||||||
|
navigator.serviceWorker.register(swFile, {
|
||||||
|
type: import.meta.env.MODE === "production"
|
||||||
|
? "classic"
|
||||||
|
: "module",
|
||||||
|
scope: "/",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener("error", handleError);
|
||||||
|
window.removeEventListener("unhandledrejection", handleError);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<template>
|
||||||
|
<div class="modal">
|
||||||
|
<Icon
|
||||||
|
icon="line-md:close-circle"
|
||||||
|
class="error-badge"
|
||||||
|
width="4rem"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span class="modal-title">問題が発生しました</span>
|
||||||
|
|
||||||
|
<p v-html="error" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
name="ホームに戻る"
|
||||||
|
color="accent"
|
||||||
|
@click='$emit("PleaseClose")'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.modal {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-badge {
|
||||||
|
color: var(--error-color);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Icon } from "@iconify/vue";
|
||||||
|
import Button from "@/components/Button.vue";
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
error: any;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
<label :for="id">{{ label }}</label>
|
<label :for="id">{{ label }}</label>
|
||||||
|
|
||||||
<textarea
|
<textarea
|
||||||
|
autocomplete="off"
|
||||||
v-model="model"
|
v-model="model"
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
:class="$attrs.class"
|
:class="$attrs.class"
|
||||||
|
|||||||
@@ -66,6 +66,6 @@ label {
|
|||||||
padding: 3rem 6rem;
|
padding: 3rem 6rem;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
max-width: 90dvw;
|
max-width: 70dvw;
|
||||||
max-height: 90dvh;
|
max-height: 90dvh;
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,33 @@
|
|||||||
import client, { initClient } from "@/lib/client";
|
import client, { initClient } from "@/lib/client";
|
||||||
import type ApiMap from "lynqchat-js/1.0.0-alpha.0/map";
|
import type ApiMap from "lynqchat-js/1.0.0-alpha.0/map";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
/*
|
|
||||||
[TODO]
|
|
||||||
キャッシュの類を全部account.tsに詰め込むのをやめる
|
|
||||||
*/
|
|
||||||
|
|
||||||
await initClient();
|
await initClient();
|
||||||
|
|
||||||
export let serverInfo = ref<ApiMap["server-info"]["response"]>(await client.value.request("server-info"));
|
export let serverInfo = ref<ApiMap["server-info"]["response"]>(await client.value.request("server-info"));
|
||||||
export let account = ref<ApiMap["me"]["response"]>(await client.value.request("me"));
|
export let account = ref<ApiMap["me"]["response"]>(await client.value.request("me"));
|
||||||
|
|
||||||
export let channels = ref<Extract<ApiMap["channel/list"]["response"], { channels: any }>["channels"]>([]);
|
let communitys = ref<Extract<ApiMap["community/list"]["response"], { communitys: any }>["communitys"]>([]);
|
||||||
export let lastLoadedChannel = ref<string>();
|
let lastLoadedCommunity = ref<string>();
|
||||||
|
export const reloadCommunitys = async () => {
|
||||||
|
lastLoadedCommunity.value = undefined;
|
||||||
|
|
||||||
|
const response = await client.value.request("community/list");
|
||||||
|
|
||||||
|
if (!response.success)
|
||||||
|
return;
|
||||||
|
|
||||||
|
communitys.value = response.communitys;
|
||||||
|
if (response.communitys.length > 0) {
|
||||||
|
lastLoadedCommunity.value = response.communitys[response.communitys.length - 1]?.id;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await reloadCommunitys();
|
||||||
|
export {
|
||||||
|
communitys,
|
||||||
|
lastLoadedCommunity,
|
||||||
|
}
|
||||||
|
|
||||||
export const reloadServerInfo = async () => {
|
export const reloadServerInfo = async () => {
|
||||||
serverInfo.value = await client.value.request("server-info");
|
serverInfo.value = await client.value.request("server-info");
|
||||||
@@ -24,34 +39,18 @@ export const reloadAccount = async () => {
|
|||||||
account.value = await client.value.request("me");
|
account.value = await client.value.request("me");
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initChannels = async () => {
|
export const loadCommunitys = async () => {
|
||||||
lastLoadedChannel.value = undefined;
|
const response = await client.value.request("community/list", lastLoadedCommunity.value ? {
|
||||||
|
since: lastLoadedCommunity.value,
|
||||||
const response = await client.value.request("channel/list");
|
|
||||||
|
|
||||||
if (!response.success) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
channels.value = response.channels;
|
|
||||||
if (response.channels.length > 0) {
|
|
||||||
lastLoadedChannel.value = response.channels[response.channels.length - 1]?.id;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const loadChannels = async () => {
|
|
||||||
const response = await client.value.request("channel/list", lastLoadedChannel.value ? {
|
|
||||||
since: lastLoadedChannel.value,
|
|
||||||
} : undefined);
|
} : undefined);
|
||||||
|
|
||||||
if (!response.success) {
|
if (!response.success) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
channels.value = channels.value.concat(response.channels);
|
communitys.value = communitys.value.concat(response.communitys);
|
||||||
if (response.channels.length > 0) {
|
if (response.communitys.length > 0) {
|
||||||
lastLoadedChannel.value = response.channels[response.channels.length - 1]?.id;
|
lastLoadedCommunity.value = response.communitys[response.communitys.length - 1]?.id;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import Dexie, { type EntityTable } from "dexie";
|
import Dexie, { type EntityTable } from "dexie";
|
||||||
|
|
||||||
export interface Settings {
|
interface Settings {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
value: string;
|
value: string;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { reactive } from "vue";
|
import { reactive, ref } from "vue";
|
||||||
|
|
||||||
const routerStatus = reactive<{
|
const routerStatus = reactive<{
|
||||||
isLoad: boolean;
|
isLoad: boolean;
|
||||||
@@ -6,4 +6,6 @@ const routerStatus = reactive<{
|
|||||||
isLoad: false,
|
isLoad: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default routerStatus;
|
export default routerStatus;
|
||||||
|
|
||||||
|
export const title = ref<string | undefined>(undefined);
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
const swSelf = globalThis as unknown as ServiceWorkerGlobalScope;
|
||||||
|
|
||||||
|
swSelf.addEventListener("install", (event) => {
|
||||||
|
event.waitUntil(swSelf.skipWaiting());
|
||||||
|
});
|
||||||
|
|
||||||
|
swSelf.addEventListener("activate", (event) => {
|
||||||
|
event.waitUntil(swSelf.clients.claim());
|
||||||
|
});
|
||||||
|
|
||||||
|
swSelf.addEventListener("fetch", (event) => {
|
||||||
|
const request = event.request;
|
||||||
|
|
||||||
|
if (request.url.indexOf("http") !== 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
event.respondWith((async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(request);
|
||||||
|
return res;
|
||||||
|
} catch (err) {
|
||||||
|
return new Response("Network error", {
|
||||||
|
status: 504,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})());
|
||||||
|
});
|
||||||
@@ -46,6 +46,13 @@ const router = createRouter({
|
|||||||
},
|
},
|
||||||
component: () => import("@/routes/signin.vue"),
|
component: () => import("@/routes/signin.vue"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/community/:id",
|
||||||
|
meta: {
|
||||||
|
title: "コミュニティ",
|
||||||
|
},
|
||||||
|
component: () => import("@/routes/community/index.vue"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/:NotFound(.*)*",
|
path: "/:NotFound(.*)*",
|
||||||
meta: {
|
meta: {
|
||||||
@@ -55,11 +62,19 @@ const router = createRouter({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
router.beforeEach(() => {
|
router.beforeEach((to, from) => {
|
||||||
|
if (to.path === from.path)
|
||||||
|
return false;
|
||||||
|
|
||||||
routerStatus.isLoad = true;
|
routerStatus.isLoad = true;
|
||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
router.afterEach((to) => {
|
router.afterEach((to, from) => {
|
||||||
|
if (to.path === from.path) {
|
||||||
|
routerStatus.isLoad = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const title = to.meta.title;
|
const title = to.meta.title;
|
||||||
|
|
||||||
let serverName = "LynqChat";
|
let serverName = "LynqChat";
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
<template>
|
||||||
|
<Progress
|
||||||
|
v-if="isProcessing"
|
||||||
|
:size="24"
|
||||||
|
class="processing-progress"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.processing-progress {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
import { account, serverInfo } from "@/lib/account";
|
||||||
|
import client from "@/lib/client";
|
||||||
|
import { createModal } from "@/lib/modal";
|
||||||
|
import GoHomeError from "@/components/Modal/GoHomeError.vue";
|
||||||
|
import { ref } from "vue";
|
||||||
|
import Progress from "@/components/Progress.vue";
|
||||||
|
import { title } from "@/lib/router";
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const isProcessing = ref<boolean>(false);
|
||||||
|
|
||||||
|
if (!serverInfo.value.success) {
|
||||||
|
throw new Error("サーバー情報の取得に失敗しました。");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!serverInfo.value.isInitialized) {
|
||||||
|
router.replace("/setup/initialization");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!account.value.success) {
|
||||||
|
switch (account.value.error.bad) {
|
||||||
|
case "client":
|
||||||
|
router.replace("/signin");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error("アカウント情報の取得に失敗しました。");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
isProcessing.value = true;
|
||||||
|
|
||||||
|
if (!serverInfo.value.success) {
|
||||||
|
throw new Error("サーバー情報の取得に失敗しました。");
|
||||||
|
}
|
||||||
|
|
||||||
|
const communityId = route.params.id;
|
||||||
|
if (typeof communityId !== "string") {
|
||||||
|
isProcessing.value = false;
|
||||||
|
createModal({
|
||||||
|
component: GoHomeError,
|
||||||
|
onClose: async () => await router.push("/"),
|
||||||
|
props: {
|
||||||
|
error: "不正なアクセスです。",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const community = await client.value.request("community/get", {
|
||||||
|
id: communityId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!community.success) {
|
||||||
|
isProcessing.value = false;
|
||||||
|
createModal({
|
||||||
|
component: GoHomeError,
|
||||||
|
onClose: async () => await router.push("/"),
|
||||||
|
props: {
|
||||||
|
error: "コミュニティが取得できませんでした。",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.title = `${community.community.name} | ${serverInfo.value.name}`;
|
||||||
|
title.value = community.community.name;
|
||||||
|
|
||||||
|
isProcessing.value = false;
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
@@ -8,6 +8,12 @@ import { useRouter } from "vue-router";
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
if (!account.value.success) {
|
if (!account.value.success) {
|
||||||
router.replace("/signin");
|
switch (account.value.error.bad) {
|
||||||
|
case "client":
|
||||||
|
router.replace("/signin");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error("アカウント情報の取得に失敗しました。");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
<form novalidate @submit="submit">
|
<form novalidate @submit="submit">
|
||||||
<Input
|
<Input
|
||||||
label="ユーザーID"
|
label="ユーザーID"
|
||||||
autocomplete="off"
|
autocomplete="userid"
|
||||||
v-model="userid"
|
v-model="userid"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@@ -48,6 +48,13 @@
|
|||||||
<Icon icon="material-symbols:error-outline-rounded" />
|
<Icon icon="material-symbols:error-outline-rounded" />
|
||||||
{{ passwordIssue.message }}
|
{{ passwordIssue.message }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
class="password-strength"
|
||||||
|
v-if="passwordStrength.message"
|
||||||
|
>
|
||||||
|
{{ passwordStrength.message }}
|
||||||
|
</span>
|
||||||
</InputPassword>
|
</InputPassword>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@@ -71,6 +78,7 @@ form {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
max-width: 30rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-issue {
|
.input-issue {
|
||||||
@@ -96,9 +104,10 @@ import Button from "@/components/Button.vue";
|
|||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import z from "zod/v3";
|
import z from "zod/v3";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
|
import zxcvbn from "zxcvbn";
|
||||||
import { createModal } from "@/lib/modal";
|
import { createModal } from "@/lib/modal";
|
||||||
import Loading from "@/components/Modal/Loading.vue";
|
import Loading from "@/components/Modal/Loading.vue";
|
||||||
import Error from "@/components/Modal/Error.vue";
|
import ErrorModal from "@/components/Modal/Error.vue";
|
||||||
import Success from "@/components/Modal/Success.vue";
|
import Success from "@/components/Modal/Success.vue";
|
||||||
import InputPassword from "@/components/InputPassword.vue";
|
import InputPassword from "@/components/InputPassword.vue";
|
||||||
import client from "@/lib/client";
|
import client from "@/lib/client";
|
||||||
@@ -109,7 +118,7 @@ const router = useRouter();
|
|||||||
const isProcessing = ref<boolean>(false);
|
const isProcessing = ref<boolean>(false);
|
||||||
|
|
||||||
if (!serverInfo.value.success) {
|
if (!serverInfo.value.success) {
|
||||||
throw new Error();
|
throw new Error("サーバー情報の取得に失敗しました。");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serverInfo.value.isFirstAdminExists) {
|
if (serverInfo.value.isFirstAdminExists) {
|
||||||
@@ -127,6 +136,21 @@ const useridIssue = computed(() => getIssueFromPath("userid", result.value));
|
|||||||
const emailIssue = computed(() => getIssueFromPath("email", result.value));
|
const emailIssue = computed(() => getIssueFromPath("email", result.value));
|
||||||
const passwordIssue = computed(() => getIssueFromPath("password", result.value));
|
const passwordIssue = computed(() => getIssueFromPath("password", result.value));
|
||||||
|
|
||||||
|
const passwordScores = [
|
||||||
|
"危険なパスワード",
|
||||||
|
"推測可能なパスワード",
|
||||||
|
"推測しやすいパスワード",
|
||||||
|
"安全なパスワード",
|
||||||
|
"強力なパスワード",
|
||||||
|
];
|
||||||
|
const passwordStrength = computed(() => {
|
||||||
|
const evaluation = zxcvbn(password.value);
|
||||||
|
return {
|
||||||
|
message: passwordScores[evaluation.score],
|
||||||
|
score: evaluation.score,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const EmailRegex = /^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:([0-9]{1,3}\.){3}[0-9]{1,3})\])$/i;
|
const EmailRegex = /^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:([0-9]{1,3}\.){3}[0-9]{1,3})\])$/i;
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
userid: z.string({ message: "文字列で入力してください。" })
|
userid: z.string({ message: "文字列で入力してください。" })
|
||||||
@@ -160,7 +184,7 @@ const submit = async (e: Event) => {
|
|||||||
closeLoadingModal();
|
closeLoadingModal();
|
||||||
|
|
||||||
return createModal({
|
return createModal({
|
||||||
component: Error,
|
component: ErrorModal,
|
||||||
onClose: () => isProcessing.value = false,
|
onClose: () => isProcessing.value = false,
|
||||||
props: {
|
props: {
|
||||||
error: `不正な入力です。<br>${messages.join("<br>")}`,
|
error: `不正な入力です。<br>${messages.join("<br>")}`,
|
||||||
@@ -175,7 +199,7 @@ const submit = async (e: Event) => {
|
|||||||
closeLoadingModal();
|
closeLoadingModal();
|
||||||
|
|
||||||
return createModal({
|
return createModal({
|
||||||
component: Error,
|
component: ErrorModal,
|
||||||
onClose: () => isProcessing.value = false,
|
onClose: () => isProcessing.value = false,
|
||||||
props: {
|
props: {
|
||||||
error: response.error.message,
|
error: response.error.message,
|
||||||
|
|||||||
@@ -28,7 +28,6 @@
|
|||||||
|
|
||||||
<Textarea
|
<Textarea
|
||||||
label="サーバー説明"
|
label="サーバー説明"
|
||||||
autocomplete="on"
|
|
||||||
v-model="description"
|
v-model="description"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@@ -97,7 +96,7 @@ import z from "zod/v3";
|
|||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { createModal } from "@/lib/modal";
|
import { createModal } from "@/lib/modal";
|
||||||
import Loading from "@/components/Modal/Loading.vue";
|
import Loading from "@/components/Modal/Loading.vue";
|
||||||
import Error from "@/components/Modal/Error.vue";
|
import ErrorModal from "@/components/Modal/Error.vue";
|
||||||
import Success from "@/components/Modal/Success.vue";
|
import Success from "@/components/Modal/Success.vue";
|
||||||
import client from "@/lib/client";
|
import client from "@/lib/client";
|
||||||
import { getIssueFromPath } from "@/lib/validation";
|
import { getIssueFromPath } from "@/lib/validation";
|
||||||
@@ -108,7 +107,7 @@ const router = useRouter();
|
|||||||
const isProcessing = ref<boolean>(false);
|
const isProcessing = ref<boolean>(false);
|
||||||
|
|
||||||
if (!serverInfo.value.success) {
|
if (!serverInfo.value.success) {
|
||||||
throw new Error();
|
throw new Error("サーバー情報の取得に失敗しました。");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serverInfo.value.isInitialized) {
|
if (serverInfo.value.isInitialized) {
|
||||||
@@ -154,7 +153,7 @@ const submit = async (e: Event) => {
|
|||||||
closeLoadingModal();
|
closeLoadingModal();
|
||||||
|
|
||||||
return createModal({
|
return createModal({
|
||||||
component: Error,
|
component: ErrorModal,
|
||||||
onClose: () => isProcessing.value = false,
|
onClose: () => isProcessing.value = false,
|
||||||
props: {
|
props: {
|
||||||
error: `不正な入力です。<br>${messages.join("<br>")}`,
|
error: `不正な入力です。<br>${messages.join("<br>")}`,
|
||||||
@@ -169,7 +168,7 @@ const submit = async (e: Event) => {
|
|||||||
closeLoadingModal();
|
closeLoadingModal();
|
||||||
|
|
||||||
return createModal({
|
return createModal({
|
||||||
component: Error,
|
component: ErrorModal,
|
||||||
onClose: () => isProcessing.value = false,
|
onClose: () => isProcessing.value = false,
|
||||||
props: {
|
props: {
|
||||||
error: response.error.message,
|
error: response.error.message,
|
||||||
|
|||||||
@@ -62,11 +62,12 @@ form {
|
|||||||
import Button from "@/components/Button.vue";
|
import Button from "@/components/Button.vue";
|
||||||
import Input from "@/components/Input.vue";
|
import Input from "@/components/Input.vue";
|
||||||
import InputPassword from "@/components/InputPassword.vue";
|
import InputPassword from "@/components/InputPassword.vue";
|
||||||
|
import ErrorModal from "@/components/Modal/Error.vue";
|
||||||
import Loading from "@/components/Modal/Loading.vue";
|
import Loading from "@/components/Modal/Loading.vue";
|
||||||
import Success from "@/components/Modal/Success.vue";
|
import Success from "@/components/Modal/Success.vue";
|
||||||
import { account, reloadAccount, serverInfo } from "@/lib/account";
|
import { account, reloadAccount, reloadCommunitys, serverInfo } from "@/lib/account";
|
||||||
import client from "@/lib/client";
|
import client from "@/lib/client";
|
||||||
import Database from "@/lib/db";
|
import Database, { getByIndex } from "@/lib/db";
|
||||||
import { createModal } from "@/lib/modal";
|
import { createModal } from "@/lib/modal";
|
||||||
import { getIssueFromPath } from "@/lib/validation";
|
import { getIssueFromPath } from "@/lib/validation";
|
||||||
import { Icon } from "@iconify/vue";
|
import { Icon } from "@iconify/vue";
|
||||||
@@ -82,13 +83,21 @@ if (account.value.success) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!serverInfo.value.success) {
|
if (!serverInfo.value.success) {
|
||||||
throw new Error();
|
throw new Error("サーバー情報の取得に失敗しました。");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!serverInfo.value.isInitialized) {
|
if (!serverInfo.value.isInitialized) {
|
||||||
router.replace("/setup/initialization");
|
router.replace("/setup/initialization");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const db = new Database();
|
||||||
|
const tokenRow = await getByIndex(db.settings, "name", "token");
|
||||||
|
if (tokenRow) {
|
||||||
|
await db.settings.delete(tokenRow.id);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
const userid = ref<string>("");
|
const userid = ref<string>("");
|
||||||
const password = ref<string>("");
|
const password = ref<string>("");
|
||||||
const useridIssue = computed(() => getIssueFromPath("userid", result.value));
|
const useridIssue = computed(() => getIssueFromPath("userid", result.value));
|
||||||
@@ -121,7 +130,7 @@ const submit = async (e: Event) => {
|
|||||||
closeLoadingModal();
|
closeLoadingModal();
|
||||||
|
|
||||||
return createModal({
|
return createModal({
|
||||||
component: Error,
|
component: ErrorModal,
|
||||||
onClose: () => isProcessing.value = false,
|
onClose: () => isProcessing.value = false,
|
||||||
props: {
|
props: {
|
||||||
error: `不正な入力です。<br>${messages.join("<br>")}`,
|
error: `不正な入力です。<br>${messages.join("<br>")}`,
|
||||||
@@ -136,7 +145,7 @@ const submit = async (e: Event) => {
|
|||||||
closeLoadingModal();
|
closeLoadingModal();
|
||||||
|
|
||||||
return createModal({
|
return createModal({
|
||||||
component: Error,
|
component: ErrorModal,
|
||||||
onClose: () => isProcessing.value = false,
|
onClose: () => isProcessing.value = false,
|
||||||
props: {
|
props: {
|
||||||
error: response.error.message,
|
error: response.error.message,
|
||||||
@@ -150,7 +159,9 @@ const submit = async (e: Event) => {
|
|||||||
name: "token",
|
name: "token",
|
||||||
value: response.token,
|
value: response.token,
|
||||||
});
|
});
|
||||||
|
|
||||||
await reloadAccount();
|
await reloadAccount();
|
||||||
|
await reloadCommunitys();
|
||||||
|
|
||||||
closeLoadingModal();
|
closeLoadingModal();
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"types": ["vite/client"],
|
"types": ["vite/client"],
|
||||||
"typeRoots": ["./node_modules/"],
|
"typeRoots": ["./node_modules/"],
|
||||||
"target": "ES2023",
|
"target": "ES2023",
|
||||||
"lib": ["ES2023", "DOM"],
|
"lib": ["ES2023", "DOM", "WebWorker", "WebWorker.Iterable"],
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
|
|
||||||
/* Linting */
|
/* Linting */
|
||||||
|
|||||||
@@ -1,13 +1,31 @@
|
|||||||
import { defineConfig, loadEnv } from "vite";
|
import { defineConfig, loadEnv } from "vite";
|
||||||
import vue from "@vitejs/plugin-vue";
|
import vue from "@vitejs/plugin-vue";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
import { VitePWA } from "vite-plugin-pwa";
|
||||||
|
|
||||||
export default defineConfig(({ mode }) => {
|
export default defineConfig(({ mode }) => {
|
||||||
const env = loadEnv(mode, process.cwd(), "")
|
const env = loadEnv(mode, process.cwd(), "")
|
||||||
const apiPort = Number(env.VITE_API_PORT) || 3300
|
const apiPort = Number(env.VITE_API_PORT) || 3300
|
||||||
|
|
||||||
return {
|
return {
|
||||||
plugins: [vue()],
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
VitePWA({
|
||||||
|
strategies: "injectManifest",
|
||||||
|
srcDir: "./src/lib",
|
||||||
|
filename: "sw.ts",
|
||||||
|
injectRegister: false,
|
||||||
|
manifest: false,
|
||||||
|
injectManifest: {
|
||||||
|
injectionPoint: undefined,
|
||||||
|
},
|
||||||
|
devOptions: {
|
||||||
|
enabled: true,
|
||||||
|
type: "module",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
],
|
||||||
server: {
|
server: {
|
||||||
hmr: {
|
hmr: {
|
||||||
clientPort: 5173,
|
clientPort: 5173,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import AuthError from "../..//modules/error/auth";
|
|||||||
|
|
||||||
export default interface ChannelCreate {
|
export default interface ChannelCreate {
|
||||||
"channel/create": {
|
"channel/create": {
|
||||||
body: Omit<Channel, "userid">;
|
body: Pick<Channel, "name", "description", "community">;
|
||||||
response: (Success & {
|
response: (Success & {
|
||||||
id: string;
|
id: string;
|
||||||
}) | InputError | AuthError | DatabaseError | YetInitializationError | UnknownError;
|
}) | InputError | AuthError | DatabaseError | YetInitializationError | UnknownError;
|
||||||
|
|||||||
@@ -5,16 +5,12 @@ import UnknownError from "../../modules/error/unknown";
|
|||||||
import Success from "../../modules/response/success";
|
import Success from "../../modules/response/success";
|
||||||
import Channel from "../../modules/channel";
|
import Channel from "../../modules/channel";
|
||||||
import YetInitializationError from "../../modules/error/yet_init";
|
import YetInitializationError from "../../modules/error/yet_init";
|
||||||
import AuthError from "../..//modules/error/auth";
|
import AuthError from "../../modules/error/auth";
|
||||||
|
import { RequireAtLeastOne } from "../../modules/generic";
|
||||||
type RequireAtLeastOne<T, Keys extends keyof T = keyof T> =
|
|
||||||
Keys extends keyof T
|
|
||||||
? Required<Pick<T, Keys>> & Partial<Omit<T, Keys>>
|
|
||||||
: never;
|
|
||||||
|
|
||||||
export default interface ChannelEdit {
|
export default interface ChannelEdit {
|
||||||
"channel/edit": {
|
"channel/edit": {
|
||||||
body: RequireAtLeastOne<Omit<Channel, "userid">>;
|
body: RequireAtLeastOne<Omit<Channel, "userid", "community">>;
|
||||||
response: (Success & {
|
response: (Success & {
|
||||||
id: string;
|
id: string;
|
||||||
}) | InputError | AuthError | DatabaseError | YetInitializationError | UnknownError;
|
}) | InputError | AuthError | DatabaseError | YetInitializationError | UnknownError;
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import InputError from "../../modules/error/input";
|
||||||
|
import ErrorBase from "../../modules/error";
|
||||||
|
import DatabaseError from "../../modules/error/database";
|
||||||
|
import UnknownError from "../../modules/error/unknown";
|
||||||
|
import Success from "../../modules/response/success";
|
||||||
|
import Channel from "../../modules/channel";
|
||||||
|
import YetInitializationError from "../../modules/error/yet_init";
|
||||||
|
import AuthError from "../../modules/error/auth";
|
||||||
|
|
||||||
|
export default interface ChannelGet {
|
||||||
|
"channel/get": {
|
||||||
|
body: {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
response: (Success & {
|
||||||
|
channel: Channel;
|
||||||
|
}) | InputError | AuthError | DatabaseError | YetInitializationError | UnknownError;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -9,16 +9,13 @@ import AuthError from "../..//modules/error/auth";
|
|||||||
|
|
||||||
export default interface ChannelList {
|
export default interface ChannelList {
|
||||||
"channel/list": {
|
"channel/list": {
|
||||||
body?: {
|
body: {
|
||||||
limit?: number;
|
limit?: number;
|
||||||
since?: string | Date;
|
since?: string | Date;
|
||||||
|
community: string;
|
||||||
};
|
};
|
||||||
response: (Success & {
|
response: (Success & {
|
||||||
channels: (Omit<Channel, "userid"> & {
|
channels: Channel[];
|
||||||
id: string;
|
|
||||||
createdBy: string;
|
|
||||||
createdAt: string;
|
|
||||||
})[];
|
|
||||||
}) | InputError | AuthError | DatabaseError | YetInitializationError | UnknownError;
|
}) | InputError | AuthError | DatabaseError | YetInitializationError | UnknownError;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import InputError from "../../modules/error/input";
|
||||||
|
import ErrorBase from "../../modules/error";
|
||||||
|
import DatabaseError from "../../modules/error/database";
|
||||||
|
import UnknownError from "../../modules/error/unknown";
|
||||||
|
import Success from "../../modules/response/success";
|
||||||
|
import Community from "../../modules/community";
|
||||||
|
import YetInitializationError from "../../modules/error/yet_init";
|
||||||
|
import AuthError from "../../modules/error/auth";
|
||||||
|
|
||||||
|
export default interface CommunityCreate {
|
||||||
|
"community/create": {
|
||||||
|
body: Pick<Community, "name", "description">;
|
||||||
|
response: (Success & {
|
||||||
|
id: string;
|
||||||
|
}) | InputError | AuthError | DatabaseError | YetInitializationError | UnknownError;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import InputError from "../../modules/error/input";
|
||||||
|
import ErrorBase from "../../modules/error";
|
||||||
|
import DatabaseError from "../../modules/error/database";
|
||||||
|
import UnknownError from "../../modules/error/unknown";
|
||||||
|
import Success from "../../modules/response/success";
|
||||||
|
import Community from "../../modules/community";
|
||||||
|
import YetInitializationError from "../../modules/error/yet_init";
|
||||||
|
import AuthError from "../../modules/error/auth";
|
||||||
|
import { RequireAtLeastOne } from "../../modules/generic";
|
||||||
|
|
||||||
|
export default interface CommunityEdit {
|
||||||
|
"community/edit": {
|
||||||
|
body: RequireAtLeastOne<Omit<Community, "userid">>;
|
||||||
|
response: (Success & {
|
||||||
|
id: string;
|
||||||
|
}) | InputError | AuthError | DatabaseError | YetInitializationError | UnknownError;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import InputError from "../../modules/error/input";
|
||||||
|
import ErrorBase from "../../modules/error";
|
||||||
|
import DatabaseError from "../../modules/error/database";
|
||||||
|
import UnknownError from "../../modules/error/unknown";
|
||||||
|
import Success from "../../modules/response/success";
|
||||||
|
import Community from "../../modules/community";
|
||||||
|
import YetInitializationError from "../../modules/error/yet_init";
|
||||||
|
import AuthError from "../../modules/error/auth";
|
||||||
|
|
||||||
|
export default interface CommunityGet {
|
||||||
|
"community/get": {
|
||||||
|
body: {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
response: (Success & {
|
||||||
|
community: Community;
|
||||||
|
}) | InputError | AuthError | DatabaseError | YetInitializationError | UnknownError;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import InputError from "../../modules/error/input";
|
||||||
|
import ErrorBase from "../../modules/error";
|
||||||
|
import DatabaseError from "../../modules/error/database";
|
||||||
|
import UnknownError from "../../modules/error/unknown";
|
||||||
|
import Success from "../../modules/response/success";
|
||||||
|
import Community from "../../modules/community";
|
||||||
|
import YetInitializationError from "../../modules/error/yet_init";
|
||||||
|
import AuthError from "../../modules/error/auth";
|
||||||
|
|
||||||
|
export default interface CommunityList {
|
||||||
|
"community/list": {
|
||||||
|
body?: {
|
||||||
|
limit?: number;
|
||||||
|
since?: string | Date;
|
||||||
|
};
|
||||||
|
response: (Success & {
|
||||||
|
communitys: Community[];
|
||||||
|
}) | InputError | AuthError | DatabaseError | YetInitializationError | UnknownError;
|
||||||
|
};
|
||||||
|
}
|
||||||
+10
@@ -1,6 +1,11 @@
|
|||||||
import ChannelCreate from "./api/channel/create";
|
import ChannelCreate from "./api/channel/create";
|
||||||
import ChannelEdit from "./api/channel/edit";
|
import ChannelEdit from "./api/channel/edit";
|
||||||
|
import ChannelGet from "./api/channel/get";
|
||||||
import ChannelList from "./api/channel/list";
|
import ChannelList from "./api/channel/list";
|
||||||
|
import CommunityCreate from "./api/community/create";
|
||||||
|
import CommunityEdit from "./api/community/edit";
|
||||||
|
import CommunityGet from "./api/community/get";
|
||||||
|
import CommunityList from "./api/community/list";
|
||||||
import Me from "./api/me";
|
import Me from "./api/me";
|
||||||
import PrimarySignin from "./api/primary/signin";
|
import PrimarySignin from "./api/primary/signin";
|
||||||
import PrimarySignup from "./api/primary/signup";
|
import PrimarySignup from "./api/primary/signup";
|
||||||
@@ -15,8 +20,13 @@ type ApiMap =
|
|||||||
PrimarySignin &
|
PrimarySignin &
|
||||||
PrimarySignup &
|
PrimarySignup &
|
||||||
Me &
|
Me &
|
||||||
|
CommunityCreate &
|
||||||
|
CommunityEdit &
|
||||||
|
CommunityGet &
|
||||||
|
CommunityList &
|
||||||
ChannelCreate &
|
ChannelCreate &
|
||||||
ChannelEdit &
|
ChannelEdit &
|
||||||
|
ChannelGet &
|
||||||
ChannelList;
|
ChannelList;
|
||||||
|
|
||||||
export default ApiMap;
|
export default ApiMap;
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
export default interface Channel {
|
export default interface Channel {
|
||||||
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
userid: string;
|
community: string;
|
||||||
|
createdBy: string;
|
||||||
|
createdAt: Date;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export default interface Community {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
icon: string | null;
|
||||||
|
createdBy: string;
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export type RequireAtLeastOne<T, Keys extends keyof T = keyof T> =
|
||||||
|
Keys extends keyof T
|
||||||
|
? Required<Pick<T, Keys>> & Partial<Omit<T, Keys>>
|
||||||
|
: never;
|
||||||
@@ -26,8 +26,10 @@ export default class LynqChat<
|
|||||||
this.retry = options.retry ?? 5;
|
this.retry = options.retry ?? 5;
|
||||||
this.waiting = options.waiting ?? 500;
|
this.waiting = options.waiting ?? 500;
|
||||||
|
|
||||||
if (this.retry < 1) throw new lynqError("Invalid retry count.");
|
if (this.retry < 1)
|
||||||
if (this.waiting < 1) throw new lynqError("Invalid base waiting time.");
|
throw new lynqError("Invalid retry count.");
|
||||||
|
if (this.waiting < 1)
|
||||||
|
throw new lynqError("Invalid base waiting time.");
|
||||||
if (options.origin !== new URL(options.origin).origin)
|
if (options.origin !== new URL(options.origin).origin)
|
||||||
throw new lynqError("Invalid origin.");
|
throw new lynqError("Invalid origin.");
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+2966
-12
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user