made pretty and fixed message posting

This commit is contained in:
Kevin Puig
2025-09-27 15:55:20 -04:00
parent 74f4e076ce
commit da1310b9c8
10 changed files with 415 additions and 390 deletions

View File

@@ -1,12 +1,11 @@
import { Context } from "hono";
import { sendMessageToChannel, removeMessageFromChannel } from "../services/realtime.js"
import {
sendMessageToChannel,
removeMessageFromChannel,
} from "../services/realtime.js";
import { success } from "zod";
export async function postMessageToChannel(
io: any,
c: Context
) {
export async function postMessageToChannel(io: any, c: Context) {
try {
io = c.get("io");
@@ -21,55 +20,53 @@ export async function postMessageToChannel(
channelId,
message,
"new_channel_message",
io
)
io,
);
if(result === "Event not implemented"){
console.log("controller::realtime::postMessageToChannel - Failed to send message")
if (result === "Event not implemented") {
console.log(
"controller::realtime::postMessageToChannel - Failed to send message",
);
return c.json({
success: false,
message: "Event not implemented or recognized",
status: 400
})
status: 400,
});
}
if(result === "no acknowledgment"){
console.log("controller::realtime::postMessageToChannel - No acknowledgment received from client")
if (result === "no acknowledgment") {
console.log(
"controller::realtime::postMessageToChannel - No acknowledgment received from client",
);
return c.json({
success: false,
message: "No acknowledgment received from client",
status: 500
})
status: 500,
});
}
if(!result){
if (!result) {
throw new Error("failed to send message");
}
return c.json({
success: true,
message: "Message sent successfully",
status: 200
})
status: 200,
});
} catch (err) {
const errMessage = err as Error;
console.log("controller::realtime::postMessageToChannel - ", errMessage);
return c.json({
success: false,
message: errMessage.message,
status: 500
status: 500,
});
}
}
export async function deleteMessageFromChannel(
io: any,
c: Context
){
export async function deleteMessageFromChannel(io: any, c: Context) {
try {
io = c.get("io");
const instanceId = c.req.param("instanceId");
@@ -83,45 +80,47 @@ export async function deleteMessageFromChannel(
channelId,
messageId,
"delete_channel_message",
io
)
io,
);
if(result === "event not implemented"){
console.log("controller::realtime::deleteMessageFromChannel - Event not implemented")
if (result === "event not implemented") {
console.log(
"controller::realtime::deleteMessageFromChannel - Event not implemented",
);
return c.json({
success: false,
message: "Event not implemented or recognized",
status: 400
status: 400,
});
}
if(result === "no acknowledgment"){
console.log("controller::realtime::deleteMessageFromChannel - No acknowledgment received from client")
if (result === "no acknowledgment") {
console.log(
"controller::realtime::deleteMessageFromChannel - No acknowledgment received from client",
);
return c.json({
success: false,
message: "No acknowledgment received from client",
status: 500
status: 500,
});
}
if(!result){
if (!result) {
throw new Error("failed to delete message");
}
c.json({
success: true,
message: "Message deleted successfully",
status: 200
})
status: 200,
});
} catch (err) {
const errMessage = err as Error;
console.log("services::realtime::deleteMessageFromChannel - ", errMessage);
return c.json({
success: false,
message: errMessage.message,
status: 500
status: 500,
});
}
}

View File

@@ -1,4 +1,8 @@
import { getAllUsersFrom, getUserInformation, createUser } from "../services/userService";
import {
getAllUsersFrom,
getUserInformation,
createUser,
} from "../services/userService";
import { CreateUserInput } from "../validators/userValidator";
export async function fetchUserData(id: string) {

View File

@@ -1,5 +1,5 @@
import * as crypto from 'crypto';
import * as crypto from "crypto";
export default function shaHash(data:string, salt:string) : string {
return crypto.createHmac('sha256', salt).update(data).digest('hex');
export default function shaHash(data: string, salt: string): string {
return crypto.createHmac("sha256", salt).update(data).digest("hex");
}

View File

@@ -53,21 +53,19 @@ app.use(
app.route("/api", routes);
app.get(
'/openapi',
"/openapi",
openAPIRouteHandler(app, {
documentation: {
info: {
title: 'Hono API',
version: '1.0.0',
description: 'Greeting API',
title: "Hono API",
version: "1.0.0",
description: "Greeting API",
},
servers: [
{ url: 'http://localhost:3000', description: 'Local Server' },
],
servers: [{ url: "http://localhost:3000", description: "Local Server" }],
},
})
)
}),
);
app.get('/scalar', Scalar({ url: '/openapi' }))
app.get("/scalar", Scalar({ url: "/openapi" }));
export default app;

View File

@@ -1,8 +1,9 @@
import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator";
import { describeRoute, resolver } from "hono-openapi";
import { postMessageToChannel,
deleteMessageFromChannel
import {
postMessageToChannel,
deleteMessageFromChannel,
} from "../controller/realtime";
const app = new Hono();
@@ -11,8 +12,8 @@ app.post(
"message/",
zValidator({
body: z.object({
content: z.string().min(1).max(500)
})
content: z.string().min(1).max(500),
}),
}),
async (c) => {
const { instanceId, categoryId, channelId } = c.req.params;
@@ -22,7 +23,7 @@ app.post(
instanceId,
categoryId,
channelId,
content
content,
});
}
},
);

View File

@@ -1,29 +1,38 @@
import { Hono } from "hono";
import { fetchAllUsers, fetchUserData, createNewUser } from "../controller/userController";
import { createUserSchema, queryAllUsersByInstanceId, queryUserByIdSchema } from "../validators/userValidator";
import {
fetchAllUsers,
fetchUserData,
createNewUser,
} from "../controller/userController";
import {
createUserSchema,
queryAllUsersByInstanceId,
queryUserByIdSchema,
} from "../validators/userValidator";
import { zValidator } from "@hono/zod-validator";
import { describeRoute, resolver } from "hono-openapi";
const actions = new Hono();
actions.get("user/:id",
actions.get(
"user/:id",
describeRoute({
description: "Get user by id",
responses: {
200: {
description: "Success getting user",
content: {
"application/json": { schema: resolver(queryUserByIdSchema) }
}
"application/json": { schema: resolver(queryUserByIdSchema) },
},
},
404: {
description: "User id not found",
content: {
"application/json": { schema: resolver(queryUserByIdSchema) }
}
}
}
"application/json": { schema: resolver(queryUserByIdSchema) },
},
},
},
}),
zValidator('param', queryUserByIdSchema),
zValidator("param", queryUserByIdSchema),
async (c) => {
const id = c.req.param("id");
const userData = await fetchUserData(id);
@@ -32,22 +41,23 @@ actions.get("user/:id",
} else {
return c.json({ error: "User not found" }, 404);
}
}
},
);
actions.get("user",
actions.get(
"user",
describeRoute({
description: "Get all users by instance id",
responses: {
200: {
description: "Success getting all users in instance",
content: {
"application/json": { schema: resolver(queryAllUsersByInstanceId) }
}
}
}
"application/json": { schema: resolver(queryAllUsersByInstanceId) },
},
},
},
}),
zValidator('query', queryAllUsersByInstanceId),
zValidator("query", queryAllUsersByInstanceId),
async (c) => {
const instanceId = c.req.query("instanceId");
if (!instanceId) {
@@ -60,7 +70,7 @@ actions.get("user",
} else {
return c.json({ error: "Error getting all users from instance" }, 500);
}
}
},
);
actions.post(
@@ -71,18 +81,18 @@ actions.post(
201: {
description: "Success",
content: {
'application/json': { schema: resolver(createUserSchema) },
"application/json": { schema: resolver(createUserSchema) },
},
},
400: {
description: "Bad request (user exists)",
content: {
'application/json': { schema: resolver(createUserSchema) }
}
}
}
"application/json": { schema: resolver(createUserSchema) },
},
},
},
}),
zValidator('json', createUserSchema),
zValidator("json", createUserSchema),
async (c) => {
try {
const data = await c.req.json();
@@ -94,7 +104,7 @@ actions.post(
} catch (error) {
return c.json({ error: "Error creating user" }, 500);
}
}
},
);
export default actions;

View File

@@ -3,75 +3,76 @@ import {
MessagePing,
PrismaClient,
Role,
Reply
Reply,
} from "@prisma/client";
import { CreateUserInput } from '../validators/userValidator';
import { CreateUserInput } from "../validators/userValidator";
const prisma = new PrismaClient();
class MessageService {
public async function sendMessageToChannel(
export async function sendMessageToChannel(
channelId: string,
userId: string,
content: string,
repliedMessageId: string | null
): Promise<{
id: string,
channelId: string,
userId: string,
text: string,
deleted: boolean,
repliedMessageId: string | null,
): Promise<{
id: string;
channelId: string;
userId: string;
text: string;
deleted: boolean;
replies: null | {
messageId: string,
repliesToId: string,
repliesToText: string
}
} | null> {
messageId: string;
repliesToId: string;
repliesToText: string;
};
} | null> {
try {
const newMessage = await prisma.message.create({
data: {
channelId: channelId,
userId: userId,
text: content,
deleted: false,
},
});
if (!newMessage) {
return null;
}
})
let origMessage;
if(repliedMessageId){
if (repliedMessageId) {
origMessage = await prisma.message.findUnique({
where: {
id: repliedMessageId
}
})
id: repliedMessageId,
},
});
if(!origMessage){
if (!origMessage) {
throw new Error("could not find original message to reply to");
}
await prisma.reply.create({
data: {
messageId: newMessage.id,
repliesToId: origMessage.id
}
})
repliesToId: origMessage.id,
},
});
}
return {
...newMessage,
replies: repliedMessageId ? {
channelId: newMessage.channelId!,
userId: newMessage.userId!,
replies: origMessage
? {
messageId: newMessage.id,
repliesToId: origMessage?.id,
repliesToText: origMessage?.text
} : null
repliesToText: origMessage?.text,
}
: null,
};
} catch (error) {
return null;
}
}

View File

@@ -3,9 +3,7 @@ import { readonly } from "zod";
const EVENTS = {
NEW_CHANNEL_MESSAGE: "new_channel_message",
DELETE_CHANNEL_MESSAGE: "delete_channel_message",
}
};
export async function sendMessageToChannel(
instanceId: string,
@@ -16,9 +14,8 @@ export async function sendMessageToChannel(
io: any,
): Promise<string | boolean> {
try {
//TODO: implement middleware to replace this
if(EVENTS.NEW_CHANNEL_MESSAGE === event){
if (EVENTS.NEW_CHANNEL_MESSAGE === event) {
throw new Error("Event not implemented");
}
@@ -26,11 +23,13 @@ export async function sendMessageToChannel(
return new Promise((resolve) => {
io.to(instanceId).emit(event, message, (ack: any) => {
if (ack && ack.status === 'received') {
if (ack && ack.status === "received") {
console.log(`Message ${ack.messageId} acknowledged by client.`);
resolve(true);
} else {
console.log('services::realtime::sendMessageToChannel No acknowledgment received from client.');
console.log(
"services::realtime::sendMessageToChannel No acknowledgment received from client.",
);
resolve("no acknowledgment");
}
});
@@ -38,12 +37,13 @@ export async function sendMessageToChannel(
} catch (err) {
const errMessage = err as Error;
if (errMessage.message === "Event not implemented") {
console.log(`services::realtime::sendMessageToChannel - Event not implemented. Attempted event: ${event}`)
return "event not implemented"
console.log(
`services::realtime::sendMessageToChannel - Event not implemented. Attempted event: ${event}`,
);
return "event not implemented";
}
console.log("services::realtime::sendMessageToChannel - ", errMessage);
return false;
}
}
@@ -53,12 +53,11 @@ export async function removeMessageFromChannel(
channelId: string,
messageId: string,
event: string,
io: any
): Promise<string | boolean>{
io: any,
): Promise<string | boolean> {
try {
//TODO: implement middleware to replace this
if(EVENTS.DELETE_CHANNEL_MESSAGE === event){
if (EVENTS.DELETE_CHANNEL_MESSAGE === event) {
throw new Error("event not implemented");
}
@@ -66,11 +65,13 @@ export async function removeMessageFromChannel(
return new Promise((resolve) => {
io.to(instanceId).emit(event, { messageId }, (ack: any) => {
if (ack && ack.status === 'received') {
if (ack && ack.status === "received") {
console.log(`Message ${ack.messageId} acknowledged by client.`);
resolve(true);
} else {
console.log('services::realtime::deleteMessageFromChannel No acknowledgment received from client.');
console.log(
"services::realtime::deleteMessageFromChannel No acknowledgment received from client.",
);
resolve("no acknowledgment");
}
});
@@ -78,11 +79,12 @@ export async function removeMessageFromChannel(
} catch (err) {
const errMessage = err as Error;
if (errMessage.message === "Event not implemented") {
console.log(`services::realtime::deleteMessageFromChannel - Event not implemented. Attempted event: ${event}`)
console.log(
`services::realtime::deleteMessageFromChannel - Event not implemented. Attempted event: ${event}`,
);
return false;
}
console.log("services::realtime::deleteMessageFromChannel - ", errMessage);
return false;
}
}

View File

@@ -5,31 +5,35 @@ import {
Role,
UserAuth,
} from "@prisma/client";
import { CreateUserInput } from '../validators/userValidator';
import { CreateUserInput } from "../validators/userValidator";
import shaHash from "../helper/hashing";
const prisma = new PrismaClient();
export async function createUser(data: CreateUserInput): Promise<{
username: string,
nickname: string | null,
bio: string | null,
picture: string | null,
banner: string | null,
status: string,
admin: boolean
username: string;
nickname: string | null;
bio: string | null;
picture: string | null;
banner: string | null;
status: string;
admin: boolean;
} | null> {
const requestingUser = await getUserInformation(data.requestingUserId);
const requestingUserCredentials = await getUserCredentials(data.requestingUserId)
if (!requestingUser
|| !requestingUserCredentials
|| !requestingUser.admin
|| requestingUserCredentials.token == null
|| data.requestingUserToken != requestingUserCredentials.token) {
const requestingUserCredentials = await getUserCredentials(
data.requestingUserId,
);
if (
!requestingUser ||
!requestingUserCredentials ||
!requestingUser.admin ||
requestingUserCredentials.token == null ||
data.requestingUserToken != requestingUserCredentials.token
) {
return null;
}
if (await prisma.user.count({ where: { username: data.username }}) >= 1) {
if ((await prisma.user.count({ where: { username: data.username } })) >= 1) {
return null;
}
@@ -45,13 +49,15 @@ export async function createUser(data: CreateUserInput): Promise<{
},
});
if (!(await prisma.userAuth.create({
if (
!(await prisma.userAuth.create({
data: {
userId: userData.id,
password: shaHash(data.passwordhash, userData.id),
token: null,
}
}))) {
},
}))
) {
return null;
}
@@ -59,10 +65,10 @@ export async function createUser(data: CreateUserInput): Promise<{
}
export async function getUserCredentials(userId: string): Promise<{
userId: string,
password: string,
token: string | null
} | null> {
userId: string;
password: string;
token: string | null;
} | null> {
try {
if (!userId) {
throw new Error("missing userId");
@@ -104,7 +110,7 @@ export async function getUserCredentials(userId: string): Promise<{
);
return null;
}
}
}
export async function getUserInformation(userId: string): Promise<{
id: string;
@@ -196,9 +202,9 @@ export async function getAllUsersFrom(instanceId: string): Promise<
try {
const instances = await prisma.instance.count({
where: {
id: instanceId
}
})
id: instanceId,
},
});
if (instances < 1) {
throw new Error("could not find given instance id");
}
@@ -218,8 +224,8 @@ export async function getAllUsersFrom(instanceId: string): Promise<
const admins = await prisma.user.findMany({
where: {
admin: true
}
admin: true,
},
});
if (!admins) {
throw new Error("could not get all admins");
@@ -249,13 +255,13 @@ 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 => ({
role: adminRoles.map((r) => ({
userId: r.userId,
instanceId: r.instanceId,
})),
}
})
)
};
}),
);
const userData = await Promise.all(
users.map(async (u) => {

View File

@@ -1,12 +1,12 @@
import { z } from 'zod'
import { z } from "zod";
export const queryUserByIdSchema = z.object({
id: z.uuidv7()
})
id: z.uuidv7(),
});
export const queryAllUsersByInstanceId = z.object({
instanceId: z.uuidv7()
})
instanceId: z.uuidv7(),
});
export const createUserSchema = z.object({
username: z.string().min(3).max(30),
@@ -14,13 +14,17 @@ export const createUserSchema = z.object({
bio: z.string().max(500).optional(),
picture: z.url().optional(),
banner: z.url().optional(),
status: z.enum(['online', 'offline', 'dnd', 'idle', 'invis']).default('online'),
status: z
.enum(["online", "offline", "dnd", "idle", "invis"])
.default("online"),
admin: z.boolean().default(false),
requestingUserId: z.uuidv7(),
requestingUserToken: z.uuidv4(),
passwordhash: z.string(),
})
});
export type QueryUserByIdInput = z.infer<typeof queryUserByIdSchema>
export type QueryAllUsersByInstanceIdInput = z.infer<typeof queryAllUsersByInstanceId>
export type CreateUserInput = z.infer<typeof createUserSchema>
export type QueryUserByIdInput = z.infer<typeof queryUserByIdSchema>;
export type QueryAllUsersByInstanceIdInput = z.infer<
typeof queryAllUsersByInstanceId
>;
export type CreateUserInput = z.infer<typeof createUserSchema>;