diff --git a/concord-server/src/controller/instanceController.ts b/concord-server/src/controller/instanceController.ts new file mode 100644 index 0000000..97578fd --- /dev/null +++ b/concord-server/src/controller/instanceController.ts @@ -0,0 +1,10 @@ +import { createInstance, getAllInstances } from "../services/instanceService"; +import { CreateInstanceRequest } from "../validators/instanceValidator"; + +export async function createInstanceReq(data:CreateInstanceRequest) { + return await createInstance(data); +} + +export async function getAllInstancesReq() { + return await getAllInstances(); +} \ No newline at end of file diff --git a/concord-server/src/routes/index.ts b/concord-server/src/routes/index.ts index a33ac3d..97440a1 100644 --- a/concord-server/src/routes/index.ts +++ b/concord-server/src/routes/index.ts @@ -3,11 +3,13 @@ import { Hono } from "hono"; import userRoutes from "./userRoutes"; import messageRoutes from "./messageRoutes"; import channelRoutes from "./channelRoutes"; +import instanceRoutes from "./instanceRoutes"; const routes = new Hono(); routes.route("/user", userRoutes); routes.route("/message", messageRoutes); routes.route("/channel", channelRoutes); +routes.route("/instance", instanceRoutes); export default routes; diff --git a/concord-server/src/routes/instanceRoutes.ts b/concord-server/src/routes/instanceRoutes.ts new file mode 100644 index 0000000..5fea872 --- /dev/null +++ b/concord-server/src/routes/instanceRoutes.ts @@ -0,0 +1,63 @@ +import { Hono } from "hono"; +import { describeRoute, resolver } from "hono-openapi"; +import { createInstanceRequestSchema, getAllInstancesResponseSchema } from "../validators/instanceValidator"; +import { zValidator } from "@hono/zod-validator"; +import { createInstanceReq, getAllInstancesReq } from "../controller/instanceController"; + +const instanceRoutes = new Hono(); + +instanceRoutes.post( + "", + describeRoute({ + description: "Create instance", + responses: { + 200: { + description: "Instance created", + content: { + "application/json": { schema: resolver(createInstanceRequestSchema) } + }, + }, + 400: { + description: "Invalid request", + }, + } + }), + zValidator('json', createInstanceRequestSchema), + async (c) => { + const data = await c.req.json(); + if (!data) { + return c.json({ error: "could not parse data" }, 400); + } + + const instance = await createInstanceReq(data); + return c.json(instance, 201); + } +); + +instanceRoutes.get( + "", + describeRoute({ + description: "Get all instances", + responses: { + 200: { + description: "List of all instances", + content: { + "application/json": { schema: resolver(getAllInstancesResponseSchema) } + }, + }, + 500: { + description: "Server error", + }, + } + }), + async (c) => { + const instances = await getAllInstancesReq(); + if (instances.success) { + return c.json(instances, 200); + } else { + return c.json({ success: false, error: instances.error || "Failed to fetch instances" }, 500); + } + } +); + +export default instanceRoutes; diff --git a/concord-server/src/services/instanceService.ts b/concord-server/src/services/instanceService.ts new file mode 100644 index 0000000..d37f3a0 --- /dev/null +++ b/concord-server/src/services/instanceService.ts @@ -0,0 +1,57 @@ +import { PrismaClient } from "@prisma/client"; +import { CreateInstanceRequest } from "../validators/instanceValidator"; +import { getUserCredentials, getUserInformation } from "./userService"; + +const prisma = new PrismaClient(); + +export async function createInstance(data: CreateInstanceRequest) { + try { + const creds = await getUserCredentials(data.requestingUserId); + const user = await getUserInformation(data.requestingUserId); + if (!creds + || creds.token != data.requestingUserToken + || !user + || !user.admin) { + return null; + } + + const newInstance = await prisma.instance.create({ + data: { + name: data.name, + icon: data.icon + } + }); + + return { + success: true, + data: newInstance + }; + } catch (error) { + console.error("Error creating instance:", error); + return { + success: false, + error: "Failed to create instance" + }; + } +} + +export async function getAllInstances() { + try { + const instances = await prisma.instance.findMany({ + orderBy: { + createdAt: 'desc' + } + }); + + return { + success: true, + data: instances + }; + } catch (error) { + console.error("Error fetching instances:", error); + return { + success: false, + error: "Failed to fetch instances" + }; + } +} \ No newline at end of file diff --git a/concord-server/src/services/userService.ts b/concord-server/src/services/userService.ts index e960534..6598b19 100644 --- a/concord-server/src/services/userService.ts +++ b/concord-server/src/services/userService.ts @@ -256,10 +256,7 @@ export async function getAllUsersFrom(instanceId: string): Promise< ).includes(u.status as any) ? (u.status as "online" | "offline" | "dnd" | "idle" | "invis") : "offline", - role: adminRoles.map((r) => ({ - userId: r.userId, - instanceId: r.instanceId, - })), + role: adminRoles, }; }), ); diff --git a/concord-server/src/validators/instanceValidator.ts b/concord-server/src/validators/instanceValidator.ts new file mode 100644 index 0000000..a4497fe --- /dev/null +++ b/concord-server/src/validators/instanceValidator.ts @@ -0,0 +1,29 @@ +import { z } from 'zod'; + +export const createInstanceRequestSchema = z.object({ + name: z.string().min(1, 'Instance name cannot be empty'), + icon: z.url().optional(), + requestingUserId: z.uuidv7(), + requestingUserToken: z.string() +}); + +export const getAllInstancesResponseSchema = z.object({ + success: z.boolean(), + data: z.array( + z.object({ + id: z.string(), + name: z.string(), + icon: z.string().nullable(), + createdAt: z.string().refine((val) => !isNaN(Date.parse(val)), { + message: "Invalid date string format" + }), + updatedAt: z.string().refine((val) => !isNaN(Date.parse(val)), { + message: "Invalid date string format" + }) + }) + ).optional(), + error: z.string().optional() +}); + +export type CreateInstanceRequest = z.infer; +export type GetAllInstancesResponse = z.infer; \ No newline at end of file