mirror of
https://github.com/detleph/server.git
synced 2026-09-04 08:36:06 +02:00
Merge branch 'dev' into feature-email-verification
This commit is contained in:
@@ -1,26 +1,56 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
D_SERVICES="mail postgres redis"
|
||||||
|
|
||||||
DATABASE_PASSWORD=server COMPOSE_PROJECT_NAME=detleph_server docker-compose up -d mail postgres redis
|
DATABASE_PASSWORD=server COMPOSE_PROJECT_NAME=detleph_server docker-compose up -d $D_SERVICES
|
||||||
|
|
||||||
|
# Flag: Should the container be deleted and created again?
|
||||||
|
RECREATE=true
|
||||||
|
D_PORT="${PORT:-3000}"
|
||||||
|
|
||||||
|
if [ "$(docker ps -a | grep detleph_server_dev)" ]; then
|
||||||
|
echo "The dev server is already on the system!"
|
||||||
|
|
||||||
|
RECREATE=false
|
||||||
|
|
||||||
|
# Use -s flag to skip any prompts
|
||||||
|
if [ "$1" != "-s" ]; then
|
||||||
|
echo "Do you want to recreate it? [y/n]"
|
||||||
|
read input
|
||||||
|
|
||||||
|
if [ "$input" = "y" ]; then
|
||||||
|
RECREATE=true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$RECREATE" = true ]; then
|
||||||
|
echo "Removing container"
|
||||||
|
docker rm detleph_server_dev
|
||||||
|
rm -f INITIALIZED # Flag has to be reset
|
||||||
|
|
||||||
|
echo "Do you also want to reset the other services? [y/n]"
|
||||||
|
read input
|
||||||
|
|
||||||
|
if [ "$input" = "y" ]; then
|
||||||
|
COMPOSE_PROJECT_NAME=detleph_server docker-compose down
|
||||||
|
DATABASE_PASSWORD=server COMPOSE_PROJECT_NAME=detleph_server docker-compose up -d $D_SERVICES
|
||||||
|
fi
|
||||||
|
|
||||||
|
else
|
||||||
|
echo "Starting existing container"
|
||||||
|
docker start -ia detleph_server_dev
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$RECREATE" = true ]; then
|
||||||
|
echo "Creating the dev container"
|
||||||
|
|
||||||
if [ ! "$(docker ps -a | grep detleph_server_dev)" ]; then
|
|
||||||
echo "Creating the server container"
|
|
||||||
|
|
||||||
docker run -it \
|
docker run -it \
|
||||||
--name detleph_server_dev \
|
--name detleph_server_dev \
|
||||||
--mount type=bind,source="$(pwd)",target=/app \
|
--mount type=bind,source="$(pwd)",target=/app \
|
||||||
--network detleph_server_default \
|
--network detleph_server_default \
|
||||||
-p 3000:3000 -e PORT=3000 \
|
|
||||||
-e DATABASE_URL="postgresql://server:server@postgres:5432/management?schema=public" \
|
|
||||||
-e DATABASE_USER=server \
|
|
||||||
-e DATABASE_PASSWORD=server \
|
-e DATABASE_PASSWORD=server \
|
||||||
|
-e DATABASE_URL="postgresql://server:server@postgres:5432/management?schema=public" \
|
||||||
--entrypoint "/app/scripts/docker-entrypoint.dev.sh" \
|
--entrypoint "/app/scripts/docker-entrypoint.dev.sh" \
|
||||||
node
|
node
|
||||||
else
|
|
||||||
echo "The server container already exists; Starting..."
|
|
||||||
|
|
||||||
docker start -ia detleph_server_dev
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# After container termination
|
|
||||||
|
|
||||||
COMPOSE_PROJECT_NAME=detleph_server docker-compose stop
|
COMPOSE_PROJECT_NAME=detleph_server docker-compose stop
|
||||||
|
|||||||
@@ -134,9 +134,9 @@ model Group {
|
|||||||
name String
|
name String
|
||||||
user_limit Int @default(40)
|
user_limit Int @default(40)
|
||||||
level Int
|
level Int
|
||||||
|
organisation Organisation @relation(fields: [organisationId], references: [id], onDelete: Cascade)
|
||||||
|
organisationId Int
|
||||||
|
|
||||||
oragnisation Organisation @relation(fields: [oragnisationId], references: [id], onDelete: Cascade)
|
|
||||||
oragnisationId Int
|
|
||||||
participants Participant[]
|
participants Participant[]
|
||||||
link Link?
|
link Link?
|
||||||
admins Admin[]
|
admins Admin[]
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import prisma from "../lib/prisma";
|
|||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import { AdminLevel } from "@prisma/client";
|
import { AdminLevel } from "@prisma/client";
|
||||||
import argon2 from "argon2";
|
import argon2 from "argon2";
|
||||||
import { DataType, generateInvalidBodyError } from "./common";
|
import { AUTH_ERROR, createInsufficientPermissionsError, DataType, generateInvalidBodyError } from "./common";
|
||||||
import { authClient } from "../lib/redis";
|
import { authClient } from "../lib/redis";
|
||||||
|
|
||||||
export const regenerateRevision = async (pid: string) => {
|
export const regenerateRevision = async (pid: string) => {
|
||||||
@@ -16,28 +16,6 @@ export const regenerateRevision = async (pid: string) => {
|
|||||||
await authClient.set(pid, revision.toISOString());
|
await authClient.set(pid, revision.toISOString());
|
||||||
};
|
};
|
||||||
|
|
||||||
const AUTH_ERROR = {
|
|
||||||
type: "failure",
|
|
||||||
payload: {
|
|
||||||
message: "The server was not able to validate your credentials; Please try again later",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const createInsufficientPermissionsError = (required: AdminLevel = "ELEVATED") => ({
|
|
||||||
type: "error",
|
|
||||||
payload: {
|
|
||||||
message: "You do not have sufficient permissions to use this feature",
|
|
||||||
required_level: required,
|
|
||||||
},
|
|
||||||
_links: [
|
|
||||||
{
|
|
||||||
rel: "authentication",
|
|
||||||
href: "/api/authentication",
|
|
||||||
type: "POST",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
// requires: auth(elevated)
|
// requires: auth(elevated)
|
||||||
export const getAllAdmins = async (req: Request, res: Response) => {
|
export const getAllAdmins = async (req: Request, res: Response) => {
|
||||||
if (!req.auth?.isAuthenticated) {
|
if (!req.auth?.isAuthenticated) {
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
import { AdminLevel, Prisma } from "@prisma/client";
|
||||||
|
import { PrismaClientKnownRequestError, PrismaClientUnknownRequestError } from "@prisma/client/runtime";
|
||||||
|
import { Request, Response } from "express";
|
||||||
|
import prisma from "../lib/prisma";
|
||||||
|
|
||||||
export enum DataType {
|
export enum DataType {
|
||||||
STRING = "string",
|
STRING = "string",
|
||||||
NUMBER = "number",
|
NUMBER = "number",
|
||||||
@@ -20,3 +25,126 @@ export function generateInvalidBodyError(body: Body) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const AUTH_ERROR = {
|
||||||
|
type: "failure",
|
||||||
|
payload: {
|
||||||
|
message: "The server was not able to validate your credentials; Please try again later",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createInsufficientPermissionsError = (required: AdminLevel = "ELEVATED") => ({
|
||||||
|
type: "error",
|
||||||
|
payload: {
|
||||||
|
message: "You do not have sufficient permissions to use this feature",
|
||||||
|
required_level: required,
|
||||||
|
},
|
||||||
|
_links: [
|
||||||
|
{
|
||||||
|
rel: "authentication",
|
||||||
|
href: "/api/authentication",
|
||||||
|
type: "POST",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
export const generateError = (message: string) => {
|
||||||
|
return {
|
||||||
|
type: "error",
|
||||||
|
payload: {
|
||||||
|
message,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const genericError = {
|
||||||
|
type: "error",
|
||||||
|
payload: {
|
||||||
|
message: "There was an error processing your request, please try again later",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export function validateName(name: string) {
|
||||||
|
return name.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Organisation = Partial<Prisma.OrganisationCreateArgs["data"]>;
|
||||||
|
type Group = Partial<Prisma.GroupCreateArgs["data"]>;
|
||||||
|
|
||||||
|
export async function handleCreateByName(
|
||||||
|
create: { type: "organisation"; data: Organisation },
|
||||||
|
link: { type: "event"; id: string },
|
||||||
|
req: Request<any>,
|
||||||
|
res: Response
|
||||||
|
): Promise<unknown>;
|
||||||
|
export async function handleCreateByName(
|
||||||
|
create: { type: "group"; data: Group },
|
||||||
|
link: { type: "organisation"; id: string },
|
||||||
|
req: Request<any>,
|
||||||
|
res: Response
|
||||||
|
): Promise<unknown>;
|
||||||
|
export async function handleCreateByName(
|
||||||
|
create: { type: "organisation" | "group"; data: Organisation | Group },
|
||||||
|
link: { type: "event" | "organisation"; id: string },
|
||||||
|
req: Request<any>,
|
||||||
|
res: Response
|
||||||
|
) {
|
||||||
|
if (!req.auth?.isAuthenticated) {
|
||||||
|
return res.status(500).json(AUTH_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.auth.permission_level !== "ELEVATED") {
|
||||||
|
return res.status(403).json(createInsufficientPermissionsError());
|
||||||
|
}
|
||||||
|
|
||||||
|
const { name } = create.data;
|
||||||
|
|
||||||
|
if (typeof name !== "string") {
|
||||||
|
return res
|
||||||
|
.status(400)
|
||||||
|
.json(generateInvalidBodyError({ name: DataType.STRING, [link.type + "Pid"]: DataType.STRING }));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validateName(name)) {
|
||||||
|
return res.status(400).json({
|
||||||
|
type: "error",
|
||||||
|
payload: {
|
||||||
|
message: "The name has to be at least 1 character long",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if link object exsits
|
||||||
|
|
||||||
|
try {
|
||||||
|
// @ts-ignore
|
||||||
|
const linked = await prisma[link.type].findUnique({ where: { pid: link.id }, select: { id: true } });
|
||||||
|
|
||||||
|
if (!linked) {
|
||||||
|
return res.status(404).json({
|
||||||
|
type: "error",
|
||||||
|
payload: {
|
||||||
|
message: `Could not link to ${link.type} with ID '${link.id}'`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// REVIEW: Check for valid UUID
|
||||||
|
if (e instanceof PrismaClientUnknownRequestError) {
|
||||||
|
return res.status(400).json({
|
||||||
|
type: "error",
|
||||||
|
payload: {
|
||||||
|
message: "Unknown error occured. This could be due to malformed IDs",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const object = await prisma[create.type].create({
|
||||||
|
data: { ...create.data, [link.type]: { connect: { pid: link.id } } },
|
||||||
|
select: { pid: true, name: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(201).json({ type: "success", payload: { [create.type]: object } });
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client"
|
||||||
|
import { PrismaClientKnownRequestError } from "@prisma/client/runtime";
|
||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import prisma from "../lib/prisma";
|
import prisma from "../lib/prisma";
|
||||||
import { DataType, generateInvalidBodyError } from "./common";
|
import { createInsufficientPermissionsError, DataType, generateError, generateInvalidBodyError } from "./common";
|
||||||
|
|
||||||
export const getAllEvents = async (req: Request, res: Response) => {
|
export const getAllEvents = async (req: Request, res: Response) => {
|
||||||
const events = await prisma.event.findMany({
|
const events = await prisma.event.findMany({
|
||||||
@@ -13,8 +14,13 @@ export const getAllEvents = async (req: Request, res: Response) => {
|
|||||||
id: false,
|
id: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (events.length > 0) res.status(200).json(events);
|
if (events.length > 0)
|
||||||
else res.status(200).json([]);
|
res.status(200).json({
|
||||||
|
type: "success",
|
||||||
|
payload: {
|
||||||
|
events,
|
||||||
|
},
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getEvent = async (req: Request, res: Response) => {
|
export const getEvent = async (req: Request, res: Response) => {
|
||||||
@@ -42,7 +48,12 @@ export const getEvent = async (req: Request, res: Response) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
res.status(200).json(event);
|
res.status(200).json({
|
||||||
|
type: "success",
|
||||||
|
payload: {
|
||||||
|
event,
|
||||||
|
},
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof Prisma.PrismaClientKnownRequestError) {
|
if (e instanceof Prisma.PrismaClientKnownRequestError) {
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
@@ -68,20 +79,23 @@ export const getEvent = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// requires: auth(ELEVATED)
|
||||||
export const addEvent = async (req: Request, res: Response) => {
|
export const addEvent = async (req: Request, res: Response) => {
|
||||||
|
if (req.auth?.permission_level !== "ELEVATED") {
|
||||||
|
res.status(403).json(createInsufficientPermissionsError());
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
typeof req.body.name !== "string" ||
|
typeof req.body.name !== "string" ||
|
||||||
typeof req.body.date !== "string" ||
|
typeof req.body.date !== "string" ||
|
||||||
typeof req.body.description !== "string"
|
typeof req.body.description !== "string"
|
||||||
) {
|
) {
|
||||||
res.status(400).json(
|
|
||||||
generateInvalidBodyError({
|
generateInvalidBodyError({
|
||||||
name: DataType.STRING,
|
name: DataType.STRING,
|
||||||
date: DataType.DATETIME,
|
date: DataType.DATETIME,
|
||||||
description: DataType.STRING,
|
description: DataType.STRING,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: Check if date is valid
|
//TODO: Check if date is valid
|
||||||
@@ -102,9 +116,34 @@ export const addEvent = async (req: Request, res: Response) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
type: "succes",
|
type: "success",
|
||||||
payload: {
|
payload: {
|
||||||
event,
|
event,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface DeleteEventQueryParams {
|
||||||
|
pid: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// requires: auth(ELEVATED)
|
||||||
|
export const deleteEvent = (req: Request<DeleteEventQueryParams>, res: Response) => {
|
||||||
|
if (req.auth?.permission_level !== "ELEVATED") {
|
||||||
|
res.status(403).json(createInsufficientPermissionsError());
|
||||||
|
}
|
||||||
|
|
||||||
|
const { pid } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
prisma.event.delete({ where: { pid } });
|
||||||
|
|
||||||
|
return res.status(204).end();
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof PrismaClientKnownRequestError && e.code === "P2025") {
|
||||||
|
return res.status(404).json(generateError(`The organisation with the ID ${pid} could not be found`));
|
||||||
|
}
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
import { Admin } from "@prisma/client";
|
||||||
|
import { PrismaClientUnknownRequestError } from "@prisma/client/runtime";
|
||||||
|
import { Request, Response } from "express";
|
||||||
|
import prisma from "../lib/prisma";
|
||||||
|
import { generateError, genericError, handleCreateByName } from "./common";
|
||||||
|
|
||||||
|
const basicGroup = {
|
||||||
|
pid: true,
|
||||||
|
name: true,
|
||||||
|
organisation: { select: { pid: true, name: true } },
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const _getAllGroups = async (res: Response, organisationId: string | undefined) => {
|
||||||
|
const groups = await prisma.group.findMany({
|
||||||
|
where: { organisation: { pid: organisationId } },
|
||||||
|
select: basicGroup,
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
type: "success",
|
||||||
|
payload: {
|
||||||
|
groups,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
interface GetAllGroupsSearchParams {
|
||||||
|
organisationPid?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getAllGroups = async (req: Request<{}, {}, {}, GetAllGroupsSearchParams>, res: Response) => {
|
||||||
|
return _getAllGroups(res, req.query.organisationPid);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface getAllGroupsWithParamQueryParams {
|
||||||
|
organisationPid: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getAllGroupsWithParam = async (req: Request<getAllGroupsWithParamQueryParams>, res: Response) => {
|
||||||
|
return _getAllGroups(res, req.params.organisationPid);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface GetGroupQueryParams {
|
||||||
|
pid: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getGroup = async (req: Request<GetGroupQueryParams>, res: Response) => {
|
||||||
|
const { pid } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const group: {
|
||||||
|
pid: string;
|
||||||
|
name: string;
|
||||||
|
organisation: { pid: string; name: string };
|
||||||
|
admins?: { pid: string; name: string }[];
|
||||||
|
participants?: { pid: string }[];
|
||||||
|
} | null = await prisma.group.findUnique({
|
||||||
|
where: { pid },
|
||||||
|
select: req.auth?.isAuthenticated
|
||||||
|
? {
|
||||||
|
pid: true,
|
||||||
|
name: true,
|
||||||
|
organisation: { select: { pid: true, name: true } },
|
||||||
|
admins: { select: { pid: true, name: true } },
|
||||||
|
participants: { select: { pid: true } },
|
||||||
|
}
|
||||||
|
: basicGroup,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!group) {
|
||||||
|
return res.status(404).json(generateError(`The group with ID '${pid}' could not be found`));
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
type: "success",
|
||||||
|
payload: {
|
||||||
|
group: {
|
||||||
|
...group,
|
||||||
|
organisation: {
|
||||||
|
...group.organisation,
|
||||||
|
_links: [{ rel: "self", type: "GET", href: `/api/organisation/${group.organisation.pid}` }],
|
||||||
|
},
|
||||||
|
...(req.auth?.isAuthenticated
|
||||||
|
? {
|
||||||
|
admins: group.admins?.map((admin) => ({
|
||||||
|
...admin,
|
||||||
|
_links: [{ rel: "self", type: "GET", href: `/api/admins/${admin.pid}` }],
|
||||||
|
})),
|
||||||
|
participants: group.participants?.map((participant) => ({
|
||||||
|
...participant,
|
||||||
|
_links: [{ rel: "self", type: "GET", href: `/api/participant/${participant.pid}` }],
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof PrismaClientUnknownRequestError) {
|
||||||
|
return res.status(400).json(generateError("Unknown error occured. This could be due to malformed IDs"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(500).json(genericError);
|
||||||
|
};
|
||||||
|
|
||||||
|
// at: POST /api/organisations/:eventPid/groups
|
||||||
|
// requires: auth(ELEVATED)
|
||||||
|
export const createGroup = async (req: Request<{ organisationPid: string }, {}, { name?: string }>, res: Response) => {
|
||||||
|
return handleCreateByName(
|
||||||
|
{ type: "group", data: { name: req.body.name, level: 1 } },
|
||||||
|
{ type: "organisation", id: req.params.organisationPid },
|
||||||
|
req,
|
||||||
|
res
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,233 @@
|
|||||||
|
import { PrismaClientUnknownRequestError } from "@prisma/client/runtime";
|
||||||
|
import { Request, Response } from "express";
|
||||||
|
import prisma from "../lib/prisma";
|
||||||
|
import {
|
||||||
|
AUTH_ERROR,
|
||||||
|
createInsufficientPermissionsError,
|
||||||
|
DataType,
|
||||||
|
generateError,
|
||||||
|
generateInvalidBodyError,
|
||||||
|
genericError,
|
||||||
|
handleCreateByName,
|
||||||
|
} from "./common";
|
||||||
|
import { PrismaClientKnownRequestError } from "@prisma/client/runtime";
|
||||||
|
import { Prisma } from "@prisma/client";
|
||||||
|
|
||||||
|
function validateOranisationName(name: string) {
|
||||||
|
return name.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const detailedOrganisation = {
|
||||||
|
pid: true,
|
||||||
|
name: true,
|
||||||
|
event: {
|
||||||
|
select: {
|
||||||
|
pid: true,
|
||||||
|
name: true,
|
||||||
|
date: true,
|
||||||
|
description: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
const basicOrganisation = {
|
||||||
|
pid: true,
|
||||||
|
name: true,
|
||||||
|
event: {
|
||||||
|
select: {
|
||||||
|
pid: true,
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const _getAllOrganisations = async (res: Response, eventId: string | undefined = undefined) => {
|
||||||
|
const organisations = await prisma.organisation.findMany({
|
||||||
|
where: { event: { pid: eventId } },
|
||||||
|
select: basicOrganisation,
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
type: "success",
|
||||||
|
payload: {
|
||||||
|
organisations,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
interface GetAllOrganisationsSearchParams {
|
||||||
|
eventId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getAllOrganisations = async (req: Request<{}, {}, {}, GetAllOrganisationsSearchParams>, res: Response) => {
|
||||||
|
// TODO: Maybe rename to eventPid
|
||||||
|
return _getAllOrganisations(res, req.query.eventId);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface GetAllOrganisationsWithParamQueryParams {
|
||||||
|
eventPid: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getAllOrganisationsWithParam = async (
|
||||||
|
req: Request<GetAllOrganisationsWithParamQueryParams>,
|
||||||
|
res: Response
|
||||||
|
) => {
|
||||||
|
return _getAllOrganisations(res, req.params.eventPid);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface GetOrganisationQueryParams {
|
||||||
|
pid: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getOrganisation = async (req: Request<GetOrganisationQueryParams>, res: Response) => {
|
||||||
|
const { pid } = req.params;
|
||||||
|
|
||||||
|
const organisation = await prisma.organisation.findUnique({
|
||||||
|
where: { pid },
|
||||||
|
select: detailedOrganisation,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!organisation) {
|
||||||
|
return res.status(404).json({
|
||||||
|
type: "error",
|
||||||
|
payload: {
|
||||||
|
message: `The organisation with ID '${pid} could not be found'`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
type: "success",
|
||||||
|
payload: {
|
||||||
|
organisation: {
|
||||||
|
...organisation,
|
||||||
|
event: {
|
||||||
|
...organisation.event,
|
||||||
|
_links: [{ rel: "self", type: "GET", href: `/api/event/${organisation.event.pid}` }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
interface CreateOrganisationQueryParams {
|
||||||
|
eventPid: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CreateOrganisationBody {
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// at: POST /api/events/:eventPid/organisations
|
||||||
|
// requires: auth(ELEVATED)
|
||||||
|
export const createOrganisation = async (
|
||||||
|
req: Request<CreateOrganisationQueryParams, {}, CreateOrganisationBody>,
|
||||||
|
res: Response
|
||||||
|
) => {
|
||||||
|
return handleCreateByName(
|
||||||
|
{ type: "organisation", data: { name: req.body.name } },
|
||||||
|
{ type: "event", id: req.params.eventPid },
|
||||||
|
req,
|
||||||
|
res
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface UpdateOrganisationQueryParams {
|
||||||
|
pid: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateOrganisationBody {
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// requires: auth(ELEVATED)
|
||||||
|
export const updateOrganisation = async (
|
||||||
|
req: Request<UpdateOrganisationQueryParams, {}, UpdateOrganisationBody>,
|
||||||
|
res: Response
|
||||||
|
) => {
|
||||||
|
if (!req.auth?.isAuthenticated) {
|
||||||
|
return res.status(500).json(AUTH_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.auth.permission_level !== "ELEVATED") {
|
||||||
|
return res.status(403).json(createInsufficientPermissionsError());
|
||||||
|
}
|
||||||
|
|
||||||
|
const { pid } = req.params;
|
||||||
|
const { name } = req.body;
|
||||||
|
|
||||||
|
if (name !== undefined && typeof name !== "string") {
|
||||||
|
return res.status(400).json(
|
||||||
|
generateInvalidBodyError({
|
||||||
|
name: DataType.STRING,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name !== undefined && !validateOranisationName(name)) {
|
||||||
|
return res.status(400).json({
|
||||||
|
type: "error",
|
||||||
|
payload: {
|
||||||
|
message: "The name has to be at least 1 character long",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const organisation = await prisma.organisation.update({
|
||||||
|
where: { pid },
|
||||||
|
data: { name },
|
||||||
|
select: detailedOrganisation,
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
type: "success",
|
||||||
|
payload: {
|
||||||
|
organisation,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof PrismaClientKnownRequestError) {
|
||||||
|
if (e.code === "P2025") {
|
||||||
|
return res.status(404).json(generateError(`The organisation with the ID ${pid} could not be found`));
|
||||||
|
}
|
||||||
|
} else if (e instanceof PrismaClientUnknownRequestError) {
|
||||||
|
return res.status(400).send(generateError("Unkonwn error occured. This could be due to malformed IDs"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(500).json(genericError);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface DeleteOrganisationQueryParams {
|
||||||
|
pid: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// requires: auth(ELEVATED)
|
||||||
|
export const deleteOrganisation = async (req: Request<DeleteOrganisationQueryParams>, res: Response) => {
|
||||||
|
if (!req.auth?.isAuthenticated) {
|
||||||
|
return res.status(500).json(AUTH_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.auth.permission_level !== "ELEVATED") {
|
||||||
|
return res.status(403).json(createInsufficientPermissionsError());
|
||||||
|
}
|
||||||
|
|
||||||
|
const { pid } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
prisma.organisation.delete({ where: { pid } });
|
||||||
|
|
||||||
|
res.status(204).end();
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof PrismaClientKnownRequestError) {
|
||||||
|
if ((e.code = "P2025")) {
|
||||||
|
return res.status(404).json(generateError(`The organisation with the ID ${pid} could not be found`));
|
||||||
|
}
|
||||||
|
} else if (e instanceof PrismaClientUnknownRequestError) {
|
||||||
|
return res.status(400).send(generateError("Unkonwn error occured. This could be due to malformed IDs"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(500).json(genericError);
|
||||||
|
};
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import express from "express";
|
||||||
|
import { createGroup, getAllGroups, getAllGroupsWithParam, getGroup } from "../Controllers/group.controllers";
|
||||||
|
import { requireAuthentication } from "../Middleware/auth/auth";
|
||||||
|
import organisationRouter from "./organisation.routes";
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.get("/", getAllGroups);
|
||||||
|
router.get("/:pid", getGroup);
|
||||||
|
|
||||||
|
organisationRouter.get("/:organisationPid/groups", getAllGroupsWithParam);
|
||||||
|
organisationRouter.post("/:organisationPid/groups", requireAuthentication, createGroup);
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import express from "express";
|
||||||
|
import eventRouter from "./event.routes";
|
||||||
|
import {
|
||||||
|
createOrganisation,
|
||||||
|
getAllOrganisations,
|
||||||
|
getAllOrganisationsWithParam,
|
||||||
|
getOrganisation,
|
||||||
|
updateOrganisation,
|
||||||
|
} from "../Controllers/organisation.controller";
|
||||||
|
import { requireAuthentication } from "../Middleware/auth/auth";
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.get("/", getAllOrganisations);
|
||||||
|
router.get("/:pid", getOrganisation);
|
||||||
|
router.put<"/:pid", { pid: string }>("/:pid", requireAuthentication, updateOrganisation);
|
||||||
|
|
||||||
|
eventRouter.get("/:eventPid/organisations", getAllOrganisationsWithParam);
|
||||||
|
eventRouter.post<"/:eventPid/organisations", { eventPid: string }>(
|
||||||
|
"/:eventPid/organisations",
|
||||||
|
requireAuthentication,
|
||||||
|
createOrganisation
|
||||||
|
);
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -4,6 +4,9 @@ import eventRouter from "./Routes/event.routes";
|
|||||||
import adminAuthRouter from "./Routes/admin_auth.routes";
|
import adminAuthRouter from "./Routes/admin_auth.routes";
|
||||||
import argon2 from "argon2";
|
import argon2 from "argon2";
|
||||||
import adminRouter from "./Routes/admin.routes";
|
import adminRouter from "./Routes/admin.routes";
|
||||||
|
import organisationRouter from "./Routes/organisation.routes";
|
||||||
|
import groupRouter from "./Routes/group.routes";
|
||||||
|
|
||||||
|
|
||||||
require("dotenv").config(); // Load dotenv config
|
require("dotenv").config(); // Load dotenv config
|
||||||
|
|
||||||
@@ -46,6 +49,9 @@ async function main() {
|
|||||||
|
|
||||||
app.use("/api/admins", adminRouter);
|
app.use("/api/admins", adminRouter);
|
||||||
|
|
||||||
|
app.use("/api/organisations", organisationRouter);
|
||||||
|
|
||||||
|
app.use("/api/groups", groupRouter);
|
||||||
app.listen(process.env.PORT, () => {
|
app.listen(process.env.PORT, () => {
|
||||||
console.log(`Listening on Port: ${process.env.PORT}`);
|
console.log(`Listening on Port: ${process.env.PORT}`);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user