From d1273ca4ec61711d4847c8ad761db77c95d2eb89 Mon Sep 17 00:00:00 2001 From: PrimarchPaul Date: Sat, 27 Sep 2025 20:45:36 -0400 Subject: [PATCH] feat: category and channel endpoints --- .../src/controller/categoryController.ts | 39 ++ .../src/controller/channelController.ts | 38 ++ concord-server/src/routes/categoryRoutes.ts | 254 ++++++++++ concord-server/src/routes/channelRoutes.ts | 264 ++++++++++ concord-server/src/routes/index.ts | 2 + concord-server/src/services/channelService.ts | 450 ++++++++++++++++++ concord-server/src/services/userService.ts | 1 + .../src/validators/categoryValidator.ts | 55 +++ .../src/validators/channelValidator.ts | 52 ++ .../src/validators/userValidator.ts | 2 +- 10 files changed, 1156 insertions(+), 1 deletion(-) create mode 100644 concord-server/src/controller/categoryController.ts create mode 100644 concord-server/src/controller/channelController.ts create mode 100644 concord-server/src/routes/categoryRoutes.ts create mode 100644 concord-server/src/routes/channelRoutes.ts create mode 100644 concord-server/src/services/channelService.ts create mode 100644 concord-server/src/validators/categoryValidator.ts create mode 100644 concord-server/src/validators/channelValidator.ts diff --git a/concord-server/src/controller/categoryController.ts b/concord-server/src/controller/categoryController.ts new file mode 100644 index 0000000..7177e5f --- /dev/null +++ b/concord-server/src/controller/categoryController.ts @@ -0,0 +1,39 @@ +import{ + createCategory, + getCategory, + getCategoriesByInstance, + updateCategory, + deleteCategory, + deleteAllCategoriesFromInstance, +} from "../services/channelService"; +import{ + CreateCategoryInput, + UpdateCategoryInput, + DeleteCategoryInput, + DeleteCategoriesByInstanceIdInput +} from "../validators/categoryValidator"; + +export async function createNewCategory(data: CreateCategoryInput) { + return await createCategory(data); +} + +export async function fetchCategoryData(id: string) { + return await getCategory(id); +} + +export async function fetchCategoriesByInstance(instanceId: string) { + return await getCategoriesByInstance(instanceId); +} + +export async function updateExistingCategory(data: UpdateCategoryInput) { + return await updateCategory(data); +} + +export async function deleteExistingCategory(data: DeleteCategoryInput) { + return await deleteCategory(data); +} + +export async function deleteAllCategoriesByInstance(data: DeleteCategoriesByInstanceIdInput) { + return await deleteAllCategoriesFromInstance(data); +} + diff --git a/concord-server/src/controller/channelController.ts b/concord-server/src/controller/channelController.ts new file mode 100644 index 0000000..b1c6cce --- /dev/null +++ b/concord-server/src/controller/channelController.ts @@ -0,0 +1,38 @@ +import{ + createChannel, + getChannel, + getChannelsByCategory, + updateChannel, + deleteChannel, + deleteAllChannelsFromCategory +} from "../services/channelService"; +import { + CreateChannelInput, + UpdateChannelInput, + DeleteChannelInput, + DeleteChannelsByCategoryIdInput +} from "../validators/channelValidator"; + +export async function createNewChannel(data: CreateChannelInput) { + return await createChannel(data); +} + +export async function fetchChannelData(id: string) { + return await getChannel(id); +} + +export async function fetchChannelsByCategory(categoryId: string) { + return await getChannelsByCategory(categoryId); +} + +export async function updateExistingChannel(data: UpdateChannelInput) { + return await updateChannel(data); +} + +export async function deleteExistingChannel(data: DeleteChannelInput) { + return await deleteChannel(data); +} + +export async function deleteAllChannelsByCategory(data: DeleteChannelsByCategoryIdInput) { + return await deleteAllChannelsFromCategory(data); +} \ No newline at end of file diff --git a/concord-server/src/routes/categoryRoutes.ts b/concord-server/src/routes/categoryRoutes.ts new file mode 100644 index 0000000..5db776f --- /dev/null +++ b/concord-server/src/routes/categoryRoutes.ts @@ -0,0 +1,254 @@ +import { + createNewCategory, + fetchCategoryData, + fetchCategoriesByInstance, + updateExistingCategory, + deleteExistingCategory, + deleteAllCategoriesByInstance, +} from "../controller/categoryController"; + +import { + createCategorySchema, + CreateCategoryInput, + UpdateCategoryInput, + DeleteCategoriesByInstanceIdInput, +} from "../validators/categoryValidator"; +import { zValidator } from "@hono/zod-validator"; +import { Hono } from "hono"; +import { describeRoute, resolver } from "hono-openapi"; +const categoryRoutes = new Hono() + +categoryRoutes.post( + "/category/create", + describeRoute({ + description: "Create a new category", + responses: { + 200: { + description: "Success creating category", + content: { + "application/json": { schema: resolver(createCategorySchema) }, + }, + }, + 400: { + description: "Bad Request - Invalid input data", + content: { + "application/json": { schema: resolver(createCategorySchema)}, + }, + }, + 401: { + description: "Unauthorized - Admin access required", + content: { + "application/json": { schema: resolver(createCategorySchema)}, + }, + }, + 404: { + description: "User Id not found", + content: { + "application/json": { schema: resolver(createCategorySchema)}, + }, + }, + }, + }), + zValidator("json", createCategorySchema), + async (c) => { + const data = c.req.valid("json") as CreateCategoryInput; + const categoryData = await createNewCategory(data); + if (categoryData) { + return c.json(categoryData); + } else { + return c.json({ error: "Failed to create category" }, 400); + } + } +) + +categoryRoutes.get( + "/:id", + describeRoute({ + description: "Get category by id", + responses: { + 200: { + description: "Success getting category", + content: { + "application/json": { schema: resolver(createCategorySchema) }, + }, + }, + 404: { + description: "Category id not found", + content: { + "application/json": { schema: resolver(createCategorySchema) }, + }, + }, + }, + }), + async (c) => { + const id = c.req.param("id"); + const categoryData = await fetchCategoryData(id); + if (categoryData) { + return c.json(categoryData); + } else { + return c.json({ error: "Category not found" }, 404); + } + } +); + +categoryRoutes.get( + "", + describeRoute({ + description: "Get all categories by instance id", + responses: { + 200: { + description: "Success getting all categories in instance", + content: { + "application/json": { schema: resolver(createCategorySchema) }, + }, + }, + }, + }), + zValidator("query", createCategorySchema), + async (c) => { + const instanceId = c.req.query("instanceId"); + if (!instanceId) { + return c.json({ error: "No instance id provided" }, 400); + } + + const categoryData = await fetchCategoriesByInstance(instanceId); + if (categoryData) { + return c.json(categoryData); + } else { + return c.json({ error: "Error getting all categories from instance" }, 500); + } + } +); + +categoryRoutes.put( + "/category/update", + describeRoute({ + description: "Update an existing category", + responses: { + 200: { + description: "Success updating category", + content: { + "application/json": { schema: resolver(createCategorySchema) }, + }, + }, + 400: { + description: "Bad Request - Invalid input data", + content: { + "application/json": { schema: resolver(createCategorySchema)}, + }, + }, + 401: { + description: "Unauthorized - Admin access required", + content: { + "application/json": { schema: resolver(createCategorySchema)}, + }, + }, + 404: { + description: "Category id or User Id not found", + content: { + "application/json": { schema: resolver(createCategorySchema)}, + }, + }, + }, + }), + zValidator("json", createCategorySchema), + async (c) => { + const data = c.req.valid("json") as UpdateCategoryInput; + const categoryData = await updateExistingCategory(data); + if (categoryData) { + return c.json(categoryData); + } else { + return c.json({ error: "Failed to update category" }, 400); + } + } +); + +categoryRoutes.delete( + "/category/delete", + describeRoute({ + description: "Delete an existing category", + responses: { + 200: { + description: "Success deleting category", + content: { + "application/json": { schema: resolver(createCategorySchema) }, + }, + }, + 400: { + description: "Bad Request - Invalid input data", + content: { + "application/json": { schema: resolver(createCategorySchema)}, + }, + }, + 401: { + description: "Unauthorized - Admin access required", + content: { + "application/json": { schema: resolver(createCategorySchema)}, + }, + }, + 404: { + description: "Category id or User Id not found", + content: { + "application/json": { schema: resolver(createCategorySchema)}, + }, + }, + }, + }), + zValidator("json", createCategorySchema), + async (c) => { + const data = c.req.valid("json") as UpdateCategoryInput; + const categoryData = await deleteExistingCategory(data); + if (categoryData) { + return c.json(categoryData); + } else { + return c.json({ error: "Failed to delete category" }, 400); + } + } +) + +categoryRoutes.delete( + "/categories/delete/:id", + describeRoute({ + description: "Delete all categories by instance id", + responses: { + 200: { + description: "Success deleting all categories in instance", + content: { + "application/json": { schema: resolver(createCategorySchema) }, + }, + }, + 400: { + description: "Bad Request - Invalid input data", + content: { + "application/json": { schema: resolver(createCategorySchema)}, + }, + }, + 401: { + description: "Unauthorized - Admin access required", + content: { + "application/json": { schema: resolver(createCategorySchema)}, + }, + }, + 404: { + description: "Instance id or User Id not found", + content: { + "application/json": { schema: resolver(createCategorySchema)}, + }, + }, + }, + }), + zValidator("json", createCategorySchema), + async (c) => { + const data = c.req.valid("json") as DeleteCategoriesByInstanceIdInput; + const categoryData = await deleteAllCategoriesByInstance(data); + if (categoryData) { + return c.json(categoryData); + } else { + return c.json({ error: "Failed to delete categories" }, 400); + } + } +) + + + +export { categoryRoutes }; \ No newline at end of file diff --git a/concord-server/src/routes/channelRoutes.ts b/concord-server/src/routes/channelRoutes.ts new file mode 100644 index 0000000..b8a3dd6 --- /dev/null +++ b/concord-server/src/routes/channelRoutes.ts @@ -0,0 +1,264 @@ +import { + createNewChannel, + fetchChannelData, + fetchChannelsByCategory, + updateExistingChannel, + deleteExistingChannel, + deleteAllChannelsByCategory, +} from "../controller/channelController"; + +import { + createChannelSchema, + getChannelSchema, + getChannelsByCategoryIdSchema, + updateChannelSchema, + deleteChannelSchema, + deleteChannelsByCategoryIdSchema, + + CreateChannelInput, + GetChannelInput, + GetChannelsByCategoryIdInput, + UpdateChannelInput, + DeleteChannelInput, + DeleteChannelsByCategoryIdInput, +} from "../validators/channelValidator"; +import { zValidator } from "@hono/zod-validator"; +import { Hono } from "hono"; +import { describeRoute, resolver } from "hono-openapi"; + +const channelRoutes = new Hono() + +channelRoutes.post( + "/channel/create", + describeRoute({ + description: "Create a new channel", + responses: { + 200: { + description: "Success creating channel", + content: { + "application/json": { schema: resolver(createChannelSchema) }, + }, + }, + 400: { + description: "Bad Request - Invalid input data", + content: { + "application/json": { schema: resolver(createChannelSchema)}, + }, + }, + 401: { + description: "Unauthorized - Admin access required", + content: { + "application/json": { schema: resolver(createChannelSchema)}, + }, + }, + 404: { + description: "User Id not found", + content: { + "application/json": { schema: resolver(createChannelSchema)}, + }, + }, + }, + }), + zValidator("json", createChannelSchema), + async (c) => { + const data = c.req.valid("json") as CreateChannelInput; + const channelData = await createNewChannel(data); + if (channelData) { + return c.json(channelData); + } else { + return c.json({ error: "Failed to create channel" }, 400); + } + } +) + +channelRoutes.get( + "/:id", + describeRoute({ + description: "Get channel by id", + responses: { + 200: { + description: "Success getting channel", + content: { + "application/json": { schema: resolver(getChannelSchema) }, + }, + }, + 404: { + description: "Channel id not found", + content: { + "application/json": { schema: resolver(getChannelSchema) }, + }, + }, + }, + }), + async (c) => { + const id = c.req.param("id"); + const channelData = await fetchChannelData(id); + if (channelData) { + return c.json(channelData); + } else { + return c.json({ error: "Channel not found" }, 404); + } + } +); + +channelRoutes.get( + "", + describeRoute({ + description: "Get all channels by category id", + responses: { + 200: { + description: "Success getting all channels in category", + content: { + "application/json": { schema: resolver(getChannelsByCategoryIdSchema) }, + }, + }, + }, + }), + zValidator("query", getChannelsByCategoryIdSchema), + async (c) => { + const categoryId = c.req.query("categoryId"); + if (!categoryId) { + return c.json({ error: "No category id provided" }, 400); + } + + const channels = await fetchChannelsByCategory(categoryId); + if (channels) { + return c.json(channels); + } else { + return c.json({ error: "Error getting channels from category" }, 500); + } + } +); + +channelRoutes.put( + "/channel/update", + describeRoute({ + description: "Update an existing channel", + responses: { + 200: { + description: "Success updating channel", + content: { + "application/json": { schema: resolver(updateChannelSchema) }, + }, + }, + 400: { + description: "Bad Request - Invalid input data", + content: { + "application/json": { schema: resolver(updateChannelSchema)}, + }, + }, + 401: { + description: "Unauthorized - Admin access required", + content: { + "application/json": { schema: resolver(updateChannelSchema)}, + }, + }, + 404: { + description: "Channel id or User Id not found", + content: { + "application/json": { schema: resolver(updateChannelSchema)}, + }, + }, + }, + }), + zValidator("json", updateChannelSchema), + async (c) => { + const data = c.req.valid("json") as UpdateChannelInput; + const result = await updateExistingChannel(data); + if (result) { + return c.json(result); + } else { + return c.json({ error: "Failed to update channel" }, 400); + } + } +); + +channelRoutes.delete( + "/channel/delete", + describeRoute({ + description: "Delete an existing channel", + responses: { + 200: { + description: "Success deleting channel", + content: { + "application/json": { schema: resolver(deleteChannelSchema) }, + }, + }, + 400: { + description: "Bad Request - Invalid input data", + content: { + "application/json": { schema: resolver(deleteChannelSchema)}, + }, + }, + 401: { + description: "Unauthorized - Admin access required", + content: { + "application/json": { schema: resolver(deleteChannelSchema)}, + }, + }, + 404: { + description: "Channel id or User Id not found", + content: { + "application/json": { schema: resolver(deleteChannelSchema)}, + }, + }, + }, + }), + zValidator("json", deleteChannelSchema), + async (c) => { + const data = c.req.valid("json") as DeleteChannelInput; + const result = await deleteExistingChannel(data); + if (result) { + return c.json({ success: true }); + } else { + return c.json({ error: "Failed to delete channel" }, 400); + } + } +); + +channelRoutes.delete( + "/channels/delete-by-category", + describeRoute({ + description: "Delete all channels by category id", + responses: { + 200: { + description: "Success deleting all channels in category", + content: { + "application/json": { schema: resolver(deleteChannelsByCategoryIdSchema) }, + }, + }, + 400: { + description: "Bad Request - Invalid input data", + content: { + "application/json": { schema: resolver(deleteChannelsByCategoryIdSchema)}, + }, + }, + 401: { + description: "Unauthorized - Admin access required", + content: { + "application/json": { schema: resolver(deleteChannelsByCategoryIdSchema)}, + }, + }, + 404: { + description: "Category id or User Id not found", + content: { + "application/json": { schema: resolver(deleteChannelsByCategoryIdSchema)}, + }, + }, + }, + }), + zValidator("json", deleteChannelsByCategoryIdSchema), + async (c) => { + const data = c.req.valid("json") as DeleteChannelsByCategoryIdInput; + const result = await deleteAllChannelsByCategory(data); + if (result) { + return c.json({ success: true }); + } else { + return c.json({ error: "Failed to delete channels" }, 400); + } + } +) + + + +export default channelRoutes ; \ No newline at end of file diff --git a/concord-server/src/routes/index.ts b/concord-server/src/routes/index.ts index 0a8b476..a33ac3d 100644 --- a/concord-server/src/routes/index.ts +++ b/concord-server/src/routes/index.ts @@ -2,10 +2,12 @@ import { Hono } from "hono"; import userRoutes from "./userRoutes"; import messageRoutes from "./messageRoutes"; +import channelRoutes from "./channelRoutes"; const routes = new Hono(); routes.route("/user", userRoutes); routes.route("/message", messageRoutes); +routes.route("/channel", channelRoutes); export default routes; diff --git a/concord-server/src/services/channelService.ts b/concord-server/src/services/channelService.ts new file mode 100644 index 0000000..17c2b0f --- /dev/null +++ b/concord-server/src/services/channelService.ts @@ -0,0 +1,450 @@ +import{ + Channel, + Category +} from '@prisma/client'; +import { PrismaClient } from "@prisma/client"; +import { getUserInformation, getUserCredentials } from './userService'; +import { + CreateChannelInput, + UpdateChannelInput, + DeleteChannelInput, + DeleteChannelsByCategoryIdInput +} from '../validators/channelValidator'; +import{ + UpdateCategoryInput, + DeleteCategoryInput, + DeleteCategoriesByInstanceIdInput, + CreateCategoryInput +} from '../validators/categoryValidator'; + +const prisma = new PrismaClient(); + + +export async function createCategory(data: CreateCategoryInput): Promise{ + try{ + + //Confirm if user exists and is admin + const requestingUser = await getUserInformation(data.requestingUserId); + const requestingUserCredentials = await getUserCredentials( + data.requestingUserId, + ) + + if ( + !requestingUser || + !requestingUserCredentials || + !requestingUser.admin || + requestingUserCredentials.token == null || + data.requestingUserToken != requestingUserCredentials.token + ) { + return null; + } + + const newCategory = await prisma.category.create({ + data: { + name: data.name, + position: data.position + } + }); + + if(!newCategory){ + throw new Error("could not create category"); + } + + let curInstance; + if(data.instanceId){ + curInstance = await prisma.instance.findUnique({ + where: { + id: data.instanceId + }, + include: { + Category: true + } + }); + + if(!curInstance){ + throw new Error("could not find instance to add category to"); + } + + await prisma.category.update({ + where: { + id: newCategory.id + }, + data: { + instanceId: curInstance.id + } + }); + + return newCategory; + } + + return newCategory; + }catch(err){ + console.log("services::channelService::createCategory - ", err); + return null; + } +} + +export async function getCategory( + categoryId: string, +): Promise{ + try{ + const category = await prisma.category.findUnique({ + where: { + id: categoryId + } + }); + + if(!category){ + throw new Error("could not find category"); + } + + return category; + }catch(err){ + console.log("services::channelService::getCategory - ", err); + return null; + } +} + +export async function getCategoriesByInstance( + instanceId: string +): Promise{ + try{ + const categories = await prisma.category.findMany({ + where: { + instanceId: instanceId + }, + include: { + Channel: true + }, + orderBy: { + position: 'asc' + } + }); + + if(!categories){ + throw new Error("could not find categories for instance"); + } + + return categories; + }catch(err){ + console.log("services::channelService::getCategoriesByInstance - ", err); + return null; + } +} + +export async function updateCategory(data: UpdateCategoryInput): Promise{ + try{ + + //Confirm if user exists and is admin + const requestingUser = await getUserInformation(data.requestingUserId); + const requestingUserCredentials = await getUserCredentials( + data.requestingUserId, + ) + + if ( + !requestingUser || + !requestingUserCredentials || + !requestingUser.admin || + requestingUserCredentials.token == null || + data.requestingUserToken != requestingUserCredentials.token + ) { + return null; + } + + const updatedCategory = await prisma.category.update({ + where: { + id: data.id + }, + data: { + name: data.name, + position: data.position, + Channel: data.channels ? { set: data.channels } : undefined + } + }); + + if(!updatedCategory){ + throw new Error("could not update category"); + } + + return updatedCategory; + }catch(err){ + console.log("services::channelService::updateCategory - ", err); + return null; + } +} + +export async function deleteCategory(data: DeleteCategoryInput): Promise{ + try{ + + //Confirm if user exists and is admin + const requestingUser = await getUserInformation(data.requestingUserId); + const requestingUserCredentials = await getUserCredentials( + data.requestingUserId, + ) + + if ( + !requestingUser || + !requestingUserCredentials || + !requestingUser.admin || + requestingUserCredentials.token == null || + data.requestingUserToken != requestingUserCredentials.token + ) { + return null; + } + + const deleteAllChannels = await prisma.channel.deleteMany({ + where: { + categoryId: data.id + } + }); + + if(deleteAllChannels.count === 0){ + throw new Error("could not delete channels from category"); + } + + const deletedCategory = await prisma.category.delete({ + where: { + id: data.id + } + }); + + if(!deletedCategory){ + throw new Error("could not delete category"); + } + + return true; + }catch(err){ + console.log("services::channelService::deleteCategory - ", err); + return false; + } +} + +export async function deleteAllCategoriesFromInstance(data: DeleteCategoriesByInstanceIdInput): Promise{ + try{ + + //Confirm if user exists and is admin + const requestingUser = await getUserInformation(data.requestingUserId); + const requestingUserCredentials = await getUserCredentials( + data.requestingUserId, + ) + + if ( + !requestingUser || + !requestingUserCredentials || + !requestingUser.admin || + requestingUserCredentials.token == null || + data.requestingUserToken != requestingUserCredentials.token + ) { + return null; + } + + const deletedCategories = await prisma.category.deleteMany({ + where: { + instanceId: data.instanceId + } + }); + + if(deletedCategories.count === 0){ + throw new Error("could not delete categories from instance"); + } + + return true; + }catch(err){ + console.log("services::channelService::deleteAllCategoriesFromInstance - ", err); + return false; + } +} + +export async function createChannel(data: CreateChannelInput): Promise{ + try{ + + //Confirm if user exists and is admin + const requestingUser = await getUserInformation(data.requestingUserId); + const requestingUserCredentials = await getUserCredentials( + data.requestingUserId, + ) + + if ( + !requestingUser || + !requestingUserCredentials || + !requestingUser.admin || + requestingUserCredentials.token == null || + data.requestingUserToken != requestingUserCredentials.token + ) { + return null; + } + + const newChannel = await prisma.channel.create({ + data: { + type: data.type, + name: data.name, + description: data.description, + categoryId: data.categoryId ? data.categoryId : null + } + }); + + if(!newChannel){ + throw new Error("could not create channel"); + } + + return newChannel; + }catch(err){ + console.log("services::channelService::createChannel - ", err); + return null; + } +} + +export async function getChannel( + channelId: string +): Promise{ + try{ + const channel = await prisma.channel.findUnique({ + where: { + id: channelId + } + }); + + if(!channel){ + throw new Error("could not find channel"); + } + + return channel; + }catch(err){ + console.log("services::channelService::getChannel - ", err); + return null; + } +} + +export async function getChannelsByCategory( + categoryId: string +): Promise{ + try{ + const channels = await prisma.channel.findMany({ + where: { + categoryId: categoryId + } + }); + + if(!channels){ + throw new Error("could not find channels for category"); + } + return channels; + } + catch(err){ + console.log("services::channelService::getChannelsByCategory - ", err); + return null; + } +} + +export async function updateChannel(data: UpdateChannelInput): Promise{ + try{ + + //Confirm if user exists and is admin + const requestingUser = await getUserInformation(data.requestingUserId); + const requestingUserCredentials = await getUserCredentials( + data.requestingUserId, + ) + + if ( + !requestingUser || + !requestingUserCredentials || + !requestingUser.admin || + requestingUserCredentials.token == null || + data.requestingUserToken != requestingUserCredentials.token + ) { + return null; + } + + const updatedChannel = await prisma.channel.update({ + where: { + id: data.id + }, + data: { + name: data.name, + description: data.description, + categoryId: data.categoryId ? data.categoryId : undefined + } + }); + + if(!updatedChannel){ + throw new Error("could not update channel"); + } + + return updatedChannel; + }catch(err){ + console.log("services::channelService::updateChannel - ", err); + return null; + } +} + +export async function deleteChannel(data: DeleteChannelInput): Promise{ + try{ + + //Confirm if user exists and is admin + const requestingUser = await getUserInformation(data.requestingUserId); + const requestingUserCredentials = await getUserCredentials( + data.requestingUserId, + ) + + if ( + !requestingUser || + !requestingUserCredentials || + !requestingUser.admin || + requestingUserCredentials.token == null || + data.requestingUserToken != requestingUserCredentials.token + ) { + return null; + } + + const deletedChannel = await prisma.channel.delete({ + where: { + id: data.id + } + }); + + if(!deletedChannel){ + throw new Error("could not delete channel"); + } + + return true; + }catch(err){ + console.log("services::channelService::deleteChannel - ", err); + return false; + } +} + +export async function deleteAllChannelsFromCategory(data: DeleteChannelsByCategoryIdInput): Promise +{ + try{ + //Confirm if user exists and is admin + const requestingUser = await getUserInformation(data.requestingUserId); + const requestingUserCredentials = await getUserCredentials( + data.requestingUserId, + ) + + if ( + !requestingUser || + !requestingUserCredentials || + !requestingUser.admin || + requestingUserCredentials.token == null || + data.requestingUserToken != requestingUserCredentials.token + ) { + return null; + } + + const deletedChannels = await prisma.channel.deleteMany({ + where: { + categoryId: data.categoryId + } + }); + + if(deletedChannels.count === 0){ + throw new Error("could not delete channels from category"); + } + + return true; + }catch(err){ + console.log("services::channelService::deleteAllChannelsFromCategory - ", err); + return false; + } +} diff --git a/concord-server/src/services/userService.ts b/concord-server/src/services/userService.ts index eec7a47..e960534 100644 --- a/concord-server/src/services/userService.ts +++ b/concord-server/src/services/userService.ts @@ -1,3 +1,4 @@ + import { Message, MessagePing, diff --git a/concord-server/src/validators/categoryValidator.ts b/concord-server/src/validators/categoryValidator.ts new file mode 100644 index 0000000..5dde0ee --- /dev/null +++ b/concord-server/src/validators/categoryValidator.ts @@ -0,0 +1,55 @@ +import { z } from 'zod'; + +//category validators + +export const createCategorySchema = z.object({ + name: z.string().min(1).max(50), + position: z.number().min(0), + instanceId : z.uuidv7().optional(), + admin: z.boolean(), + requestingUserId: z.uuidv7(), + requestingUserToken: z.uuidv4() +}) + +export const getCategorySchema = z.object({ + id: z.uuidv7() +}) + +export const getCategoriesByInstanceIdSchema = z.object({ + instanceId: z.uuidv7() + +}) + +export const updateCategorySchema = z.object({ + id: z.uuidv7(), + name: z.string().min(1).max(50).optional(), + position: z.number().min(0).optional(), + channels: z.array(z.object({ + id: z.string() + })).optional(), + admin: z.boolean(), + requestingUserId: z.uuidv7(), + requestingUserToken: z.uuidv4() +}) + +export const deleteCategorySchema = z.object({ + id: z.uuidv7(), + admin: z.boolean(), + requestingUserId: z.uuidv7(), + requestingUserToken: z.uuidv4() +}) + +export const deleteCategoriesByInstanceIdSchema = z.object({ + instanceId: z.uuidv7(), + admin: z.boolean(), + requestingUserId: z.uuidv7(), + requestingUserToken: z.uuidv4() +}) + + +export type CreateCategoryInput = z.infer +export type GetCategoryInput = z.infer +export type GetCategoriesByInstanceIdInput = z.infer +export type UpdateCategoryInput = z.infer +export type DeleteCategoryInput = z.infer +export type DeleteCategoriesByInstanceIdInput = z.infer diff --git a/concord-server/src/validators/channelValidator.ts b/concord-server/src/validators/channelValidator.ts new file mode 100644 index 0000000..53e48bc --- /dev/null +++ b/concord-server/src/validators/channelValidator.ts @@ -0,0 +1,52 @@ +import { z } from "zod"; + +//channel validators + +export const createChannelSchema = z.object({ + type: z.enum(['text', 'voice']), + name: z.string().min(1).max(50), + description: z.string().max(255), + categoryId: z.uuidv7().optional(), + admin: z.boolean(), + requestingUserId: z.uuidv7(), + requestingUserToken: z.uuidv4() +}) + +export const getChannelSchema = z.object({ + id: z.uuidv7() +}) + +export const getChannelsByCategoryIdSchema = z.object({ + categoryId: z.uuidv7() +}) + +export const updateChannelSchema = z.object({ + id: z.uuidv7(), + name: z.string().min(1).max(50).optional(), + description: z.string().max(255).optional(), + categoryId: z.uuidv7().optional(), + admin: z.boolean(), + requestingUserId: z.uuidv7(), + requestingUserToken: z.uuidv4() +}) + +export const deleteChannelSchema = z.object({ + id: z.uuidv7(), + admin: z.boolean(), + requestingUserId: z.uuidv7(), + requestingUserToken: z.uuidv4() +}) + +export const deleteChannelsByCategoryIdSchema = z.object({ + categoryId: z.uuidv7(), + admin: z.boolean(), + requestingUserId: z.uuidv7(), + requestingUserToken: z.uuidv4() +}) + +export type CreateChannelInput = z.infer +export type GetChannelInput = z.infer +export type GetChannelsByCategoryIdInput = z.infer +export type UpdateChannelInput = z.infer +export type DeleteChannelInput = z.infer +export type DeleteChannelsByCategoryIdInput = z.infer \ No newline at end of file diff --git a/concord-server/src/validators/userValidator.ts b/concord-server/src/validators/userValidator.ts index 9b9bfb6..0789128 100644 --- a/concord-server/src/validators/userValidator.ts +++ b/concord-server/src/validators/userValidator.ts @@ -7,7 +7,7 @@ export const queryUserByIdSchema = z.object({ export const queryAllUsersByInstanceId = z.object({ instanceId: z.uuidv7(), }); - +import { is } from 'zod/v4/locales'; export const createUserSchema = z.object({ username: z.string().min(3).max(30), nickname: z.string().min(1).max(30).optional(),