From b22734831efccca4c6ad6a2cbed04cb93f59e15f Mon Sep 17 00:00:00 2001 From: Kevin Puig <119972216+k-puig@users.noreply.github.com> Date: Sat, 27 Sep 2025 11:13:37 -0400 Subject: [PATCH] Created some user endpoints ; Pretty code --- concord-server/.editorconfig | 10 ++ concord-server/README.md | 2 + concord-server/src/controller/actions.ts | 6 - .../src/controller/userController.ts | 9 ++ concord-server/src/index.ts | 73 +++++---- concord-server/src/routes/actions.ts | 36 ----- concord-server/src/routes/index.ts | 10 +- concord-server/src/routes/userRoutes.ts | 25 +++ concord-server/src/services/actions.ts | 153 ------------------ concord-server/src/services/userService.ts | 151 +++++++++++++++++ concord-server/src/validators/actions.ts | 3 - concord-server/tsconfig.json | 2 +- 12 files changed, 240 insertions(+), 240 deletions(-) create mode 100644 concord-server/.editorconfig delete mode 100644 concord-server/src/controller/actions.ts create mode 100644 concord-server/src/controller/userController.ts delete mode 100644 concord-server/src/routes/actions.ts create mode 100644 concord-server/src/routes/userRoutes.ts delete mode 100644 concord-server/src/services/actions.ts create mode 100644 concord-server/src/services/userService.ts delete mode 100644 concord-server/src/validators/actions.ts diff --git a/concord-server/.editorconfig b/concord-server/.editorconfig new file mode 100644 index 0000000..1e09672 --- /dev/null +++ b/concord-server/.editorconfig @@ -0,0 +1,10 @@ +# EditorConfig helps maintain consistent coding styles +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/concord-server/README.md b/concord-server/README.md index 6dd13e7..d950ba6 100644 --- a/concord-server/README.md +++ b/concord-server/README.md @@ -1,9 +1,11 @@ To install dependencies: + ```sh bun install ``` To run: + ```sh bun run dev ``` diff --git a/concord-server/src/controller/actions.ts b/concord-server/src/controller/actions.ts deleted file mode 100644 index b1dc7fc..0000000 --- a/concord-server/src/controller/actions.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { getUserInformation } from "../services/actions"; - - -export async function fetchUserData(id: string) { - return await getUserInformation(id); -} \ No newline at end of file diff --git a/concord-server/src/controller/userController.ts b/concord-server/src/controller/userController.ts new file mode 100644 index 0000000..f6fdd63 --- /dev/null +++ b/concord-server/src/controller/userController.ts @@ -0,0 +1,9 @@ +import { getAllUsersFrom, getUserInformation } from "../services/userService"; + +export async function fetchUserData(id: string) { + return await getUserInformation(id); +} + +export async function fetchAllUsers(instanceId: string) { + return await getAllUsersFrom(instanceId); +} diff --git a/concord-server/src/index.ts b/concord-server/src/index.ts index 16f7ec9..c26549d 100644 --- a/concord-server/src/index.ts +++ b/concord-server/src/index.ts @@ -1,50 +1,53 @@ -import { Hono } from 'hono' -import { cors } from 'hono/cors' -import { Server as Engine } from '@socket.io/bun-engine' -import { Server } from 'socket.io' -import routes from './routes/index' +import { Hono } from "hono"; +import { cors } from "hono/cors"; +import { Server as Engine } from "@socket.io/bun-engine"; +import { Server } from "socket.io"; +import routes from "./routes/index"; //initialize socket.io server -const io = new Server() +const io = new Server(); //initialize bun engine //then bind to socket.io server -const engine = new Engine() -io.bind(engine) +const engine = new Engine(); +io.bind(engine); -io.on('connection', (socket) => { +io.on("connection", (socket) => { + //get userId and clientId from query params + const userId = socket.handshake.query.userId; + const clientId = socket.handshake.query.clientId; + if (!userId || Array.isArray(userId)) { + socket.disconnect(); + throw new Error("Invalid user ID"); + } - //get userId and clientId from query params - const userId = socket.handshake.query.userId; - const clientId = socket.handshake.query.clientId; - if(!userId || Array.isArray(userId)){ - socket.disconnect(); - throw new Error('Invalid user ID'); - } + if (!clientId || Array.isArray(clientId)) { + socket.disconnect(); + throw new Error("Invalid client ID"); + } - if(!clientId || Array.isArray(clientId)){ - socket.disconnect() - throw new Error('Invalid client ID') - } + socket.join(userId); + console.log( + `User ${userId} connected. Client ID ${clientId} on socket ${socket.id}`, + ); - socket.join(userId) - console.log(`User ${userId} connected. Client ID ${clientId} on socket ${socket.id}`); - - socket.on('disconnect', () => { - console.log(`User ${userId} disconnected from socket ${socket.id}`); - }); + socket.on("disconnect", () => { + console.log(`User ${userId} disconnected from socket ${socket.id}`); + }); }); -const app = new Hono() +const app = new Hono(); -app.use('*', cors({ - origin: 'http://localhost:5173', - allowHeaders: ['Content-Type', 'Authorization'], - allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], - credentials: true -})) - -app.route('/api', routes) +app.use( + "*", + cors({ + origin: "http://localhost:5173", + allowHeaders: ["Content-Type", "Authorization"], + allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], + credentials: true, + }), +); +app.route("/api", routes); export default app; diff --git a/concord-server/src/routes/actions.ts b/concord-server/src/routes/actions.ts deleted file mode 100644 index d2ca9de..0000000 --- a/concord-server/src/routes/actions.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Hono } from "hono" -import { fetchUserData } from "../controller/actions" -const actions = new Hono() - -/* -actions.post("actions/message/:id", (c) => { - //todo: pass service function to send a message to user id -}) - -actions.delete("actions/message/delete/:id", (c) => { - //todo: pass service function to delete a message by user id -}) - -actions.get("actions/message/all/:userId", (c) => { - //todo: pass service function to fetch all messages f a user -}) - - - -actions.get("actions/userChannels/:id", (c) => { - //todo: pass service function to fetch all channels the id is part of -}) -*/ - -actions.get("actions/:id", async (c) => { - const id = c.req.param("id"); - const userData = await fetchUserData(id); - if (userData) { - return c.json(userData); - } else { - return c.json({ error: "User not found" }, 404); - } -}) - - -export default actions \ No newline at end of file diff --git a/concord-server/src/routes/index.ts b/concord-server/src/routes/index.ts index 4ba151a..550c3b3 100644 --- a/concord-server/src/routes/index.ts +++ b/concord-server/src/routes/index.ts @@ -1,11 +1,9 @@ //place exported routes below this line -import { Hono } from 'hono'; -import actions from './actions'; - +import { Hono } from "hono"; +import actions from "./userRoutes"; const routes = new Hono(); -routes.route("/", actions) +routes.route("/", actions); - -export default routes; \ No newline at end of file +export default routes; diff --git a/concord-server/src/routes/userRoutes.ts b/concord-server/src/routes/userRoutes.ts new file mode 100644 index 0000000..c7a46fa --- /dev/null +++ b/concord-server/src/routes/userRoutes.ts @@ -0,0 +1,25 @@ +import { Hono } from "hono"; +import { fetchAllUsers, fetchUserData } from "../controller/userController"; +const actions = new Hono(); + +actions.get("user/:id", async (c) => { + const id = c.req.param("id"); + const userData = await fetchUserData(id); + if (userData) { + return c.json(userData); + } else { + return c.json({ error: "User not found" }, 404); + } +}); + +actions.get("instance/:id/users", async (c) => { + const instanceId = c.req.param("id"); + const userData = await fetchAllUsers(instanceId); + if (userData) { + return c.json(userData); + } else { + return c.json({ error: "Error getting all users from instance" }, 500); + } +}); + +export default actions; diff --git a/concord-server/src/services/actions.ts b/concord-server/src/services/actions.ts deleted file mode 100644 index 1d88458..0000000 --- a/concord-server/src/services/actions.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { Message, MessagePing, PrismaClient, Role, UserAuth } from "@prisma/client"; - -const prisma = new PrismaClient(); - -export async function getUserChannels(userId: string): Promise { - try{ - if(!userId){ - throw new Error('missing userId'); - } - - //TODO: add some prisma code here - //to fetch all the channels a user is part of - - - return ["test1","test2"]; - }catch(err){ - const errMessage = err as Error - - if(errMessage.message === 'missing userId'){ - console.log('services::actions::getUserChannels - missing userId'); - return null; - } - - console.log('services::actions::getUserChannels - unknown error', errMessage); - return null; - } -} - -export async function getUserInformation(userId: string): - Promise<{ - id: string, - userName: string, - nickName: string | null, - bio: string | null, - picture: string | null, - banner: string | null, - admin: boolean, - status: 'online' | 'offline' | 'dnd' | 'idle' | 'invis', - role: Role[], - userAuth?: UserAuth, -} | null> { - try{ - if(!userId){ - throw new Error('missing userId'); - } - - const user = await prisma.user.findUnique({ - where: { - id: userId - } - }) - - if (!user) { - throw new Error('could not find user'); - } - - const userRoles = await prisma.role.findMany({ - where: { - userId: userId - } - }) - - return { - id: userId, - userName: user.username, - nickName: user.nickname, - bio: user.bio, - picture: user.picture, - banner: user.banner, - admin: user.admin, - status: (["online", "offline", "dnd", "idle", "invis"] as const).includes(user.status as any) ? user.status as 'online' | 'offline' | 'dnd' | 'idle' | 'invis' : 'offline', - role: userRoles, - }; - - }catch(err){ - const errMessage = err as Error - - if(errMessage.message === 'missing userId'){ - console.log('services::actions::getUserInformation - missing userId'); - return null; - } - - if(errMessage.message === 'could not find user'){ - console.log('services::actions::getUserInformation - unable to find user'); - return null; - } - - console.log('services::actions::getUserInformation - unknown error', errMessage); - return null; - } -} - -export async function getAllMessage(userId: string): Promise<{id: string, content: string, senderId: string, receiverId: string}[] | null> { - try{ - if(!userId){ - throw new Error('missing userId'); - } - - //TODO: add some prisma code here - //to fetch all messages for a user - - return [ - {id: "1", content: "Hello", senderId: userId, receiverId: "2"}, - {id: "2", content: "Hi", senderId: "2", receiverId: userId} - ]; - }catch(err){ - const errMessage = err as Error - - if(errMessage.message === 'missing userId'){ - console.log('services::actions::getAllMessage - missing userId'); - return null; - } - - console.log('services::actions::getAllMessage - unknown error', errMessage); - return null; - } -} - - -export async function sendMessage(userId: string, content: string): Promise { - try{ - if(!userId){ - throw new Error('missing userId'); - } - - if(!content){ - throw new Error('missing content'); - } - - //TODO: add some prisma code here - //to save a message - - //todo: add some socketio code to - //emit the message to the receiver - - return true; - }catch(err){ - const errMessage = err as Error - - if(errMessage.message === 'missing userId'){ - console.log('services::actions::sendMessage - missing userId'); - return false; - } - - if(errMessage.message === 'missing content'){ - console.log('services::actions::sendMessage - missing content'); - return false; - } - - console.log('services::actions::sendMessage - unknown error', errMessage); - return false; - } -} diff --git a/concord-server/src/services/userService.ts b/concord-server/src/services/userService.ts new file mode 100644 index 0000000..82722b2 --- /dev/null +++ b/concord-server/src/services/userService.ts @@ -0,0 +1,151 @@ +import { + Message, + MessagePing, + PrismaClient, + Role, + UserAuth, +} from "@prisma/client"; + +const prisma = new PrismaClient(); + +export async function getUserInformation(userId: string): Promise<{ + id: string; + userName: string; + nickName: string | null; + bio: string | null; + picture: string | null; + banner: string | null; + admin: boolean; + status: "online" | "offline" | "dnd" | "idle" | "invis"; + role: Role[]; +} | null> { + try { + if (!userId) { + throw new Error("missing userId"); + } + + const user = await prisma.user.findUnique({ + where: { + id: userId, + }, + }); + + if (!user) { + throw new Error("could not find user"); + } + + const userRoles = await prisma.role.findMany({ + where: { + userId: userId, + }, + }); + + return { + id: userId, + userName: user.username, + nickName: user.nickname, + bio: user.bio, + picture: user.picture, + banner: user.banner, + admin: user.admin, + status: (["online", "offline", "dnd", "idle", "invis"] as const).includes( + user.status as any, + ) + ? (user.status as "online" | "offline" | "dnd" | "idle" | "invis") + : "offline", + role: userRoles, + }; + } catch (err) { + const errMessage = err as Error; + + if (errMessage.message === "missing userId") { + console.log("services::actions::getUserInformation - missing userId"); + return null; + } + + if (errMessage.message === "could not find user") { + console.log( + "services::actions::getUserInformation - unable to find user", + ); + return null; + } + + console.log( + "services::actions::getUserInformation - unknown error", + errMessage, + ); + return null; + } +} + +export async function getAllUsersFrom(instanceId: string): Promise< + | { + id: string; + userName: string; + nickName: string | null; + bio: string | null; + picture: string | null; + banner: string | null; + admin: boolean; + status: "online" | "offline" | "dnd" | "idle" | "invis"; + role: { + userId: string; + instanceId: string; + }[]; + }[] + | null +> { + try { + const instances = await prisma.instance.findMany(); + if (!instances) { + throw new Error("could not get all instances"); + } + + const users = await prisma.user.findMany({ + where: { + Role: { + some: { + instanceId: instanceId, + }, + }, + }, + }); + if (!users) { + throw new Error("could not get all users in instance"); + } + + const userData = await Promise.all( + users.map(async (u) => { + const userRoles = await prisma.role.findMany({ + where: { + userId: u.id, + }, + }); + if (!userRoles) { + throw new Error("could not get user roles for user " + u.id); + } + + return { + id: u.id, + userName: u.username, + nickName: u.nickname, + bio: u.bio, + picture: u.picture, + banner: u.banner, + admin: u.admin, + status: ( + ["online", "offline", "dnd", "idle", "invis"] as const + ).includes(u.status as any) + ? (u.status as "online" | "offline" | "dnd" | "idle" | "invis") + : "offline", + role: userRoles, + }; + }), + ); + + return userData; + } catch (err) { + console.log(err); + return null; + } +} diff --git a/concord-server/src/validators/actions.ts b/concord-server/src/validators/actions.ts deleted file mode 100644 index 55d73ea..0000000 --- a/concord-server/src/validators/actions.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { zValidator } from "@hono/zod-validator" - -export const \ No newline at end of file diff --git a/concord-server/tsconfig.json b/concord-server/tsconfig.json index c442b33..9136c14 100644 --- a/concord-server/tsconfig.json +++ b/concord-server/tsconfig.json @@ -4,4 +4,4 @@ "jsx": "react-jsx", "jsxImportSource": "hono/jsx" } -} \ No newline at end of file +}