diff --git a/.vscode/settings.json b/.vscode/settings.json index f77971ec..a90f03e6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -34,5 +34,6 @@ "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true }, - "typescript.tsdk": "node_modules/typescript/lib" + "typescript.tsdk": "node_modules/typescript/lib", + "testing.openTesting": "neverOpen" } diff --git a/models/authResult.ts b/models/authResult.ts new file mode 100644 index 00000000..2c3a89f7 --- /dev/null +++ b/models/authResult.ts @@ -0,0 +1,7 @@ +export interface AuthResult { + success: boolean; + authToken?: string; + id?: number; + statusCode: number; + errorMessage?: string; +} diff --git a/models/user.ts b/models/user.ts index 6ef6fa07..2626b3fc 100644 --- a/models/user.ts +++ b/models/user.ts @@ -1,7 +1,8 @@ export interface User { - id?: number; - email: string; - firstName: string; - lastName: string; - role?: string; -} \ No newline at end of file + id?: number; + email: string; + firstName?: string; + lastName?: string; + role?: string; + orgId?: number; +} diff --git a/pages/api/admin/v0.1/app/index.ts b/pages/api/admin/v0.1/app/index.ts index 75157c90..00c74a63 100644 --- a/pages/api/admin/v0.1/app/index.ts +++ b/pages/api/admin/v0.1/app/index.ts @@ -4,12 +4,15 @@ import { validate } from "class-validator"; import { StatusCodes } from "http-status-codes"; import type { NextApiRequest, NextApiResponse } from "next"; import prisma from "../../../../../lib/services/db"; +import { AuthResult } from "../../../../../models/authResult"; import { CreateAppDto } from "../../../../../models/dtos/request/createAppDto"; import { AppDto } from "../../../../../models/dtos/response/appDto"; import { MessageDto } from "../../../../../models/dtos/response/messageDto"; import { authenticate } from "../../../../../util/adminApi/auth"; import { Logger } from "../../../../../util/logger"; +const logger = new Logger(__filename); + /** * @swagger * tags: @@ -115,8 +118,6 @@ export default async function handler( req: NextApiRequest, res: NextApiResponse ) { - const logger = new Logger(__filename); - const authResult = await authenticate(req, "app"); // When authResult was not successful, return error with respective @@ -130,107 +131,121 @@ export default async function handler( // Find app by token // If found, return app data with message case "GET": - logger.log(`Looking up app with id(='${authResult.id})'`); - - const app = await prisma.app.findUnique({ - where: { - id: authResult.id, - }, - include: { - messages: true, - }, - }); - - if (app == null) { - logger.error(`No app found with id '${authResult.id}'`); - return res - .status(StatusCodes.NOT_FOUND) - .json(getErrorDto(`No app found with id '${authResult.id}'`)); - } - - const convertedMessages: MessageDto[] = app.messages.map( - (message): MessageDto => ({ - id: message.id, - createdAt: message.createdAt, - updatedAt: message.updatedAt, - blocking: message.blocking, - title: message.title, - body: message.body, - endDate: message.endDate, - startDate: message.startDate, - }) - ); - const foundAppDto: AppDto = { - id: app.id, - createdAt: app.createdAt, - updatedAt: app.updatedAt, - name: app.name, - publicKey: app.publicKey, - messages: convertedMessages, - }; - - return res.status(StatusCodes.OK).json(foundAppDto); - + return getHandler(req, res, authResult); // Update app case "PUT": - const updateAppDto = plainToInstance(CreateAppDto, req.body); - const validationErrors = await validate(updateAppDto); - - if (validationErrors.length > 0) { - const errors = validationErrors - .flatMap((error) => - error.constraints - ? Object.values(error.constraints) - : ["An unknown error occurred"] - ) - .join(", "); - return res - .status(StatusCodes.BAD_REQUEST) - .json(getErrorDto(`Validation failed: ${errors}`)); - } - - try { - logger.log(`Updating app with id(='${authResult.id})'`); - - const updatedApp = await prisma.app.update({ - where: { - id: authResult.id, - }, - data: { - name: updateAppDto.name, - }, - }); - - const dto: AppDto = { - id: updatedApp.id, - createdAt: updatedApp.createdAt, - updatedAt: updatedApp.updatedAt, - name: updatedApp.name, - publicKey: updatedApp.publicKey, - }; - - return res.status(StatusCodes.CREATED).json(dto); - } catch (e) { - if (e instanceof Prisma.PrismaClientKnownRequestError) { - logger.error(`No app found with id '${authResult.id}'`); - return res - .status(StatusCodes.NOT_FOUND) - .json(getErrorDto("No app found with id " + authResult.id)); - } - - logger.error(`Internal server error occurred: ${e}`); - return res - .status(StatusCodes.INTERNAL_SERVER_ERROR) - .json( - getErrorDto( - "An internal server error occurred - please try again later!" - ) - ); - } - + return putHandler(req, res, authResult); default: return res .status(StatusCodes.METHOD_NOT_ALLOWED) .json(getErrorDto("method not allowed")); } } + +async function getHandler( + req: NextApiRequest, + res: NextApiResponse, + authResult: AuthResult +) { + logger.log(`Looking up app with id(='${authResult.id})'`); + + const app = await prisma.app.findUnique({ + where: { + id: authResult.id, + }, + include: { + messages: true, + }, + }); + + if (app == null) { + logger.error(`No app found with id '${authResult.id}'`); + return res + .status(StatusCodes.NOT_FOUND) + .json(getErrorDto(`No app found with id '${authResult.id}'`)); + } + + const convertedMessages: MessageDto[] = app.messages.map( + (message): MessageDto => ({ + id: message.id, + createdAt: message.createdAt, + updatedAt: message.updatedAt, + blocking: message.blocking, + title: message.title, + body: message.body, + endDate: message.endDate, + startDate: message.startDate, + }) + ); + const foundAppDto: AppDto = { + id: app.id, + createdAt: app.createdAt, + updatedAt: app.updatedAt, + name: app.name, + publicKey: app.publicKey, + messages: convertedMessages, + }; + + return res.status(StatusCodes.OK).json(foundAppDto); +} + +async function putHandler( + req: NextApiRequest, + res: NextApiResponse, + authResult: AuthResult +) { + const updateAppDto = plainToInstance(CreateAppDto, req.body); + const validationErrors = await validate(updateAppDto); + + if (validationErrors.length > 0) { + const errors = validationErrors + .flatMap((error) => + error.constraints + ? Object.values(error.constraints) + : ["An unknown error occurred"] + ) + .join(", "); + return res + .status(StatusCodes.BAD_REQUEST) + .json(getErrorDto(`Validation failed: ${errors}`)); + } + + try { + logger.log(`Updating app with id(='${authResult.id})'`); + + const updatedApp = await prisma.app.update({ + where: { + id: authResult.id, + }, + data: { + name: updateAppDto.name, + }, + }); + + const dto: AppDto = { + id: updatedApp.id, + createdAt: updatedApp.createdAt, + updatedAt: updatedApp.updatedAt, + name: updatedApp.name, + publicKey: updatedApp.publicKey, + }; + + return res.status(StatusCodes.CREATED).json(dto); + } catch (e) { + if (e instanceof Prisma.PrismaClientKnownRequestError) { + logger.error(`No app found with id '${authResult.id}'`); + return res + .status(StatusCodes.NOT_FOUND) + .json(getErrorDto("No app found with id " + authResult.id)); + } + + logger.error(`Internal server error occurred: ${e}`); + return res + .status(StatusCodes.INTERNAL_SERVER_ERROR) + .json( + getErrorDto( + "An internal server error occurred - please try again later!" + ) + ); + } +} diff --git a/pages/api/admin/v0.1/app/messages/[messageId].ts b/pages/api/admin/v0.1/app/messages/[messageId].ts index 6ac9b2ec..e74dd1ee 100644 --- a/pages/api/admin/v0.1/app/messages/[messageId].ts +++ b/pages/api/admin/v0.1/app/messages/[messageId].ts @@ -4,12 +4,15 @@ import { validate } from "class-validator"; import { StatusCodes } from "http-status-codes"; import type { NextApiRequest, NextApiResponse } from "next"; import prisma from "../../../../../../lib/services/db"; +import { AuthResult } from "../../../../../../models/authResult"; import { CreateMessageDto } from "../../../../../../models/dtos/request/createMessageDto"; import { ActionDto } from "../../../../../../models/dtos/response/actionDto"; import { MessageDto } from "../../../../../../models/dtos/response/messageDto"; import { authenticate } from "../../../../../../util/adminApi/auth"; import { Logger } from "../../../../../../util/logger"; +const logger = new Logger(__filename); + /** * @swagger * tags: @@ -135,8 +138,6 @@ export default async function handler( req: NextApiRequest, res: NextApiResponse ) { - const logger = new Logger(__filename); - const authResult = await authenticate(req, "app"); // When authResult was not successful, return error with respective @@ -146,199 +147,224 @@ export default async function handler( .status(authResult.statusCode) .json(getErrorDto(authResult.errorMessage)); - const messageId = Number(req.query.messageId); - switch (req.method) { // Retrieve message case "GET": - logger.log( - `Looking up message with id '${messageId}' for app with id(=${authResult.id})` - ); - const message = await prisma.message.findUnique({ - include: { - actions: true, - }, - where: { - id: messageId, - appId: authResult.id, - }, - }); + return getHandler(req, res, authResult); + // Delete message + case "DELETE": + return deleteHandler(req, res, authResult); + // Updating message + case "PUT": + return putHandler(req, res, authResult); + default: + return res + .status(StatusCodes.METHOD_NOT_ALLOWED) + .json(getErrorDto("method not allowed")); + } +} - if (message == null) { - logger.log( - `No message found with id '${messageId}' for app with id(=${authResult.id})` - ); - return res - .status(StatusCodes.NOT_FOUND) - .json(getErrorDto(`No message found with id ${messageId}`)); - } +async function getHandler( + req: NextApiRequest, + res: NextApiResponse, + authResult: AuthResult +) { + const messageId = Number(req.query.messageId); - const convertedActions: ActionDto[] = message.actions.map( - (action): ActionDto => ({ - title: action.title, - actionType: action.actionType, - buttonDesign: action.buttonDesign, - }) - ); - const dto: MessageDto = { - id: message.id, - createdAt: message.createdAt, - updatedAt: message.updatedAt, - blocking: message.blocking, - title: message.title, - body: message.body, - startDate: message.startDate, - endDate: message.endDate, - actions: convertedActions, - }; + logger.log( + `Looking up message with id '${messageId}' for app with id(=${authResult.id})` + ); + const message = await prisma.message.findUnique({ + include: { + actions: true, + }, + where: { + id: messageId, + appId: authResult.id, + }, + }); - return res.status(StatusCodes.OK).json(dto); + if (message == null) { + logger.log( + `No message found with id '${messageId}' for app with id(=${authResult.id})` + ); + return res + .status(StatusCodes.NOT_FOUND) + .json(getErrorDto(`No message found with id ${messageId}`)); + } - // Delete message - case "DELETE": - try { - logger.log( - `Deleting message with id '${messageId}' for app with id(=${authResult.id})` - ); - const deletedMessage = await prisma.message.delete({ - where: { - id: messageId, - appId: authResult.id, - }, - }); + const convertedActions: ActionDto[] = message.actions.map( + (action): ActionDto => ({ + title: action.title, + actionType: action.actionType, + buttonDesign: action.buttonDesign, + }) + ); + const dto: MessageDto = { + id: message.id, + createdAt: message.createdAt, + updatedAt: message.updatedAt, + blocking: message.blocking, + title: message.title, + body: message.body, + startDate: message.startDate, + endDate: message.endDate, + actions: convertedActions, + }; + + return res.status(StatusCodes.OK).json(dto); +} - const dto: MessageDto = { - id: deletedMessage.id, - createdAt: deletedMessage.createdAt, - updatedAt: deletedMessage.updatedAt, - blocking: deletedMessage.blocking, - title: deletedMessage.title, - body: deletedMessage.body, - startDate: deletedMessage.startDate, - endDate: deletedMessage.endDate, - actions: [], - }; +async function deleteHandler( + req: NextApiRequest, + res: NextApiResponse, + authResult: AuthResult +) { + const messageId = Number(req.query.messageId); - return res.status(StatusCodes.OK).json(dto); - } catch (e) { - if (e instanceof Prisma.PrismaClientKnownRequestError) { - logger.error( - `No message found with id '${messageId}' for app with id(=${authResult.id})` - ); - return res - .status(StatusCodes.NOT_FOUND) - .json(getErrorDto("No message found with id " + messageId)); - } + try { + logger.log( + `Deleting message with id '${messageId}' for app with id(=${authResult.id})` + ); + const deletedMessage = await prisma.message.delete({ + where: { + id: messageId, + appId: authResult.id, + }, + }); - logger.error(`Internal server error occurred: ${e}`); - return res - .status(StatusCodes.INTERNAL_SERVER_ERROR) - .json( - getErrorDto( - "An internal server error occurred - please try again later!" - ) - ); - } + const dto: MessageDto = { + id: deletedMessage.id, + createdAt: deletedMessage.createdAt, + updatedAt: deletedMessage.updatedAt, + blocking: deletedMessage.blocking, + title: deletedMessage.title, + body: deletedMessage.body, + startDate: deletedMessage.startDate, + endDate: deletedMessage.endDate, + actions: [], + }; - // Updating message - case "PUT": - const updateMessageDto = plainToInstance(CreateMessageDto, req.body); - const validationErrors = await validate(updateMessageDto); + return res.status(StatusCodes.OK).json(dto); + } catch (e) { + if (e instanceof Prisma.PrismaClientKnownRequestError) { + logger.error( + `No message found with id '${messageId}' for app with id(=${authResult.id})` + ); + return res + .status(StatusCodes.NOT_FOUND) + .json(getErrorDto("No message found with id " + messageId)); + } - if (validationErrors.length > 0) { - const errors = validationErrors - .flatMap((error) => - error.constraints - ? Object.values(error.constraints) - : ["An unknown error occurred"] - ) - .join(", "); - return res - .status(StatusCodes.BAD_REQUEST) - .json(getErrorDto(`Validation failed: ${errors}`)); - } + logger.error(`Internal server error occurred: ${e}`); + return res + .status(StatusCodes.INTERNAL_SERVER_ERROR) + .json( + getErrorDto( + "An internal server error occurred - please try again later!" + ) + ); + } +} - try { - logger.log( - `Updating message with id '${messageId}' for app with id(=${authResult.id})` - ); - const updatedMessage = await prisma.message.update({ - where: { - id: messageId, - appId: authResult.id, - }, - data: { - blocking: updateMessageDto.blocking, - title: updateMessageDto.title, - body: updateMessageDto.body, - startDate: new Date(updateMessageDto.startDate), - endDate: new Date(updateMessageDto.endDate), - }, - }); +async function putHandler( + req: NextApiRequest, + res: NextApiResponse, + authResult: AuthResult +) { + const messageId = Number(req.query.messageId); - logger.log(`Deleting actions of message with id '${messageId}'`); - await prisma.action.deleteMany({ - where: { - messageId: messageId, - }, - }); + const updateMessageDto = plainToInstance(CreateMessageDto, req.body); + const validationErrors = await validate(updateMessageDto); - let convertedActions: ActionDto[] = []; - if (updateMessageDto.actions && updateMessageDto.actions.length > 0) { - const actions: Action[] = updateMessageDto.actions; + if (validationErrors.length > 0) { + const errors = validationErrors + .flatMap((error) => + error.constraints + ? Object.values(error.constraints) + : ["An unknown error occurred"] + ) + .join(", "); + return res + .status(StatusCodes.BAD_REQUEST) + .json(getErrorDto(`Validation failed: ${errors}`)); + } - actions.forEach((action) => { - action.messageId = messageId; - }); + try { + logger.log( + `Updating message with id '${messageId}' for app with id(=${authResult.id})` + ); + const updatedMessage = await prisma.message.update({ + where: { + id: messageId, + appId: authResult.id, + }, + data: { + blocking: updateMessageDto.blocking, + title: updateMessageDto.title, + body: updateMessageDto.body, + startDate: new Date(updateMessageDto.startDate), + endDate: new Date(updateMessageDto.endDate), + }, + }); - logger.log(`Creating actions for message with id '${messageId}'`); - await prisma.action.createMany({ - data: updateMessageDto.actions, - }); + logger.log(`Deleting actions of message with id '${messageId}'`); + await prisma.action.deleteMany({ + where: { + messageId: messageId, + }, + }); - convertedActions = actions.map( - (action): ActionDto => ({ - title: action.title, - actionType: action.actionType, - buttonDesign: action.buttonDesign, - }) - ); - } + let convertedActions: ActionDto[] = []; + if (updateMessageDto.actions && updateMessageDto.actions.length > 0) { + const actions: Action[] = updateMessageDto.actions; - const dto: MessageDto = { - id: updatedMessage.id, - createdAt: updatedMessage.createdAt, - updatedAt: updatedMessage.updatedAt, - blocking: updatedMessage.blocking, - title: updatedMessage.title, - body: updatedMessage.body, - startDate: updatedMessage.startDate, - endDate: updatedMessage.endDate, - actions: convertedActions, - }; + actions.forEach((action) => { + action.messageId = messageId; + }); - return res.status(StatusCodes.CREATED).json(dto); - } catch (e) { - if (e instanceof Prisma.PrismaClientKnownRequestError) { - logger.log(`No message found with id '${messageId}'`); - return res - .status(StatusCodes.NOT_FOUND) - .json(getErrorDto("No message found with id " + messageId)); - } + logger.log(`Creating actions for message with id '${messageId}'`); + await prisma.action.createMany({ + data: updateMessageDto.actions, + }); - logger.error(`Internal server error occurred: ${e}`); - return res - .status(StatusCodes.INTERNAL_SERVER_ERROR) - .json( - getErrorDto( - "An internal server error occurred - please try again later!" - ) - ); - } + convertedActions = actions.map( + (action): ActionDto => ({ + title: action.title, + actionType: action.actionType, + buttonDesign: action.buttonDesign, + }) + ); + } - default: + const dto: MessageDto = { + id: updatedMessage.id, + createdAt: updatedMessage.createdAt, + updatedAt: updatedMessage.updatedAt, + blocking: updatedMessage.blocking, + title: updatedMessage.title, + body: updatedMessage.body, + startDate: updatedMessage.startDate, + endDate: updatedMessage.endDate, + actions: convertedActions, + }; + + return res.status(StatusCodes.CREATED).json(dto); + } catch (e) { + if (e instanceof Prisma.PrismaClientKnownRequestError) { + logger.log(`No message found with id '${messageId}'`); return res - .status(StatusCodes.METHOD_NOT_ALLOWED) - .json(getErrorDto("method not allowed")); + .status(StatusCodes.NOT_FOUND) + .json(getErrorDto("No message found with id " + messageId)); + } + + logger.error(`Internal server error occurred: ${e}`); + return res + .status(StatusCodes.INTERNAL_SERVER_ERROR) + .json( + getErrorDto( + "An internal server error occurred - please try again later!" + ) + ); } } diff --git a/pages/api/admin/v0.1/app/messages/index.ts b/pages/api/admin/v0.1/app/messages/index.ts index a9593c3e..30b06be9 100644 --- a/pages/api/admin/v0.1/app/messages/index.ts +++ b/pages/api/admin/v0.1/app/messages/index.ts @@ -4,12 +4,15 @@ import { validate } from "class-validator"; import { StatusCodes } from "http-status-codes"; import type { NextApiRequest, NextApiResponse } from "next"; import prisma from "../../../../../../lib/services/db"; +import { AuthResult } from "../../../../../../models/authResult"; import { CreateMessageDto } from "../../../../../../models/dtos/request/createMessageDto"; import { ActionDto } from "../../../../../../models/dtos/response/actionDto"; import { MessageDto } from "../../../../../../models/dtos/response/messageDto"; import { authenticate } from "../../../../../../util/adminApi/auth"; import { Logger } from "../../../../../../util/logger"; +const logger = new Logger(__filename); + /** * @swagger * tags: @@ -125,8 +128,6 @@ export default async function handler( req: NextApiRequest, res: NextApiResponse ) { - const logger = new Logger(__filename); - const authResult = await authenticate(req, "app"); // When authResult was not successful, return error with respective @@ -139,104 +140,118 @@ export default async function handler( switch (req.method) { // Get all messages for app case "GET": - logger.log(`Getting messages for app with id(=${authResult.id})`); - const messages = await prisma.message.findMany({ - where: { - appId: authResult.id, - }, - include: { - actions: true, - }, - }); - - const messageDtos: MessageDto[] = messages.map((message) => ({ - id: message.id, - createdAt: message.createdAt, - updatedAt: message.updatedAt, - blocking: message.blocking, - title: message.title, - body: message.body, - endDate: message.endDate, - startDate: message.startDate, - actions: message.actions.map((action) => ({ - id: action.id, - title: action.title, - actionType: action.actionType, - buttonDesign: action.buttonDesign, - })), - })); - - return res.status(StatusCodes.CREATED).json(messageDtos); - + return getHandler(req, res, authResult); // Create new message case "POST": - const createMessageDto = plainToInstance(CreateMessageDto, req.body); - const validationErrors = await validate(createMessageDto); - - if (validationErrors.length > 0) { - const errors = validationErrors - .flatMap((error) => - error.constraints - ? Object.values(error.constraints) - : ["An unknown error occurred"] - ) - .join(", "); - return res - .status(StatusCodes.BAD_REQUEST) - .json(getErrorDto(`Validation failed: ${errors}`)); - } - - logger.log(`Creating message for app id '${authResult.id}'`); - const message = await prisma.message.create({ - data: { - blocking: createMessageDto.blocking, - title: createMessageDto.title, - body: createMessageDto.body, - startDate: new Date(createMessageDto.startDate), - endDate: new Date(createMessageDto.endDate), - appId: authResult.id, - }, - }); - - let convertedActions: ActionDto[] = []; - if (createMessageDto.actions && createMessageDto.actions.length > 0) { - logger.log(`Creating actions for message with id '${message.id}'`); - const actions: Action[] = createMessageDto.actions; - - actions.forEach((action) => { - action.messageId = message.id; - }); - - await prisma.action.createMany({ - data: createMessageDto.actions, - }); - - convertedActions = actions.map( - (action): ActionDto => ({ - title: action.title, - actionType: action.actionType, - buttonDesign: action.buttonDesign, - }) - ); - } - - const dto: MessageDto = { - id: message.id, - createdAt: message.createdAt, - updatedAt: message.updatedAt, - blocking: message.blocking, - title: message.title, - body: message.body, - startDate: message.startDate, - endDate: message.endDate, - actions: convertedActions, - }; - - return res.status(StatusCodes.CREATED).json(dto); - + return postHandler(req, res, authResult); default: return res .status(StatusCodes.METHOD_NOT_ALLOWED) .json(getErrorDto("method not allowed")); } } + +async function getHandler( + req: NextApiRequest, + res: NextApiResponse, + authResult: AuthResult +) { + logger.log(`Getting messages for app with id(=${authResult.id})`); + const messages = await prisma.message.findMany({ + where: { + appId: authResult.id, + }, + include: { + actions: true, + }, + }); + + const messageDtos: MessageDto[] = messages.map((message) => ({ + id: message.id, + createdAt: message.createdAt, + updatedAt: message.updatedAt, + blocking: message.blocking, + title: message.title, + body: message.body, + endDate: message.endDate, + startDate: message.startDate, + actions: message.actions.map((action) => ({ + id: action.id, + title: action.title, + actionType: action.actionType, + buttonDesign: action.buttonDesign, + })), + })); + + return res.status(StatusCodes.CREATED).json(messageDtos); +} + +async function postHandler( + req: NextApiRequest, + res: NextApiResponse, + authResult: AuthResult +) { + const createMessageDto = plainToInstance(CreateMessageDto, req.body); + const validationErrors = await validate(createMessageDto); + + if (validationErrors.length > 0) { + const errors = validationErrors + .flatMap((error) => + error.constraints + ? Object.values(error.constraints) + : ["An unknown error occurred"] + ) + .join(", "); + return res + .status(StatusCodes.BAD_REQUEST) + .json(getErrorDto(`Validation failed: ${errors}`)); + } + + logger.log(`Creating message for app id '${authResult.id}'`); + const message = await prisma.message.create({ + data: { + blocking: createMessageDto.blocking, + title: createMessageDto.title, + body: createMessageDto.body, + startDate: new Date(createMessageDto.startDate), + endDate: new Date(createMessageDto.endDate), + appId: authResult.id, + }, + }); + + let convertedActions: ActionDto[] = []; + if (createMessageDto.actions && createMessageDto.actions.length > 0) { + logger.log(`Creating actions for message with id '${message.id}'`); + const actions: Action[] = createMessageDto.actions; + + actions.forEach((action) => { + action.messageId = message.id; + }); + + await prisma.action.createMany({ + data: createMessageDto.actions, + }); + + convertedActions = actions.map( + (action): ActionDto => ({ + title: action.title, + actionType: action.actionType, + buttonDesign: action.buttonDesign, + }) + ); + } + + const dto: MessageDto = { + id: message.id, + createdAt: message.createdAt, + updatedAt: message.updatedAt, + blocking: message.blocking, + title: message.title, + body: message.body, + startDate: message.startDate, + endDate: message.endDate, + actions: convertedActions, + }; + + return res.status(StatusCodes.CREATED).json(dto); +} diff --git a/pages/api/admin/v0.1/org/[appId]/token.ts b/pages/api/admin/v0.1/org/[appId]/token.ts index a970534a..39106b8d 100644 --- a/pages/api/admin/v0.1/org/[appId]/token.ts +++ b/pages/api/admin/v0.1/org/[appId]/token.ts @@ -1,12 +1,15 @@ import { StatusCodes } from "http-status-codes"; import type { NextApiRequest, NextApiResponse } from "next"; import prisma from "../../../../../../lib/services/db"; +import { AuthResult } from "../../../../../../models/authResult"; import { AppAdminTokenDto } from "../../../../../../models/dtos/response/appAdminTokenDto"; import { authenticate } from "../../../../../../util/adminApi/auth"; import { encodeAppToken } from "../../../../../../util/adminApi/tokenEncoding"; import { generateToken } from "../../../../../../util/auth"; import { Logger } from "../../../../../../util/logger"; +const logger = new Logger(__filename); + /** * @swagger * tags: @@ -70,8 +73,6 @@ export default async function handler( req: NextApiRequest, res: NextApiResponse ) { - const logger = new Logger(__filename); - const authResult = await authenticate(req, "org"); // When authResult was not successful, return error with respective @@ -81,6 +82,22 @@ export default async function handler( .status(authResult.statusCode) .json(getErrorDto(authResult.errorMessage)); + switch (req.method) { + // Create new magic AppAdminToken for app + case "POST": + return postHandler(req, res, authResult); + default: + return res + .status(StatusCodes.METHOD_NOT_ALLOWED) + .json(getErrorDto("method not allowed")); + } +} + +async function postHandler( + req: NextApiRequest, + res: NextApiResponse, + authResult: AuthResult +) { const appId = Number(req.query.appId); if (!appId) { @@ -90,62 +107,51 @@ export default async function handler( .json(getErrorDto(`No app id provided!`)); } - switch (req.method) { - // Create new magic AppAdminToken for app - case "POST": - // Check whether the app is part of the organisation - const appFromDb = await prisma.app.findFirst({ - where: { - id: appId, - orgId: authResult.id, - }, - }); + // Check whether the app is part of the organisation + const appFromDb = await prisma.app.findFirst({ + where: { + id: appId, + orgId: authResult.id, + }, + }); - if (!appFromDb) { - logger.error( + if (!appFromDb) { + logger.error( + `No app with id(=${appId}) found for org with id(=${authResult.id}!` + ); + return res + .status(StatusCodes.NOT_FOUND) + .json( + getErrorDto( `No app with id(=${appId}) found for org with id(=${authResult.id}!` - ); - return res - .status(StatusCodes.NOT_FOUND) - .json( - getErrorDto( - `No app with id(=${appId}) found for org with id(=${authResult.id}!` - ) - ); - } - - logger.log( - `Creating temporary AppAdminToken for app with id(='${appId}')` + ) ); + } - const generatedToken = generateToken(); + logger.log(`Creating temporary AppAdminToken for app with id(='${appId}')`); - const expiryDate = new Date(); - expiryDate.setMinutes(expiryDate.getMinutes() + 5); + const generatedToken = generateToken(); - const appAdminToken = await prisma.appAdminToken.create({ - data: { - token: generatedToken, - role: "TEMP", - expiryDate: expiryDate, - appId: appId, - }, - }); + const expiryDate = new Date(); + expiryDate.setMinutes(expiryDate.getMinutes() + 5); - const dto: AppAdminTokenDto = { - id: appAdminToken.id, - createdAt: appAdminToken.createdAt, - updatedAt: appAdminToken.updatedAt, - token: encodeAppToken(appAdminToken.token), - role: appAdminToken.role, - expiryDate: expiryDate, - }; + const appAdminToken = await prisma.appAdminToken.create({ + data: { + token: generatedToken, + role: "TEMP", + expiryDate: expiryDate, + appId: appId, + }, + }); - return res.status(StatusCodes.CREATED).json(dto); + const dto: AppAdminTokenDto = { + id: appAdminToken.id, + createdAt: appAdminToken.createdAt, + updatedAt: appAdminToken.updatedAt, + token: encodeAppToken(appAdminToken.token), + role: appAdminToken.role, + expiryDate: expiryDate, + }; - default: - return res - .status(StatusCodes.METHOD_NOT_ALLOWED) - .json(getErrorDto("method not allowed")); - } + return res.status(StatusCodes.CREATED).json(dto); } diff --git a/pages/api/admin/v0.1/org/app/index.ts b/pages/api/admin/v0.1/org/app/index.ts index 50e20392..a3d387e2 100644 --- a/pages/api/admin/v0.1/org/app/index.ts +++ b/pages/api/admin/v0.1/org/app/index.ts @@ -3,12 +3,15 @@ import { validate } from "class-validator"; import { StatusCodes } from "http-status-codes"; import type { NextApiRequest, NextApiResponse } from "next"; import prisma from "../../../../../../lib/services/db"; +import { AuthResult } from "../../../../../../models/authResult"; import { CreateAppDto } from "../../../../../../models/dtos/request/createAppDto"; import { AppDto } from "../../../../../../models/dtos/response/appDto"; import { authenticate } from "../../../../../../util/adminApi/auth"; import { generateToken } from "../../../../../../util/auth"; import { Logger } from "../../../../../../util/logger"; +const logger = new Logger(__filename); + /** * @swagger * tags: @@ -69,8 +72,6 @@ export default async function handler( req: NextApiRequest, res: NextApiResponse ) { - const logger = new Logger(__filename); - const authResult = await authenticate(req, "org"); // When authResult was not successful, return error with respective @@ -83,49 +84,56 @@ export default async function handler( switch (req.method) { // Create new app case "POST": - const createAppDto = plainToInstance(CreateAppDto, req.body); - const validationErrors = await validate(createAppDto); + return postHandler(req, res, authResult); + default: + return res + .status(StatusCodes.METHOD_NOT_ALLOWED) + .json(getErrorDto("method not allowed")); + } +} - if (validationErrors.length > 0) { - const errors = validationErrors - .flatMap((error) => - error.constraints - ? Object.values(error.constraints) - : ["An unknown error occurred"] - ) - .join(", "); - return res - .status(StatusCodes.BAD_REQUEST) - .json(getErrorDto(`Validation failed: ${errors}`)); - } +async function postHandler( + req: NextApiRequest, + res: NextApiResponse, + authResult: AuthResult +) { + const createAppDto = plainToInstance(CreateAppDto, req.body); + const validationErrors = await validate(createAppDto); - const name = createAppDto.name; + if (validationErrors.length > 0) { + const errors = validationErrors + .flatMap((error) => + error.constraints + ? Object.values(error.constraints) + : ["An unknown error occurred"] + ) + .join(", "); + return res + .status(StatusCodes.BAD_REQUEST) + .json(getErrorDto(`Validation failed: ${errors}`)); + } - logger.log(`Creating app with name(='${name}'`); + const name = createAppDto.name; - const generatedToken = generateToken(); + logger.log(`Creating app with name(='${name}'`); - const newApp = await prisma.app.create({ - data: { - name: name, - orgId: authResult.id, - publicKey: generatedToken, - }, - }); + const generatedToken = generateToken(); - const dto: AppDto = { - id: newApp.id, - createdAt: newApp.createdAt, - updatedAt: newApp.updatedAt, - name: newApp.name, - publicKey: newApp.publicKey, - }; + const newApp = await prisma.app.create({ + data: { + name: name, + orgId: authResult.id, + publicKey: generatedToken, + }, + }); - return res.status(StatusCodes.CREATED).json(dto); + const dto: AppDto = { + id: newApp.id, + createdAt: newApp.createdAt, + updatedAt: newApp.updatedAt, + name: newApp.name, + publicKey: newApp.publicKey, + }; - default: - return res - .status(StatusCodes.METHOD_NOT_ALLOWED) - .json(getErrorDto("method not allowed")); - } + return res.status(StatusCodes.CREATED).json(dto); } diff --git a/pages/api/admin/v0.1/org/index.ts b/pages/api/admin/v0.1/org/index.ts index ad6d95b9..5ce90427 100644 --- a/pages/api/admin/v0.1/org/index.ts +++ b/pages/api/admin/v0.1/org/index.ts @@ -1,11 +1,14 @@ import { StatusCodes } from "http-status-codes"; import type { NextApiRequest, NextApiResponse } from "next"; import prisma from "../../../../../lib/services/db"; +import { AuthResult } from "../../../../../models/authResult"; import { AppDto } from "../../../../../models/dtos/response/appDto"; import { OrgDto } from "../../../../../models/dtos/response/orgDto"; import { authenticate } from "../../../../../util/adminApi/auth"; import { Logger } from "../../../../../util/logger"; +const logger = new Logger(__filename); + /** * @swagger * tags: @@ -71,8 +74,6 @@ export default async function handler( req: NextApiRequest, res: NextApiResponse ) { - const logger = new Logger(__filename); - const authResult = await authenticate(req, "org"); // When authResult was not successful, return error with respective @@ -86,49 +87,54 @@ export default async function handler( // Find org (including apps) by token // If found, return org data with apps case "GET": - logger.log(`Looking up organisation with id(='${authResult.id}')`); - - const org = await prisma.organisation.findUnique({ - where: { - id: authResult.id, - isDeleted: false, - }, - include: { - apps: true, - }, - }); - - if (org == null) { - logger.error(`No organisation found with id '${authResult.id}'`); - return res - .status(StatusCodes.NOT_FOUND) - .json( - getErrorDto(`No organisation found with id '${authResult.id}'`) - ); - } - - const convertedApps: AppDto[] = org.apps.map( - (app): AppDto => ({ - id: app.id, - createdAt: app.createdAt, - updatedAt: app.updatedAt, - name: app.name, - publicKey: app.publicKey, - }) - ); - const dto: OrgDto = { - id: org.id, - createdAt: org.createdAt, - updatedAt: org.updatedAt, - name: org.name, - apps: convertedApps, - }; - - return res.status(StatusCodes.OK).json(dto); - + return getHandler(req, res, authResult); default: return res .status(StatusCodes.METHOD_NOT_ALLOWED) .json(getErrorDto("method not allowed")); } } + +async function getHandler( + req: NextApiRequest, + res: NextApiResponse, + authResult: AuthResult +) { + logger.log(`Looking up organisation with id(='${authResult.id}')`); + + const org = await prisma.organisation.findUnique({ + where: { + id: authResult.id, + isDeleted: false, + }, + include: { + apps: true, + }, + }); + + if (org == null) { + logger.error(`No organisation found with id '${authResult.id}'`); + return res + .status(StatusCodes.NOT_FOUND) + .json(getErrorDto(`No organisation found with id '${authResult.id}'`)); + } + + const convertedApps: AppDto[] = org.apps.map( + (app): AppDto => ({ + id: app.id, + createdAt: app.createdAt, + updatedAt: app.updatedAt, + name: app.name, + publicKey: app.publicKey, + }) + ); + const dto: OrgDto = { + id: org.id, + createdAt: org.createdAt, + updatedAt: org.updatedAt, + name: org.name, + apps: convertedApps, + }; + + return res.status(StatusCodes.OK).json(dto); +} diff --git a/pages/api/frontend/v0.1/dashboard/index.ts b/pages/api/frontend/v0.1/dashboard/index.ts index e13f9f10..da5d6051 100644 --- a/pages/api/frontend/v0.1/dashboard/index.ts +++ b/pages/api/frontend/v0.1/dashboard/index.ts @@ -1,7 +1,8 @@ import { StatusCodes } from "http-status-codes"; import type { NextApiRequest, NextApiResponse } from "next"; import prisma from "../../../../../lib/services/db"; -import { getUserWithRoleFromRequest } from "../../../../../util/auth"; +import { User } from "../../../../../models/user"; +import { authenticatedHandler } from "../../../../../util/authenticatedHandler"; import { Logger } from "../../../../../util/logger"; type QueryResult = { @@ -9,92 +10,100 @@ type QueryResult = { count: number | bigint; }[]; +const logger = new Logger(__filename); + export default async function handler( req: NextApiRequest, - res: NextApiResponse + res: NextApiResponse, + user: User ) { - const logger = new Logger(__filename); - - const user = await getUserWithRoleFromRequest(req, res); + return authenticatedHandler( + req, + res, + { method: "withRole" }, + async (req, res, user) => { + if (user.role !== "ADMIN") { + logger.log( + `User with id '${user.id}' tried to access dashboard data without sufficient rights` + ); + return res.status(StatusCodes.FORBIDDEN).json({ + message: "You do not have the rights to access this information", + }); + } - if (!user) { - return; - } else if (user.role !== "ADMIN") { - logger.log( - `User with id '${user.id}' tried to access dashboard data without sufficient rights` - ); - return res - .status(StatusCodes.FORBIDDEN) - .json({ - message: "You do not have the rights to access this information", - }); - } + switch (req.method) { + case "GET": + return getHandler(req, res, user); - switch (req.method) { - case "GET": - try { - const orgId: number = Number(req.query.orgId); - const appId = !!req.query.appId ? Number(req.query.appId) : undefined; + default: + return res + .status(StatusCodes.METHOD_NOT_ALLOWED) + .json({ message: "method not allowed" }); + } + } + ); +} - logger.log(`Retrieving dashboard data for org with id '${orgId}'`); +async function getHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User +) { + try { + const orgId: number = Number(req.query.orgId); + const appId = !!req.query.appId ? Number(req.query.appId) : undefined; - // Since prisma does not support direct date aggregation, the - // grouping by the day is done via queryRaw, which uses - // prisma's "tagged template" that should be reducing the risk - // of SQL injections - // Get the count of loggedApiRequests grouped by day for the last 31 days - let result: QueryResult; - if (appId != undefined) { - result = await prisma.$queryRaw` - WITH date_series AS ( - SELECT generate_series(CURRENT_DATE - INTERVAL '31 days', CURRENT_DATE, INTERVAL '1 day')::DATE AS date - ) - SELECT ds.date, COALESCE(count(lar."createdAt"), 0) AS count - FROM date_series ds - LEFT JOIN "LoggedApiRequests" lar ON DATE(lar."createdAt") = ds.date AND lar."appId" IN ( - SELECT "id" FROM "App" WHERE "orgId" = ${orgId}::integer AND "appId" = ${appId}::integer - ) - GROUP BY ds.date - ORDER BY ds.date DESC; - `; - } else { - result = await prisma.$queryRaw` - WITH date_series AS ( - SELECT generate_series(CURRENT_DATE - INTERVAL '31 days', CURRENT_DATE, INTERVAL '1 day')::DATE AS date - ) - SELECT ds.date, COALESCE(count(lar."createdAt"), 0) AS count - FROM date_series ds - LEFT JOIN "LoggedApiRequests" lar ON DATE(lar."createdAt") = ds.date AND lar."appId" IN ( - SELECT "id" FROM "App" WHERE "orgId" = ${orgId}::integer - ) - GROUP BY ds.date - ORDER BY ds.date DESC; - `; - } + logger.log(`Retrieving dashboard data for org with id '${orgId}'`); - const serializedResult = result.map((entry) => ({ - ...entry, - count: - typeof entry.count === "bigint" - ? entry.count.toString() - : entry.count, - })); + // Since prisma does not support direct date aggregation, the + // grouping by the day is done via queryRaw, which uses + // prisma's "tagged template" that should be reducing the risk + // of SQL injections + // Get the count of loggedApiRequests grouped by day for the last 31 days + let result: QueryResult; + if (appId != undefined) { + result = await prisma.$queryRaw` + WITH date_series AS ( + SELECT generate_series(CURRENT_DATE - INTERVAL '31 days', CURRENT_DATE, INTERVAL '1 day')::DATE AS date + ) + SELECT ds.date, COALESCE(count(lar."createdAt"), 0) AS count + FROM date_series ds + LEFT JOIN "LoggedApiRequests" lar ON DATE(lar."createdAt") = ds.date AND lar."appId" IN ( + SELECT "id" FROM "App" WHERE "orgId" = ${orgId}::integer AND "appId" = ${appId}::integer + ) + GROUP BY ds.date + ORDER BY ds.date DESC; + `; + } else { + result = await prisma.$queryRaw` + WITH date_series AS ( + SELECT generate_series(CURRENT_DATE - INTERVAL '31 days', CURRENT_DATE, INTERVAL '1 day')::DATE AS date + ) + SELECT ds.date, COALESCE(count(lar."createdAt"), 0) AS count + FROM date_series ds + LEFT JOIN "LoggedApiRequests" lar ON DATE(lar."createdAt") = ds.date AND lar."appId" IN ( + SELECT "id" FROM "App" WHERE "orgId" = ${orgId}::integer + ) + GROUP BY ds.date + ORDER BY ds.date DESC; + `; + } - let jsonResponse: any = { - dailyCounts: serializedResult, - }; + const serializedResult = result.map((entry) => ({ + ...entry, + count: + typeof entry.count === "bigint" ? entry.count.toString() : entry.count, + })); - return res.status(StatusCodes.OK).json(jsonResponse); - } catch (error) { - logger.error(`Error: ${error}`); - return res - .status(StatusCodes.INTERNAL_SERVER_ERROR) - .json({ message: "an internal server error occurred" }); - } + let jsonResponse: any = { + dailyCounts: serializedResult, + }; - default: - return res - .status(StatusCodes.METHOD_NOT_ALLOWED) - .json({ message: "method not allowed" }); + return res.status(StatusCodes.OK).json(jsonResponse); + } catch (error) { + logger.error(`Error: ${error}`); + return res + .status(StatusCodes.INTERNAL_SERVER_ERROR) + .json({ message: "an internal server error occurred" }); } } diff --git a/pages/api/frontend/v0.1/orgs/[orgId]/admin/tokens/[tokenId].ts b/pages/api/frontend/v0.1/orgs/[orgId]/admin/tokens/[tokenId].ts index f93c32ff..5a8e634a 100644 --- a/pages/api/frontend/v0.1/orgs/[orgId]/admin/tokens/[tokenId].ts +++ b/pages/api/frontend/v0.1/orgs/[orgId]/admin/tokens/[tokenId].ts @@ -3,75 +3,85 @@ import { StatusCodes } from "http-status-codes"; import type { NextApiRequest, NextApiResponse } from "next"; import prisma from "../../../../../../../../lib/services/db"; import { OrgAdminTokenDto } from "../../../../../../../../models/dtos/response/orgAdminTokenDto"; +import { User } from "../../../../../../../../models/user"; import { encodeOrgToken } from "../../../../../../../../util/adminApi/tokenEncoding"; -import { getUserWithRoleFromRequest } from "../../../../../../../../util/auth"; +import { authenticatedHandler } from "../../../../../../../../util/authenticatedHandler"; import { Logger } from "../../../../../../../../util/logger"; +const logger = new Logger(__filename); + export default async function handler( req: NextApiRequest, - res: NextApiResponse + res: NextApiResponse, + user: User ) { - const logger = new Logger(__filename); - - const user = await getUserWithRoleFromRequest(req, res); + return authenticatedHandler( + req, + res, + { method: "withRole" }, + async (req, res, user) => { + if (user.role !== "ADMIN") { + logger.error("User has no admin rights"); + return res + .status(StatusCodes.FORBIDDEN) + .json({ message: "You are not an admin" }); + } - if (!user) { - return; - } + switch (req.method) { + case "DELETE": + return deleteHandler(req, res, user); - if (user.role !== "ADMIN") { - logger.error("User has no admin rights"); - return res - .status(StatusCodes.FORBIDDEN) - .json({ message: "You are not an admin" }); - } + default: + return res + .status(StatusCodes.METHOD_NOT_ALLOWED) + .json({ message: "method not allowed" }); + } + } + ); +} +async function deleteHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User +) { const orgId = Number(req.query.orgId); const tokenId = Number(req.query.tokenId); - switch (req.method) { - case "DELETE": - try { - logger.log(`Deleting organisation admin token for org id '${orgId}'`); - const orgAdminToken = await prisma.organisationAdminToken.update({ - where: { - id: tokenId, - orgId: orgId, - isDeleted: false, - }, - data: { - isDeleted: true, - }, - }); + try { + logger.log(`Deleting organisation admin token for org id '${orgId}'`); + const orgAdminToken = await prisma.organisationAdminToken.update({ + where: { + id: tokenId, + orgId: orgId, + isDeleted: false, + }, + data: { + isDeleted: true, + }, + }); - const dto: OrgAdminTokenDto = { - id: orgAdminToken.id, - createdAt: orgAdminToken.createdAt, - updatedAt: orgAdminToken.updatedAt, - token: encodeOrgToken(orgAdminToken.token), - label: orgAdminToken.label, - role: orgAdminToken.role, - }; - - return res.status(StatusCodes.OK).json(dto); - } catch (e) { - if (e instanceof Prisma.PrismaClientKnownRequestError) { - logger.error(`No org admin token found with id '${tokenId}'`); - return res - .status(StatusCodes.NOT_FOUND) - .json({ message: `No org admin token found with id '${tokenId}'` }); - } + const dto: OrgAdminTokenDto = { + id: orgAdminToken.id, + createdAt: orgAdminToken.createdAt, + updatedAt: orgAdminToken.updatedAt, + token: encodeOrgToken(orgAdminToken.token), + label: orgAdminToken.label, + role: orgAdminToken.role, + }; - logger.error(`Internal server error occurred: ${e}`); - return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ - message: - "An internal server error occurred - please try again later!", - }); - } - - default: + return res.status(StatusCodes.OK).json(dto); + } catch (e) { + if (e instanceof Prisma.PrismaClientKnownRequestError) { + logger.error(`No org admin token found with id '${tokenId}'`); return res - .status(StatusCodes.METHOD_NOT_ALLOWED) - .json({ message: "method not allowed" }); + .status(StatusCodes.NOT_FOUND) + .json({ message: `No org admin token found with id '${tokenId}'` }); + } + + logger.error(`Internal server error occurred: ${e}`); + return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ + message: "An internal server error occurred - please try again later!", + }); } } diff --git a/pages/api/frontend/v0.1/orgs/[orgId]/admin/tokens/index.ts b/pages/api/frontend/v0.1/orgs/[orgId]/admin/tokens/index.ts index acbc7811..917bcfd0 100644 --- a/pages/api/frontend/v0.1/orgs/[orgId]/admin/tokens/index.ts +++ b/pages/api/frontend/v0.1/orgs/[orgId]/admin/tokens/index.ts @@ -2,85 +2,104 @@ import { StatusCodes } from "http-status-codes"; import type { NextApiRequest, NextApiResponse } from "next"; import prisma from "../../../../../../../../lib/services/db"; import { OrgAdminTokenDto } from "../../../../../../../../models/dtos/response/orgAdminTokenDto"; +import { User } from "../../../../../../../../models/user"; import { encodeOrgToken } from "../../../../../../../../util/adminApi/tokenEncoding"; -import { - generateToken, - getUserWithRoleFromRequest, -} from "../../../../../../../../util/auth"; +import { generateToken } from "../../../../../../../../util/auth"; +import { authenticatedHandler } from "../../../../../../../../util/authenticatedHandler"; import { Logger } from "../../../../../../../../util/logger"; +const logger = new Logger(__filename); + export default async function handler( req: NextApiRequest, - res: NextApiResponse + res: NextApiResponse, + user: User ) { - const logger = new Logger(__filename); + return authenticatedHandler( + req, + res, + { method: "withRole" }, + async (req, res, user) => { + if (user.role !== "ADMIN") { + logger.error("User has no admin rights"); + return res + .status(StatusCodes.FORBIDDEN) + .json({ message: "You are not an admin" }); + } - const user = await getUserWithRoleFromRequest(req, res); + switch (req.method) { + case "GET": + return getHandler(req, res, user); - if (!user) { - return; - } + case "POST": + return postHandler(req, res, user); - if (user.role !== "ADMIN") { - logger.error("User has no admin rights"); - return res - .status(StatusCodes.FORBIDDEN) - .json({ message: "You are not an admin" }); - } + default: + return res + .status(StatusCodes.METHOD_NOT_ALLOWED) + .json({ message: "method not allowed" }); + } + } + ); +} +async function getHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User +) { const orgId = Number(req.query.orgId); - switch (req.method) { - case "GET": - logger.log(`Looking up orgAdminTokens for org with id(=${orgId})`); - const orgAdminTokens = await prisma.organisationAdminToken.findMany({ - where: { - orgId: orgId, - isDeleted: false, - }, - }); - - return res.status(StatusCodes.OK).json( - orgAdminTokens.map((orgAdminToken): OrgAdminTokenDto => { - return { - id: orgAdminToken.id, - createdAt: orgAdminToken.createdAt, - updatedAt: orgAdminToken.updatedAt, - token: encodeOrgToken(orgAdminToken.token), - role: orgAdminToken.role, - label: orgAdminToken.label ? orgAdminToken.label : "", - }; - }) - ); - - case "POST": - const label = req.body.label; - - const generatedToken = generateToken(); - - logger.log(`Creating new organisation admin token for org id '${orgId}'`); - const orgAdminToken = await prisma.organisationAdminToken.create({ - data: { - token: generatedToken, - orgId: orgId, - label: label, - }, - }); + logger.log(`Looking up orgAdminTokens for org with id(=${orgId})`); + const orgAdminTokens = await prisma.organisationAdminToken.findMany({ + where: { + orgId: orgId, + isDeleted: false, + }, + }); - const dto: OrgAdminTokenDto = { + return res.status(StatusCodes.OK).json( + orgAdminTokens.map((orgAdminToken): OrgAdminTokenDto => { + return { id: orgAdminToken.id, createdAt: orgAdminToken.createdAt, updatedAt: orgAdminToken.updatedAt, token: encodeOrgToken(orgAdminToken.token), - label: orgAdminToken.label, role: orgAdminToken.role, + label: orgAdminToken.label ? orgAdminToken.label : "", }; + }) + ); +} + +async function postHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User +) { + const orgId = Number(req.query.orgId); + + const label = req.body.label; + + const generatedToken = generateToken(); + + logger.log(`Creating new organisation admin token for org id '${orgId}'`); + const orgAdminToken = await prisma.organisationAdminToken.create({ + data: { + token: generatedToken, + orgId: orgId, + label: label, + }, + }); - return res.status(StatusCodes.CREATED).json(dto); + const dto: OrgAdminTokenDto = { + id: orgAdminToken.id, + createdAt: orgAdminToken.createdAt, + updatedAt: orgAdminToken.updatedAt, + token: encodeOrgToken(orgAdminToken.token), + label: orgAdminToken.label, + role: orgAdminToken.role, + }; - default: - return res - .status(StatusCodes.METHOD_NOT_ALLOWED) - .json({ message: "method not allowed" }); - } + return res.status(StatusCodes.CREATED).json(dto); } diff --git a/pages/api/frontend/v0.1/orgs/[orgId]/apps/[appId]/admin/tokens/[tokenId].ts b/pages/api/frontend/v0.1/orgs/[orgId]/apps/[appId]/admin/tokens/[tokenId].ts index a51f541d..7ba55b7b 100644 --- a/pages/api/frontend/v0.1/orgs/[orgId]/apps/[appId]/admin/tokens/[tokenId].ts +++ b/pages/api/frontend/v0.1/orgs/[orgId]/apps/[appId]/admin/tokens/[tokenId].ts @@ -3,75 +3,85 @@ import { StatusCodes } from "http-status-codes"; import type { NextApiRequest, NextApiResponse } from "next"; import prisma from "../../../../../../../../../../lib/services/db"; import { AppAdminTokenDto } from "../../../../../../../../../../models/dtos/response/appAdminTokenDto"; +import { User } from "../../../../../../../../../../models/user"; import { encodeAppToken } from "../../../../../../../../../../util/adminApi/tokenEncoding"; -import { getUserWithRoleFromRequest } from "../../../../../../../../../../util/auth"; +import { authenticatedHandler } from "../../../../../../../../../../util/authenticatedHandler"; import { Logger } from "../../../../../../../../../../util/logger"; +const logger = new Logger(__filename); + export default async function handler( req: NextApiRequest, - res: NextApiResponse + res: NextApiResponse, + user: User ) { - const logger = new Logger(__filename); - - const user = await getUserWithRoleFromRequest(req, res); + return authenticatedHandler( + req, + res, + { method: "withRole" }, + async (req, res, user) => { + if (user.role !== "ADMIN") { + logger.error("User has no admin rights"); + return res + .status(StatusCodes.FORBIDDEN) + .json({ message: "You are not an admin" }); + } - if (!user) { - return; - } + switch (req.method) { + case "DELETE": + return deleteHandler(req, res, user); - if (user.role !== "ADMIN") { - logger.error("User has no admin rights"); - return res - .status(StatusCodes.FORBIDDEN) - .json({ message: "You are not an admin" }); - } + default: + return res + .status(StatusCodes.METHOD_NOT_ALLOWED) + .json({ message: "method not allowed" }); + } + } + ); +} +async function deleteHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User +) { const appId = Number(req.query.appId); const tokenId = Number(req.query.tokenId); - switch (req.method) { - case "DELETE": - try { - logger.log(`Deleting app admin token for org id '${appId}'`); - const appAdminToken = await prisma.appAdminToken.update({ - where: { - id: tokenId, - appId: appId, - isDeleted: false, - }, - data: { - isDeleted: true, - }, - }); + try { + logger.log(`Deleting app admin token for org id '${appId}'`); + const appAdminToken = await prisma.appAdminToken.update({ + where: { + id: tokenId, + appId: appId, + isDeleted: false, + }, + data: { + isDeleted: true, + }, + }); - const dto: AppAdminTokenDto = { - id: appAdminToken.id, - createdAt: appAdminToken.createdAt, - updatedAt: appAdminToken.updatedAt, - token: encodeAppToken(appAdminToken.token), - role: appAdminToken.role, - label: appAdminToken.label ?? undefined, - }; - - return res.status(StatusCodes.OK).json(dto); - } catch (e) { - if (e instanceof Prisma.PrismaClientKnownRequestError) { - logger.error(`No app admin token found with id '${tokenId}'`); - return res - .status(StatusCodes.NOT_FOUND) - .json({ message: `No app admin token found with id '${tokenId}'` }); - } + const dto: AppAdminTokenDto = { + id: appAdminToken.id, + createdAt: appAdminToken.createdAt, + updatedAt: appAdminToken.updatedAt, + token: encodeAppToken(appAdminToken.token), + role: appAdminToken.role, + label: appAdminToken.label ?? undefined, + }; - logger.error(`Internal server error occurred: ${e}`); - return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ - message: - "An internal server error occurred - please try again later!", - }); - } - - default: + return res.status(StatusCodes.OK).json(dto); + } catch (e) { + if (e instanceof Prisma.PrismaClientKnownRequestError) { + logger.error(`No app admin token found with id '${tokenId}'`); return res - .status(StatusCodes.METHOD_NOT_ALLOWED) - .json({ message: "method not allowed" }); + .status(StatusCodes.NOT_FOUND) + .json({ message: `No app admin token found with id '${tokenId}'` }); + } + + logger.error(`Internal server error occurred: ${e}`); + return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ + message: "An internal server error occurred - please try again later!", + }); } } diff --git a/pages/api/frontend/v0.1/orgs/[orgId]/apps/[appId]/admin/tokens/index.ts b/pages/api/frontend/v0.1/orgs/[orgId]/apps/[appId]/admin/tokens/index.ts index 537ea4e2..c5167122 100644 --- a/pages/api/frontend/v0.1/orgs/[orgId]/apps/[appId]/admin/tokens/index.ts +++ b/pages/api/frontend/v0.1/orgs/[orgId]/apps/[appId]/admin/tokens/index.ts @@ -5,127 +5,145 @@ import type { NextApiRequest, NextApiResponse } from "next"; import prisma from "../../../../../../../../../../lib/services/db"; import { CreateAppAdminTokenDto } from "../../../../../../../../../../models/dtos/request/createAppAdminTokenDto"; import { AppAdminTokenDto } from "../../../../../../../../../../models/dtos/response/appAdminTokenDto"; +import { User } from "../../../../../../../../../../models/user"; import { encodeAppToken } from "../../../../../../../../../../util/adminApi/tokenEncoding"; -import { - generateToken, - getUserWithRoleFromRequest, -} from "../../../../../../../../../../util/auth"; +import { generateToken } from "../../../../../../../../../../util/auth"; +import { authenticatedHandler } from "../../../../../../../../../../util/authenticatedHandler"; import { Logger } from "../../../../../../../../../../util/logger"; +const logger = new Logger(__filename); + export default async function handler( req: NextApiRequest, - res: NextApiResponse + res: NextApiResponse, + user: User ) { - const logger = new Logger(__filename); + return authenticatedHandler( + req, + res, + { method: "withRole" }, + async (req, res, user) => { + if (user.role !== "ADMIN") { + logger.error("User has no admin rights"); + return res + .status(StatusCodes.FORBIDDEN) + .json({ message: "You are not an admin" }); + } - const user = await getUserWithRoleFromRequest(req, res); + switch (req.method) { + case "GET": + return getHandler(req, res, user); - if (!user) { - return; - } + case "POST": + return postHandler(req, res, user); - if (user.role !== "ADMIN") { - logger.error("User has no admin rights"); - return res - .status(StatusCodes.FORBIDDEN) - .json({ message: "You are not an admin" }); - } + default: + return res + .status(StatusCodes.METHOD_NOT_ALLOWED) + .json({ message: "method not allowed" }); + } + } + ); +} +async function getHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User +) { const appId = Number(req.query.appId); - switch (req.method) { - case "GET": - logger.log(`Looking up appAdminTokens for app with id(=${appId})`); - const appAdminTokens = await prisma.appAdminToken.findMany({ - where: { - appId: appId, - isDeleted: false, - role: { - not: "TEMP", - }, - OR: [ - { - expiryDate: null, - }, - { - expiryDate: { - gt: new Date(), - }, - }, - ], + logger.log(`Looking up appAdminTokens for app with id(=${appId})`); + const appAdminTokens = await prisma.appAdminToken.findMany({ + where: { + appId: appId, + isDeleted: false, + role: { + not: "TEMP", + }, + OR: [ + { + expiryDate: null, }, - }); - - return res.status(StatusCodes.OK).json( - appAdminTokens.map((appAdminToken): AppAdminTokenDto => { - return { - id: appAdminToken.id, - createdAt: appAdminToken.createdAt, - updatedAt: appAdminToken.updatedAt, - token: encodeAppToken(appAdminToken.token), - role: appAdminToken.role, - label: appAdminToken.label ? appAdminToken.label : "", - expiryDate: appAdminToken.expiryDate - ? appAdminToken.expiryDate - : undefined, - }; - }) - ); - - case "POST": - const requestObj = plainToInstance(CreateAppAdminTokenDto, req.body); - const validationErrors = await validate(requestObj); - - if (validationErrors.length > 0) { - const errors = validationErrors - .flatMap((error) => - error.constraints - ? Object.values(error.constraints) - : ["An unknown error occurred"] - ) - .join(", "); - logger.error(`Validation failed: ${errors}`); - return res - .status(StatusCodes.BAD_REQUEST) - .json(getErrorDto(`Validation failed: ${errors}`)); - } - - const { timeToLive, label } = requestObj; - - const generatedToken = generateToken(); - - const expiryDate = - timeToLive === 0 ? null : new Date(Date.now() + timeToLive * 1000); - - logger.log(`Creating new app admin token for app id '${appId}'`); - const appAdminToken = await prisma.appAdminToken.create({ - data: { - token: generatedToken, - expiryDate: expiryDate, - label: label, - app: { - connect: { - id: appId, - }, + { + expiryDate: { + gt: new Date(), }, }, - }); + ], + }, + }); - const dto: AppAdminTokenDto = { + return res.status(StatusCodes.OK).json( + appAdminTokens.map((appAdminToken): AppAdminTokenDto => { + return { id: appAdminToken.id, createdAt: appAdminToken.createdAt, updatedAt: appAdminToken.updatedAt, token: encodeAppToken(appAdminToken.token), role: appAdminToken.role, - label: appAdminToken.label ?? undefined, - expiryDate: appAdminToken.expiryDate ?? undefined, + label: appAdminToken.label ? appAdminToken.label : "", + expiryDate: appAdminToken.expiryDate + ? appAdminToken.expiryDate + : undefined, }; + }) + ); +} - return res.status(StatusCodes.CREATED).json(dto); +async function postHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User +) { + const requestObj = plainToInstance(CreateAppAdminTokenDto, req.body); + const validationErrors = await validate(requestObj); + const appId = Number(req.query.appId); - default: - return res - .status(StatusCodes.METHOD_NOT_ALLOWED) - .json({ message: "method not allowed" }); + if (validationErrors.length > 0) { + const errors = validationErrors + .flatMap((error) => + error.constraints + ? Object.values(error.constraints) + : ["An unknown error occurred"] + ) + .join(", "); + logger.error(`Validation failed: ${errors}`); + return res + .status(StatusCodes.BAD_REQUEST) + .json(getErrorDto(`Validation failed: ${errors}`)); } + + const { timeToLive, label } = requestObj; + + const generatedToken = generateToken(); + + const expiryDate = + timeToLive === 0 ? null : new Date(Date.now() + timeToLive * 1000); + + logger.log(`Creating new app admin token for app id '${appId}'`); + const appAdminToken = await prisma.appAdminToken.create({ + data: { + token: generatedToken, + expiryDate: expiryDate, + label: label, + app: { + connect: { + id: appId, + }, + }, + }, + }); + + const dto: AppAdminTokenDto = { + id: appAdminToken.id, + createdAt: appAdminToken.createdAt, + updatedAt: appAdminToken.updatedAt, + token: encodeAppToken(appAdminToken.token), + role: appAdminToken.role, + label: appAdminToken.label ?? undefined, + expiryDate: appAdminToken.expiryDate ?? undefined, + }; + + return res.status(StatusCodes.CREATED).json(dto); } diff --git a/pages/api/frontend/v0.1/orgs/[orgId]/apps/[appId]/index.ts b/pages/api/frontend/v0.1/orgs/[orgId]/apps/[appId]/index.ts index fc8eae0e..f99e8191 100644 --- a/pages/api/frontend/v0.1/orgs/[orgId]/apps/[appId]/index.ts +++ b/pages/api/frontend/v0.1/orgs/[orgId]/apps/[appId]/index.ts @@ -2,142 +2,173 @@ import { Prisma } from "@prisma/client"; import { StatusCodes } from "http-status-codes"; import type { NextApiRequest, NextApiResponse } from "next"; import prisma from "../../../../../../../../lib/services/db"; -import { getUserWithRoleFromRequest } from "../../../../../../../../util/auth"; +import { User } from "../../../../../../../../models/user"; +import { authenticatedHandler } from "../../../../../../../../util/authenticatedHandler"; import { Logger } from "../../../../../../../../util/logger"; +const logger = new Logger(__filename); + export default async function handler( req: NextApiRequest, - res: NextApiResponse + res: NextApiResponse, + user: User ) { - const logger = new Logger(__filename); + return authenticatedHandler( + req, + res, + { method: "withRole" }, + async (req, res, user) => { + const org = await prisma.organisation.findFirst({ + where: { + id: Number(req.query.orgId), + isDeleted: false, + }, + }); - const user = await getUserWithRoleFromRequest(req, res); + if (!org) { + logger.error( + `Organisation with id '${req.query.orgId}' has been deleted or not found` + ); + return res.status(StatusCodes.NOT_FOUND).json({ + message: `Organisation with id '${req.query.orgId}' not found`, + }); + } - if (!user) { - return; - } + switch (req.method) { + case "GET": + return getHandler(req, res, user); + + case "DELETE": + return deleteHandler(req, res, user); + + case "PUT": + return putHandler(req, res, user); + + default: + return res + .status(StatusCodes.METHOD_NOT_ALLOWED) + .json({ message: "method not allowed" }); + } + } + ); +} - const org = await prisma.organisation.findFirst({ +async function getHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User +) { + logger.log(`Looking up app with id '${req.query.appId}'`); + const app = await prisma.app.findFirst({ + include: { + messages: { + include: { + actions: true, + }, + orderBy: [ + { + startDate: "asc", + }, + { + endDate: "asc", + }, + ], + }, + }, where: { - id: Number(req.query.orgId), - isDeleted: false, + id: Number(req.query.appId), + orgId: Number(req.query.orgId), }, }); - if (!org) { - logger.error( - `Organisation with id '${req.query.orgId}' has been deleted or not found` - ); - return res.status(StatusCodes.NOT_FOUND).json({ - message: `Organisation with id '${req.query.orgId}' not found`, - }); + if (app == null) { + logger.error(`No app found with id '${req.query.appId}'`); + return res + .status(StatusCodes.NOT_FOUND) + .json({ message: "No app found with id " + req.query.appId }); } - switch (req.method) { - case "GET": - logger.log(`Looking up app with id '${req.query.appId}'`); - const app = await prisma.app.findFirst({ - include: { - messages: { - include: { - actions: true, - }, - orderBy: [ - { - startDate: "asc", - }, - { - endDate: "asc", - }, - ], - }, - }, - where: { - id: Number(req.query.appId), - orgId: Number(req.query.orgId), - }, + return res.status(StatusCodes.OK).json({ + role: user.role, + publicKey: user.role === "ADMIN" ? app.publicKey : "", + name: app.name, + messages: app.messages, + }); +} + +async function deleteHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User +) { + try { + if (user.role === "USER") { + logger.error( + `You are not allowed to delete app with id '${req.query.appId}'` + ); + return res.status(StatusCodes.FORBIDDEN).json({ + message: "You are not allowed to delete app with id " + req.query.orgId, }); + } - if (app == null) { - logger.error(`No app found with id '${req.query.appId}'`); - return res - .status(StatusCodes.NOT_FOUND) - .json({ message: "No app found with id " + req.query.appId }); - } + logger.log(`Deleting app with id '${req.query.appId}'`); + const deletedApp = await prisma.app.delete({ + where: { + id: Number(req.query.appId), + }, + }); - return res.status(StatusCodes.OK).json({ - role: user.role, - publicKey: user.role === "ADMIN" ? app.publicKey : "", - name: app.name, - messages: app.messages, - }); + return res.status(StatusCodes.OK).json(deletedApp); + } catch (e) { + if (e instanceof Prisma.PrismaClientKnownRequestError) { + logger.error(`No app found with id '${req.query.appId}'`); + return res + .status(StatusCodes.NOT_FOUND) + .json({ message: "No app found with id " + req.query.appId }); + } + return res + .status(StatusCodes.NOT_IMPLEMENTED) + .json({ message: "Not implemented: unhandled response path" }); + } +} - case "DELETE": - try { - if (user.role === "USER") { - logger.error( - `You are not allowed to delete app with id '${req.query.appId}'` - ); - return res.status(StatusCodes.FORBIDDEN).json({ - message: - "You are not allowed to delete app with id " + req.query.orgId, - }); - } - - logger.log(`Deleting app with id '${req.query.appId}'`); - const deletedApp = await prisma.app.delete({ - where: { - id: Number(req.query.appId), - }, - }); +async function putHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User +) { + try { + if (user.role === "USER") { + logger.error( + `You are not allowed to update app with id '${req.query.appId}'` + ); + return res.status(StatusCodes.FORBIDDEN).json({ + message: "You are not allowed to update app with id " + req.query.orgId, + }); + } - return res.status(StatusCodes.OK).json(deletedApp); - } catch (e) { - if (e instanceof Prisma.PrismaClientKnownRequestError) { - logger.error(`No app found with id '${req.query.appId}'`); - return res - .status(StatusCodes.NOT_FOUND) - .json({ message: "No app found with id " + req.query.appId }); - } - } - break; - - case "PUT": - try { - if (user.role === "USER") { - logger.error( - `You are not allowed to update app with id '${req.query.appId}'` - ); - return res.status(StatusCodes.FORBIDDEN).json({ - message: - "You are not allowed to update app with id " + req.query.orgId, - }); - } - - logger.log(`Updating app with id '${req.query.appId}'`); - const updatedApp = await prisma.app.update({ - where: { - id: Number(req.query.appId), - }, - data: { - name: req.body.name, - }, - }); + logger.log(`Updating app with id '${req.query.appId}'`); + const updatedApp = await prisma.app.update({ + where: { + id: Number(req.query.appId), + }, + data: { + name: req.body.name, + }, + }); - return res.status(StatusCodes.CREATED).json(updatedApp); - } catch (e) { - if (e instanceof Prisma.PrismaClientKnownRequestError) { - logger.error(`No app found with id '${req.query.appId}'`); - res - .status(StatusCodes.NOT_FOUND) - .json({ message: "No app found with id " + req.query.appId }); - } - } - break; + return res.status(StatusCodes.CREATED).json(updatedApp); + } catch (e) { + if (e instanceof Prisma.PrismaClientKnownRequestError) { + logger.error(`No app found with id '${req.query.appId}'`); + res + .status(StatusCodes.NOT_FOUND) + .json({ message: "No app found with id " + req.query.appId }); + } - default: - return res - .status(StatusCodes.METHOD_NOT_ALLOWED) - .json({ message: "method not allowed" }); + logger.error(`Error: ${e}`); + return res + .status(StatusCodes.INTERNAL_SERVER_ERROR) + .json("An internal server error occurred, please try again later"); } } diff --git a/pages/api/frontend/v0.1/orgs/[orgId]/apps/[appId]/messages/[messageId].ts b/pages/api/frontend/v0.1/orgs/[orgId]/apps/[appId]/messages/[messageId].ts index 64e6604d..e18414a2 100644 --- a/pages/api/frontend/v0.1/orgs/[orgId]/apps/[appId]/messages/[messageId].ts +++ b/pages/api/frontend/v0.1/orgs/[orgId]/apps/[appId]/messages/[messageId].ts @@ -3,139 +3,162 @@ import { StatusCodes } from "http-status-codes"; import type { NextApiRequest, NextApiResponse } from "next"; import prisma from "../../../../../../../../../lib/services/db"; import { Action } from "../../../../../../../../../models/action"; -import { getUserWithRoleFromRequest } from "../../../../../../../../../util/auth"; +import { User } from "../../../../../../../../../models/user"; +import { authenticatedHandler } from "../../../../../../../../../util/authenticatedHandler"; import { Logger } from "../../../../../../../../../util/logger"; +const logger = new Logger(__filename); + export default async function handler( req: NextApiRequest, - res: NextApiResponse + res: NextApiResponse, + user: User ) { - const logger = new Logger(__filename); - - const user = await getUserWithRoleFromRequest(req, res); + return authenticatedHandler( + req, + res, + { method: "withRole" }, + async (req, res, user) => { + switch (req.method) { + case "GET": + return getHandler(req, res, user); + + case "DELETE": + return deleteHandler(req, res, user); + + case "PUT": + return putHandler(req, res, user); + + default: + return res + .status(StatusCodes.METHOD_NOT_ALLOWED) + .json({ message: "method not allowed" }); + } + } + ); +} - if (!user) { - return; +async function getHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User +) { + logger.log(`Looking up message with id '${req.query.messageId}'`); + const message = await prisma.message.findUnique({ + include: { + actions: true, + }, + where: { + id: Number(req.query.messageId), + }, + }); + + if (message == null) { + logger.log(`No message found with id '${req.query.messageId}'`); + return res + .status(StatusCodes.NOT_FOUND) + .json({ message: `No message found with id ${req.query.messageId}` }); } - switch (req.method) { - case "GET": - logger.log(`Looking up message with id '${req.query.messageId}'`); - const message = await prisma.message.findUnique({ - include: { - actions: true, - }, - where: { - id: Number(req.query.messageId), - }, - }); - - if (message == null) { - logger.log(`No message found with id '${req.query.messageId}'`); - return res - .status(StatusCodes.NOT_FOUND) - .json({ message: `No message found with id ${req.query.messageId}` }); - } + return res.status(StatusCodes.OK).json(message); +} - return res.status(StatusCodes.OK).json(message); - - case "DELETE": - try { - logger.log(`Deleting actions with message id '${req.query.messageId}'`); - await prisma.action.deleteMany({ - where: { - messageId: Number(req.query.messageId), - }, - }); - - logger.log(`Deleting message with id '${req.query.messageId}'`); - const deletedMessage = await prisma.message.delete({ - where: { - id: Number(req.query.messageId), - }, - }); - - return res.status(StatusCodes.OK).json(deletedMessage); - } catch (e) { - if (e instanceof Prisma.PrismaClientKnownRequestError) { - logger.error(`No message found with id '${req.query.messageId}'`); - return res.status(StatusCodes.NOT_FOUND).json({ - message: "No message found with id " + req.query.messageId, - }); - } - - logger.error(`Internal server error occurred: ${e}`); - return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ - message: - "An internal server error occurred - please try again later!", - }); - } +async function deleteHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User +) { + try { + logger.log(`Deleting actions with message id '${req.query.messageId}'`); + await prisma.action.deleteMany({ + where: { + messageId: Number(req.query.messageId), + }, + }); + + logger.log(`Deleting message with id '${req.query.messageId}'`); + const deletedMessage = await prisma.message.delete({ + where: { + id: Number(req.query.messageId), + }, + }); + + return res.status(StatusCodes.OK).json(deletedMessage); + } catch (e) { + if (e instanceof Prisma.PrismaClientKnownRequestError) { + logger.error(`No message found with id '${req.query.messageId}'`); + return res.status(StatusCodes.NOT_FOUND).json({ + message: "No message found with id " + req.query.messageId, + }); + } - case "PUT": - try { - if (new Date(req.body.startDate) >= new Date(req.body.endDate)) { - logger.error("Start date is not before end date"); - return res - .status(StatusCodes.BAD_REQUEST) - .json({ message: "Start date has to be before end date" }); - } - - logger.log(`Updating message with id '${req.query.messageId}'`); - const updatedMessage = await prisma.message.update({ - where: { - id: Number(req.query.messageId), - }, - data: { - blocking: req.body.blocking, - title: req.body.title, - body: req.body.body, - startDate: new Date(req.body.startDate), - endDate: new Date(req.body.endDate), - appId: req.body.appId, - }, - }); - - logger.log( - `Deleting actions of message with id '${req.query.messageId}'` - ); - await prisma.action.deleteMany({ - where: { - messageId: Number(req.query.messageId), - }, - }); - - if (req.body.actions.length > 0) { - const actions: Action[] = req.body.actions; - actions.forEach((action) => { - action.messageId = Number(req.query.messageId); - }); - logger.log( - `Creating actions for message with id '${req.query.messageId}'` - ); - await prisma.action.createMany({ - data: req.body.actions, - }); - } - - return res.status(StatusCodes.CREATED).json(updatedMessage); - } catch (e) { - if (e instanceof Prisma.PrismaClientKnownRequestError) { - logger.log(`No message found with id '${req.query.messageId}'`); - return res.status(StatusCodes.NOT_FOUND).json({ - message: "No message found with id " + req.query.messageId, - }); - } - - logger.error(`Internal server error occurred: ${e}`); - return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ - message: - "An internal server error occurred - please try again later!", - }); - } + logger.error(`Internal server error occurred: ${e}`); + return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ + message: "An internal server error occurred - please try again later!", + }); + } +} - default: +async function putHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User +) { + try { + if (new Date(req.body.startDate) >= new Date(req.body.endDate)) { + logger.error("Start date is not before end date"); return res - .status(StatusCodes.METHOD_NOT_ALLOWED) - .json({ message: "method not allowed" }); + .status(StatusCodes.BAD_REQUEST) + .json({ message: "Start date has to be before end date" }); + } + + logger.log(`Updating message with id '${req.query.messageId}'`); + const updatedMessage = await prisma.message.update({ + where: { + id: Number(req.query.messageId), + }, + data: { + blocking: req.body.blocking, + title: req.body.title, + body: req.body.body, + startDate: new Date(req.body.startDate), + endDate: new Date(req.body.endDate), + appId: req.body.appId, + }, + }); + + logger.log(`Deleting actions of message with id '${req.query.messageId}'`); + await prisma.action.deleteMany({ + where: { + messageId: Number(req.query.messageId), + }, + }); + + if (req.body.actions.length > 0) { + const actions: Action[] = req.body.actions; + actions.forEach((action) => { + action.messageId = Number(req.query.messageId); + }); + logger.log( + `Creating actions for message with id '${req.query.messageId}'` + ); + await prisma.action.createMany({ + data: req.body.actions, + }); + } + + return res.status(StatusCodes.CREATED).json(updatedMessage); + } catch (e) { + if (e instanceof Prisma.PrismaClientKnownRequestError) { + logger.log(`No message found with id '${req.query.messageId}'`); + return res.status(StatusCodes.NOT_FOUND).json({ + message: "No message found with id " + req.query.messageId, + }); + } + + logger.error(`Internal server error occurred: ${e}`); + return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ + message: "An internal server error occurred - please try again later!", + }); } } diff --git a/pages/api/frontend/v0.1/orgs/[orgId]/apps/[appId]/messages/index.ts b/pages/api/frontend/v0.1/orgs/[orgId]/apps/[appId]/messages/index.ts index 5869064c..3f57b734 100644 --- a/pages/api/frontend/v0.1/orgs/[orgId]/apps/[appId]/messages/index.ts +++ b/pages/api/frontend/v0.1/orgs/[orgId]/apps/[appId]/messages/index.ts @@ -2,71 +2,90 @@ import { StatusCodes } from "http-status-codes"; import type { NextApiRequest, NextApiResponse } from "next"; import prisma from "../../../../../../../../../lib/services/db"; import { Action } from "../../../../../../../../../models/action"; -import { getUserWithRoleFromRequest } from "../../../../../../../../../util/auth"; +import { User } from "../../../../../../../../../models/user"; +import { authenticatedHandler } from "../../../../../../../../../util/authenticatedHandler"; import { Logger } from "../../../../../../../../../util/logger"; +const logger = new Logger(__filename); + export default async function handler( req: NextApiRequest, - res: NextApiResponse + res: NextApiResponse, + user: User ) { - const logger = new Logger(__filename); - - const user = await getUserWithRoleFromRequest(req, res); - - if (!user) { - return; - } + return authenticatedHandler( + req, + res, + { method: "withRole" }, + async (req, res, user) => { + switch (req.method) { + case "GET": + return getHandler(req, res, user); - switch (req.method) { - case "GET": - logger.log(`Looking up messages with app id '${req.query.appId}'`); - const allMessages = await prisma.message.findMany({ - include: { - actions: true, - }, - where: { - appId: Number(req.query.appId), - }, - }); + case "POST": + return postHandler(req, res, user); - return res.status(StatusCodes.OK).json(allMessages); - - case "POST": - if (new Date(req.body.startDate) >= new Date(req.body.endDate)) { - logger.log("Start date has to be before end date"); - return res - .status(StatusCodes.BAD_REQUEST) - .json({ message: "Start date has to be before end date" }); + default: + return res + .status(StatusCodes.METHOD_NOT_ALLOWED) + .json({ message: "method not allowed" }); } + } + ); +} - logger.log(`Creating message for app id '${req.query.appId}'`); - const message = await prisma.message.create({ - data: { - blocking: req.body.blocking, - title: req.body.title, - body: req.body.body, - startDate: new Date(req.body.startDate), - endDate: new Date(req.body.endDate), - appId: req.body.appId, - }, - }); +async function getHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User +) { + logger.log(`Looking up messages with app id '${req.query.appId}'`); + const allMessages = await prisma.message.findMany({ + include: { + actions: true, + }, + where: { + appId: Number(req.query.appId), + }, + }); - if (req.body.actions.length > 0) { - logger.log(`Creating actions for message with id '${message.id}'`); - const actions: Action[] = req.body.actions; - actions.forEach((action) => { - action.messageId = message.id; - }); - await prisma.action.createMany({ - data: req.body.actions, - }); - } + return res.status(StatusCodes.OK).json(allMessages); +} - return res.status(StatusCodes.CREATED).json(message); +async function postHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User +) { + if (new Date(req.body.startDate) >= new Date(req.body.endDate)) { + logger.log("Start date has to be before end date"); + return res + .status(StatusCodes.BAD_REQUEST) + .json({ message: "Start date has to be before end date" }); + } + + logger.log(`Creating message for app id '${req.query.appId}'`); + const message = await prisma.message.create({ + data: { + blocking: req.body.blocking, + title: req.body.title, + body: req.body.body, + startDate: new Date(req.body.startDate), + endDate: new Date(req.body.endDate), + appId: req.body.appId, + }, + }); - default: - return res - .status(StatusCodes.METHOD_NOT_ALLOWED) - .json({ message: "method not allowed" }); + if (req.body.actions.length > 0) { + logger.log(`Creating actions for message with id '${message.id}'`); + const actions: Action[] = req.body.actions; + actions.forEach((action) => { + action.messageId = message.id; + }); + await prisma.action.createMany({ + data: req.body.actions, + }); } + + return res.status(StatusCodes.CREATED).json(message); } diff --git a/pages/api/frontend/v0.1/orgs/[orgId]/apps/index.ts b/pages/api/frontend/v0.1/orgs/[orgId]/apps/index.ts index 252c8feb..38597718 100644 --- a/pages/api/frontend/v0.1/orgs/[orgId]/apps/index.ts +++ b/pages/api/frontend/v0.1/orgs/[orgId]/apps/index.ts @@ -1,10 +1,9 @@ import { StatusCodes } from "http-status-codes"; import type { NextApiRequest, NextApiResponse } from "next"; import prisma from "../../../../../../../lib/services/db"; -import { - generateToken, - getUserWithRoleFromRequest, -} from "../../../../../../../util/auth"; +import { User } from "../../../../../../../models/user"; +import { generateToken } from "../../../../../../../util/auth"; +import { authenticatedHandler } from "../../../../../../../util/authenticatedHandler"; import { Logger } from "../../../../../../../util/logger"; type AppDto = { @@ -14,93 +13,110 @@ type AppDto = { activeMessages: number; }; +const logger = new Logger(__filename); + export default async function handler( req: NextApiRequest, - res: NextApiResponse + res: NextApiResponse, + user: User ) { - const logger = new Logger(__filename); + return authenticatedHandler( + req, + res, + { method: "withRole" }, + async (req, res, user) => { + switch (req.method) { + case "GET": + return getHandler(req, res, user); - const user = await getUserWithRoleFromRequest(req, res); + case "POST": + return postHandler(req, res, user); - if (!user) { - return; - } + default: + return res + .status(StatusCodes.METHOD_NOT_ALLOWED) + .json({ message: "method not allowed" }); + } + } + ); +} - switch (req.method) { - case "GET": - const org = await prisma.organisation.findFirst({ - where: { - id: Number(req.query.orgId), - isDeleted: false, - }, - }); +async function getHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User +) { + const org = await prisma.organisation.findFirst({ + where: { + id: Number(req.query.orgId), + isDeleted: false, + }, + }); - if (!org) { - logger.error( - `Organisation with id '${req.query.orgId}' has been deleted or not found` - ); - return res.status(StatusCodes.NOT_FOUND).json({ - message: `Organisation with id '${req.query.orgId}' not found`, - }); - } + if (!org) { + logger.error( + `Organisation with id '${req.query.orgId}' has been deleted or not found` + ); + return res.status(StatusCodes.NOT_FOUND).json({ + message: `Organisation with id '${req.query.orgId}' not found`, + }); + } - logger.log(`Looking up apps with org id '${req.query.orgId}'`); - const allApps = await prisma.app.findMany({ + logger.log(`Looking up apps with org id '${req.query.orgId}'`); + const allApps = await prisma.app.findMany({ + where: { + orgId: Number(req.query.orgId), + }, + orderBy: { + id: "asc", + }, + include: { + messages: { where: { - orgId: Number(req.query.orgId), - }, - orderBy: { - id: "asc", - }, - include: { - messages: { - where: { - AND: [ - { - startDate: { - lte: new Date(), - }, - }, - { - endDate: { - gte: new Date(), - }, - }, - ], + AND: [ + { + startDate: { + lte: new Date(), + }, }, - }, + { + endDate: { + gte: new Date(), + }, + }, + ], }, - }); + }, + }, + }); - return res.status(StatusCodes.OK).json( - allApps.map((app): AppDto => { - return { - id: app.id, - name: app.name, - role: user.role, - activeMessages: app.messages.length, - }; - }) - ); + return res.status(StatusCodes.OK).json( + allApps.map((app): AppDto => { + return { + id: app.id, + name: app.name, + role: user.role as string, + activeMessages: app.messages.length, + }; + }) + ); +} - case "POST": - const generatedToken = generateToken(); +async function postHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User +) { + const generatedToken = generateToken(); - logger.log( - `Creating app '${req.body.name}' for org id '${req.query.orgId}'` - ); - const app = await prisma.app.create({ - data: { - name: req.body.name, - orgId: req.body.orgId, - publicKey: generatedToken, - }, - }); - return res.status(StatusCodes.CREATED).json(app); + logger.log(`Creating app '${req.body.name}' for org id '${req.query.orgId}'`); + const app = await prisma.app.create({ + data: { + name: req.body.name, + orgId: req.body.orgId, + publicKey: generatedToken, + }, + }); - default: - return res - .status(StatusCodes.METHOD_NOT_ALLOWED) - .json({ message: "method not allowed" }); - } + return res.status(StatusCodes.CREATED).json(app); } diff --git a/pages/api/frontend/v0.1/orgs/[orgId]/index.ts b/pages/api/frontend/v0.1/orgs/[orgId]/index.ts index 8c99f570..080b7d40 100644 --- a/pages/api/frontend/v0.1/orgs/[orgId]/index.ts +++ b/pages/api/frontend/v0.1/orgs/[orgId]/index.ts @@ -2,176 +2,205 @@ import { Prisma } from "@prisma/client"; import { StatusCodes } from "http-status-codes"; import type { NextApiRequest, NextApiResponse } from "next"; import prisma from "../../../../../../lib/services/db"; -import { getUserWithRoleFromRequest } from "../../../../../../util/auth"; +import { User } from "../../../../../../models/user"; +import { authenticatedHandler } from "../../../../../../util/authenticatedHandler"; import { Logger } from "../../../../../../util/logger"; +const logger = new Logger(__filename); + export default async function handler( req: NextApiRequest, - res: NextApiResponse + res: NextApiResponse, + user: User ) { - const logger = new Logger(__filename); - - const user = await getUserWithRoleFromRequest(req, res); + return authenticatedHandler( + req, + res, + { method: "withRole" }, + async (req, res, user) => { + const orgId = Number(req.query.orgId); + + switch (req.method) { + case "GET": + return getHandler(req, res, user, orgId); + + case "DELETE": + return deleteHandler(req, res, user, orgId); + + case "PUT": + return putHandler(req, res, user, orgId); + + default: + return res + .status(StatusCodes.METHOD_NOT_ALLOWED) + .json({ message: "method not allowed" }); + } + } + ); +} - if (!user) { - return; +async function getHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User, + orgId: number +) { + logger.log(`Looking up organisation with id '${orgId}'`); + const org = await prisma.organisation.findUnique({ + where: { + id: orgId, + isDeleted: false, + }, + include: { + apps: true, + }, + }); + + if (org == null) { + logger.error(`No organisation found with id '${orgId}'`); + return res.status(StatusCodes.NOT_FOUND).json({ + message: `No organisation found with id '${orgId}'`, + }); } - const orgId = Number(req.query.orgId); - - switch (req.method) { - case "GET": - logger.log(`Looking up organisation with id '${orgId}'`); - const org = await prisma.organisation.findUnique({ - where: { - id: orgId, - isDeleted: false, - }, - include: { - apps: true, - }, - }); - - if (org == null) { - logger.error(`No organisation found with id '${orgId}'`); - return res.status(StatusCodes.NOT_FOUND).json({ - message: `No organisation found with id '${orgId}'`, - }); - } + return res.status(StatusCodes.OK).json({ + name: org.name, + apps: org.apps, + role: user.role, + customer: org.stripeCustomerId, + invitationToken: user.role === "ADMIN" ? org.invitationToken : "", + }); +} - return res.status(StatusCodes.OK).json({ - name: org.name, - apps: org.apps, - role: user.role, - customer: org.stripeCustomerId, - invitationToken: user.role === "ADMIN" ? org.invitationToken : "", +async function deleteHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User, + orgId: number +) { + try { + if (user.role === "USER") { + logger.error( + `You are not allowed to delete organisation with id '${orgId}'` + ); + return res.status(StatusCodes.FORBIDDEN).json({ + message: "You are not allowed to delete organisation with id " + orgId, }); + } - case "DELETE": - try { - if (user.role === "USER") { - logger.error( - `You are not allowed to delete organisation with id '${orgId}'` - ); - return res.status(StatusCodes.FORBIDDEN).json({ - message: - "You are not allowed to delete organisation with id " + orgId, - }); - } - - const orgToDelete = await prisma.organisation.findUnique({ - include: { - subs: { - where: { - isDeleted: false, - }, - }, - }, - where: { - id: orgId, - }, - }); - - if (orgToDelete && orgToDelete.isDeleted) { - logger.error("Organisation has already been deleted"); - return res.status(StatusCodes.NOT_FOUND).json({ - message: `Organisation with id '' not found`, - }); - } - - if (orgToDelete && orgToDelete.subs.length > 0) { - logger.error( - "Cannot delete organisation with active subscription! Cancel subscription first" - ); - return res.status(StatusCodes.BAD_REQUEST).json({ - message: - "Cannot delete organisation with active subscription! Cancel subscription first", - }); - } - - logger.log( - `Setting the isDeleted flag for all active orgAdminTokens for org with id(=${orgId})` - ); - await prisma.organisationAdminToken.updateMany({ + const orgToDelete = await prisma.organisation.findUnique({ + include: { + subs: { where: { - orgId: orgId, isDeleted: false, }, - data: { - isDeleted: true, - }, - }); + }, + }, + where: { + id: orgId, + }, + }); + + if (orgToDelete && orgToDelete.isDeleted) { + logger.error("Organisation has already been deleted"); + return res.status(StatusCodes.NOT_FOUND).json({ + message: `Organisation with id '${orgId}' not found`, + }); + } + + if (orgToDelete && orgToDelete.subs.length > 0) { + logger.error( + "Cannot delete organisation with active subscription! Cancel subscription first" + ); + return res.status(StatusCodes.BAD_REQUEST).json({ + message: + "Cannot delete organisation with active subscription! Cancel subscription first", + }); + } + + logger.log( + `Setting the isDeleted flag for all active orgAdminTokens for org with id(=${orgId})` + ); + await prisma.organisationAdminToken.updateMany({ + where: { + orgId: orgId, + isDeleted: false, + }, + data: { + isDeleted: true, + }, + }); + + logger.log(`Deleting organisation with id '${orgId}'`); + const deletedOrg = await prisma.organisation.update({ + where: { + id: orgId, + }, + data: { + isDeleted: true, + }, + }); + + logger.log(`Successfully deleted org with id '${orgId}'`); + return res.status(StatusCodes.OK).json(deletedOrg); + } catch (e) { + if (e instanceof Prisma.PrismaClientKnownRequestError) { + logger.error(`Error while deleting org with id '${orgId}': ${e}`); + return res.status(StatusCodes.NOT_FOUND).json({ + message: "Error while deleting org!", + }); + } + return res + .status(StatusCodes.NOT_IMPLEMENTED) + .json({ message: "Not implemented: unhandled response path" }); + } +} - logger.log(`Deleting organisation with id '${orgId}'`); - const deletedOrg = await prisma.organisation.update({ - where: { - id: orgId, - }, - data: { - isDeleted: true, - }, - }); - - logger.log(`Successfully deleted org with id '${orgId}'`); - return res.status(StatusCodes.OK).json(deletedOrg); - } catch (e) { - if (e instanceof Prisma.PrismaClientKnownRequestError) { - logger.error(`Error while deleting org with id '${orgId}': ${e}`); - return res.status(StatusCodes.NOT_FOUND).json({ - message: "Error while deleting org!", - }); - } - } - break; - - case "PUT": - try { - if (user.role === "USER") { - logger.error( - `You are not allowed to update organisation with id '${orgId}'` - ); - return res.status(StatusCodes.FORBIDDEN).json({ - message: - "You are not allowed to update organisation with id " + orgId, - }); - } - - const newName = req.body.name; - - if (!newName) { - return res.status(StatusCodes.NO_CONTENT).json({ - message: - "No content provided to update organisation with id '" + - orgId + - "'", - }); - } - - logger.log(`Updating org with id '${orgId}'`); - const updatedOrg = await prisma.organisation.update({ - where: { - id: orgId, - isDeleted: false, - }, - data: { - name: newName, - }, - }); - - return res.status(StatusCodes.OK).json(updatedOrg); - } catch (e) { - if (e instanceof Prisma.PrismaClientKnownRequestError) { - logger.error(`No organisation found with id '${orgId}'`); - return res.status(StatusCodes.NOT_FOUND).json({ - message: "No organisation found with id " + orgId, - }); - } - } - break; +async function putHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User, + orgId: number +) { + try { + if (user.role === "USER") { + logger.error( + `You are not allowed to update organisation with id '${orgId}'` + ); + return res.status(StatusCodes.FORBIDDEN).json({ + message: "You are not allowed to update organisation with id " + orgId, + }); + } + + const newName = req.body.name; - default: - return res - .status(StatusCodes.METHOD_NOT_ALLOWED) - .json({ message: "method not allowed" }); + if (!newName) { + return res.status(StatusCodes.NO_CONTENT).json({ + message: `No content provided to update organisation with id '${orgId}'`, + }); + } + + logger.log(`Updating org with id '${orgId}'`); + const updatedOrg = await prisma.organisation.update({ + where: { + id: orgId, + isDeleted: false, + }, + data: { + name: newName, + }, + }); + + return res.status(StatusCodes.OK).json(updatedOrg); + } catch (e) { + if (e instanceof Prisma.PrismaClientKnownRequestError) { + logger.error(`No organisation found with id '${orgId}'`); + return res.status(StatusCodes.NOT_FOUND).json({ + message: "No organisation found with id " + orgId, + }); + } + return res + .status(StatusCodes.NOT_IMPLEMENTED) + .json({ message: "Not implemented: unhandled response path" }); } } diff --git a/pages/api/frontend/v0.1/orgs/[orgId]/users.ts b/pages/api/frontend/v0.1/orgs/[orgId]/users.ts index bdf45a70..60444396 100644 --- a/pages/api/frontend/v0.1/orgs/[orgId]/users.ts +++ b/pages/api/frontend/v0.1/orgs/[orgId]/users.ts @@ -4,242 +4,267 @@ import type { NextApiRequest, NextApiResponse } from "next"; import prisma from "../../../../../../lib/services/db"; import { UserDto } from "../../../../../../models/dtos/response/userDto"; import { MailType } from "../../../../../../models/mailType"; -import { - generateToken, - getUserWithRoleFromRequest, - sendTokenPerMail, -} from "../../../../../../util/auth"; +import { User } from "../../../../../../models/user"; +import { generateToken, sendTokenPerMail } from "../../../../../../util/auth"; +import { authenticatedHandler } from "../../../../../../util/authenticatedHandler"; import { Logger } from "../../../../../../util/logger"; +const logger = new Logger(__filename); + export default async function handler( req: NextApiRequest, res: NextApiResponse ) { - const logger = new Logger(__filename); + return authenticatedHandler( + req, + res, + { method: "withRole" }, + async (req, res, user) => { + const orgId = Number(req.query.orgId); + + switch (req.method) { + case "GET": + return getHandler(req, res, user, orgId); + case "PUT": + return putHandler(req, res, user, orgId); + case "POST": + return postHandler(req, res, user, orgId); + default: + return res + .status(StatusCodes.METHOD_NOT_ALLOWED) + .json({ message: "Method not allowed" }); + } + } + ); +} - const user = await getUserWithRoleFromRequest(req, res); +async function getHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User, + orgId: number +) { + logger.log(`Looking up users in organisation with id '${orgId}'`); + const usersInOrg = await prisma.usersInOrganisations.findMany({ + include: { + user: true, + }, + where: { + orgId: orgId, + org: { + isDeleted: false, + }, + user: { + isDeleted: false, + }, + }, + orderBy: { + createdAt: "asc", + }, + }); - if (!user) { - return; + if (usersInOrg == null) { + logger.error(`No users found in organisation with id '${orgId}'`); + return res.status(StatusCodes.NOT_FOUND).json({ + message: "No users found in organisation with id " + orgId, + }); } - const orgId = Number(req.query.orgId); + logger.log( + `Looking up user direct invites for organisation with id '${orgId}'` + ); + const userInvitationsInOrg = await prisma.userInvitationToken.findMany({ + where: { + orgId: orgId, + isObsolete: false, + isArchived: false, + }, + orderBy: { + createdAt: "asc", + }, + }); - switch (req.method) { - case "GET": - logger.log(`Looking up users in organisation with id '${orgId}'`); - const usersInOrg = await prisma.usersInOrganisations.findMany({ - include: { - user: true, - }, - where: { - orgId: orgId, - org: { - isDeleted: false, - }, - user: { - isDeleted: false, - }, - }, - orderBy: { - createdAt: "asc", - }, - }); + const users = usersInOrg.map((userInOrg): UserDto => { + return { + id: userInOrg.userId, + firstName: userInOrg.user.firstName as string, + lastName: userInOrg.user.lastName as string, + email: userInOrg.user.email as string, + role: userInOrg.role, + }; + }); - if (usersInOrg == null) { - logger.error(`No users found in organisation with id '${orgId}'`); - return res.status(StatusCodes.NOT_FOUND).json({ - message: "No users found in organisation with id " + orgId, - }); - } + const usersInvited = userInvitationsInOrg.map((invite): UserDto => { + return { + id: -1, + firstName: "(pending)", + lastName: "", + email: invite.invitedEmail, + role: invite.role, + }; + }); - logger.log( - `Looking up user direct invites for organisation with id '${orgId}'` - ); - const userInvitationsInOrg = await prisma.userInvitationToken.findMany({ - where: { + // combine user list with pending invites list + return res.status(StatusCodes.OK).json([...users, ...usersInvited]); +} + +async function putHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User, + orgId: number +) { + if (user.role === "USER") { + logger.error( + `You are not allowed to update user with id '${req.body.userId}' in organisation with id '${orgId}'` + ); + return res.status(StatusCodes.FORBIDDEN).json({ + message: + "You are not allowed to update user with id " + + req.body.userId + + " in organisation with id " + + orgId, + }); + } + + if (user.id === req.body.userId) { + logger.error("You cannot change your own role"); + return res + .status(StatusCodes.BAD_REQUEST) + .json({ message: "You cannot change your own role" }); + } + + try { + logger.log( + `Updating role of user with id '${req.body.userId}' in organisation with id '${orgId}'` + ); + const updatedApp = await prisma.usersInOrganisations.update({ + where: { + orgId_userId: { + userId: Number(req.body.userId), orgId: orgId, - isObsolete: false, - isArchived: false, }, - orderBy: { - createdAt: "asc", + org: { + isDeleted: false, }, - }); + }, + data: { + role: req.body.role, + }, + }); - const users = usersInOrg.map((userInOrg): UserDto => { - return { - id: userInOrg.userId, - firstName: userInOrg.user.firstName as string, - lastName: userInOrg.user.lastName as string, - email: userInOrg.user.email as string, - role: userInOrg.role, - }; - }); - - const usersInvited = userInvitationsInOrg.map((invite): UserDto => { - return { - id: -1, - firstName: "(pending)", - lastName: "", - email: invite.invitedEmail, - role: invite.role, - }; + return res.status(StatusCodes.OK).json(updatedApp); + } catch (e) { + if (e instanceof Prisma.PrismaClientKnownRequestError) { + logger.error( + `No user with id '${req.body.userId}' found in organisation with id '${orgId}'` + ); + return res.status(StatusCodes.NOT_FOUND).json({ + message: `No user with id ${req.body.userId} found in organisation with id ${orgId}`, }); + } + return res + .status(StatusCodes.NOT_IMPLEMENTED) + .json({ message: "Not implemented: unhandled response path" }); + } +} - // combine user list with pending invites list - return res.status(StatusCodes.OK).json([...users, ...usersInvited]); - - case "PUT": - if (user.role === "USER") { - logger.error( - `You are not allowed to update user with id '${req.body.userId}' in organisation with id '${orgId}'` - ); - return res.status(StatusCodes.FORBIDDEN).json({ - message: - "You are not allowed to update user with id " + - req.body.userId + - " in organisation with id " + - orgId, - }); - } - - if (user.id === req.body.userId) { - logger.error("You cannot change your own role"); - return res - .status(StatusCodes.BAD_REQUEST) - .json({ message: "You cannot change your own role" }); - } +async function postHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User, + orgId: number +) { + if (user.role === "USER") { + logger.error( + `You are not allowed to add user with email '${req.body.email}' to organisation with id '${orgId}'` + ); + return res.status(StatusCodes.FORBIDDEN).json({ + message: + "You are not allowed to add user with email " + + req.body.email + + " to organisation with id " + + orgId, + }); + } - try { - logger.log( - `Updating role of user with id '${req.body.userId}' in organisation with id '${orgId}'` - ); - const updatedApp = await prisma.usersInOrganisations.update({ - where: { - orgId_userId: { - userId: Number(req.body.userId), - orgId: orgId, - }, - org: { - isDeleted: false, - }, - }, - data: { - role: req.body.role, - }, - }); - - return res.status(StatusCodes.OK).json(updatedApp); - } catch (e) { - if (e instanceof Prisma.PrismaClientKnownRequestError) { - logger.error( - `No user with id '${req.body.userId}' found in organisation with id '${orgId}'` - ); - return res.status(StatusCodes.NOT_FOUND).json({ - message: `No user with id ${req.body.userId} found in organisation with id ${orgId}`, - }); - } - } - break; - - case "POST": - if (user.role === "USER") { - logger.error( - `You are not allowed to add user with email '${req.body.email}' to organisation with id '${orgId}'` - ); - return res.status(StatusCodes.FORBIDDEN).json({ - message: - "You are not allowed to add user with email " + - req.body.email + - " to organisation with id " + - orgId, - }); - } + const generatedToken = generateToken(); - const generatedToken = generateToken(); + logger.log(`Looking up user with email '${req.body.email}'`); + const userByEmail = await prisma.user.findFirst({ + where: { + email: req.body.email, + NOT: { + isDeleted: true, + }, + }, + }); - logger.log(`Looking up user with email '${req.body.email}'`); - const userByEmail = await prisma.user.findFirst({ + if (userByEmail && userByEmail.id) { + logger.log( + `Looking up user with id '${userByEmail.id}' in organisation with id '${orgId}'` + ); + const searchUserAlreadyInOrganisation = + await prisma.usersInOrganisations.findFirst({ where: { - email: req.body.email, - NOT: { - isDeleted: true, - }, + userId: userByEmail.id, + orgId: orgId, + }, + include: { + org: true, }, }); - if (userByEmail && userByEmail.id) { - logger.log( - `Looking up user with id '${userByEmail.id}' in organisation with id '${orgId}'` - ); - const searchUserAlreadyInOrganisation = - await prisma.usersInOrganisations.findFirst({ - where: { - userId: userByEmail.id, - orgId: orgId, - }, - include: { - org: true, - }, - }); - - if (searchUserAlreadyInOrganisation) { - // Check if organisation has already been deleted - if (searchUserAlreadyInOrganisation.org.isDeleted) { - logger.error(`Organisation with id '${orgId}' has been deleted`); - return res - .status(StatusCodes.NOT_FOUND) - .json({ message: `No organisation found with id '${orgId}'` }); - } - - logger.error("User already in organisation"); - return res - .status(StatusCodes.BAD_REQUEST) - .json({ message: "User already in organisation!" }); - } + if (searchUserAlreadyInOrganisation) { + // Check if organisation has already been deleted + if (searchUserAlreadyInOrganisation.org.isDeleted) { + logger.error(`Organisation with id '${orgId}' has been deleted`); + return res + .status(StatusCodes.NOT_FOUND) + .json({ message: `No organisation found with id '${orgId}'` }); } - logger.log("Updating previous user invitation tokens as obsolete"); - await prisma.userInvitationToken.updateMany({ - where: { - invitedEmail: req.body.email, - isObsolete: false, - }, - data: { - isObsolete: true, - }, - }); + logger.error("User already in organisation"); + return res + .status(StatusCodes.BAD_REQUEST) + .json({ message: "User already in organisation!" }); + } + } - var expiryDate = new Date(); - // set expiryDate one hour from now - expiryDate.setTime(expiryDate.getTime() + 60 * 60 * 1000); + logger.log("Updating previous user invitation tokens as obsolete"); + await prisma.userInvitationToken.updateMany({ + where: { + invitedEmail: req.body.email, + isObsolete: false, + }, + data: { + isObsolete: true, + }, + }); - logger.log( - `Creating user invitation token for email '${req.body.email}' for organisation with id '${orgId}'` - ); - const uit = await prisma.userInvitationToken.create({ - data: { - invitedEmail: req.body.email, - token: generatedToken, - orgId: orgId, - userId: user.id, - expiryDate: expiryDate, - }, - }); + var expiryDate = new Date(); + // set expiryDate one hour from now + expiryDate.setTime(expiryDate.getTime() + 60 * 60 * 1000); - sendTokenPerMail( - req.body.email as string, - userByEmail ? (userByEmail.firstName as string) : "", - generatedToken, - userByEmail ? MailType.DirectInvite : MailType.DirectInviteNewUser - ); + logger.log( + `Creating user invitation token for email '${req.body.email}' for organisation with id '${orgId}'` + ); + const uit = await prisma.userInvitationToken.create({ + data: { + invitedEmail: req.body.email, + token: generatedToken, + orgId: orgId, + userId: user.id!, + expiryDate: expiryDate, + }, + }); - return res.status(StatusCodes.CREATED).json(uit); + sendTokenPerMail( + req.body.email as string, + userByEmail ? (userByEmail.firstName as string) : "", + generatedToken, + userByEmail ? MailType.DirectInvite : MailType.DirectInviteNewUser + ); - default: - return res - .status(StatusCodes.METHOD_NOT_ALLOWED) - .json({ message: "method not allowed" }); - } + return res.status(StatusCodes.CREATED).json(uit); } diff --git a/pages/api/frontend/v0.1/orgs/[orgId]/users/[userEmail].ts b/pages/api/frontend/v0.1/orgs/[orgId]/users/[userEmail].ts index 0f5c6e6c..24cbd126 100644 --- a/pages/api/frontend/v0.1/orgs/[orgId]/users/[userEmail].ts +++ b/pages/api/frontend/v0.1/orgs/[orgId]/users/[userEmail].ts @@ -2,188 +2,203 @@ import { Prisma } from "@prisma/client"; import { StatusCodes } from "http-status-codes"; import type { NextApiRequest, NextApiResponse } from "next"; import prisma from "../../../../../../../lib/services/db"; -import { getUserWithRoleFromRequest } from "../../../../../../../util/auth"; +import { User } from "../../../../../../../models/user"; +import { authenticatedHandler } from "../../../../../../../util/authenticatedHandler"; import { Logger } from "../../../../../../../util/logger"; -export default async function handler( +const logger = new Logger(__filename); + +export default function handler(req: NextApiRequest, res: NextApiResponse) { + return authenticatedHandler( + req, + res, + { method: "withRole" }, + async (req, res, user) => { + switch (req.method) { + case "DELETE": + return deleteHandler(req, res, user); + case "PUT": + return putHandler(req, res, user); + default: + return res + .status(StatusCodes.METHOD_NOT_ALLOWED) + .json({ message: "Method not allowed" }); + } + } + ); +} + +async function deleteHandler( req: NextApiRequest, - res: NextApiResponse + res: NextApiResponse, + user: User ) { - const logger = new Logger(__filename); - - const user = await getUserWithRoleFromRequest(req, res); - - if (!user) { - return; + if (user.role === "USER" && user.email !== req.query.userEmail) { + logger.error( + `You are not allowed to delete user with email '${req.query.userEmail}' from organisation with id '${req.query.orgId}'` + ); + return res.status(StatusCodes.FORBIDDEN).json({ + message: + "You are not allowed to delete user with email " + + req.query.userEmail + + " from organisation with id " + + req.query.orgId, + }); } - switch (req.method) { - case "DELETE": - if (user.role === "USER" && user.email !== req.query.userEmail) { + if (user.role === "ADMIN" && user.email === req.query.userEmail) { + logger.log(`Looking up all admins in org with id '${req.query.orgId}'`); + + const otherAdminsInOrg = await prisma.usersInOrganisations.findMany({ + where: { + orgId: Number(req.query.orgId), + role: "ADMIN", + }, + include: { + org: true, + }, + }); + + if (otherAdminsInOrg.length >= 1) { + if (otherAdminsInOrg[0].org.isDeleted) { logger.error( - `You are not allowed to delete user with email '${req.query.userEmail}' from organisation with id '${req.query.orgId}'` + `Organisation with id '${req.query.orgId}' has been already deleted` ); - return res.status(StatusCodes.FORBIDDEN).json({ - message: - "You are not allowed to delete user with email " + - req.query.userEmail + - " from organisation with id " + - req.query.orgId, + return res.status(StatusCodes.NOT_FOUND).json({ + message: `Organisation with id '${req.query.orgId}' not found`, }); } - if (user.role === "ADMIN" && user.email === req.query.userEmail) { - logger.log(`Looking up all admins in org with id '${req.query.orgId}'`); - - const otherAdminsInOrg = await prisma.usersInOrganisations.findMany({ - where: { - orgId: Number(req.query.orgId), - role: "ADMIN", - }, - include: { - org: true, - }, + if (otherAdminsInOrg.length === 1) { + logger.error("Organisation cannot be left by the only admin"); + return res.status(StatusCodes.BAD_REQUEST).json({ + message: "You cannot leave organisation when you are the only admin", }); - - if (otherAdminsInOrg.length >= 1) { - if (otherAdminsInOrg[0].org.isDeleted) { - logger.error( - `Organisation with id '${req.query.orgId}' has been already deleted` - ); - return res.status(StatusCodes.NOT_FOUND).json({ - message: `Organisation with id '${req.query.orgId}' not found`, - }); - } - - if (otherAdminsInOrg.length === 1) { - logger.error("Organisation cannot be left by the only admin"); - return res.status(StatusCodes.BAD_REQUEST).json({ - message: - "You cannot leave organisation when you are the only admin", - }); - } - } } + } + } - logger.log(`Looking up user with email '${req.query.userEmail}'`); - const userByEmail = await prisma.user.findFirst({ + logger.log(`Looking up user with email '${req.query.userEmail}'`); + const userByEmail = await prisma.user.findFirst({ + where: { + email: req.query.userEmail as string, + NOT: { + isDeleted: true, + }, + }, + }); + + if (userByEmail && userByEmail.id) { + logger.log(`Looking up user in org with id '${req.query.orgId}'`); + const userInOrg = await prisma.usersInOrganisations.findUnique({ + where: { + orgId_userId: { + userId: Number(userByEmail?.id), + orgId: Number(req.query.orgId), + }, + }, + }); + + if (userInOrg && userInOrg.userId) { + logger.log( + `Deleting user with id '${userInOrg.userId}' from org with id '${req.query.orgId}'` + ); + const deletedUserInOrg = await prisma.usersInOrganisations.delete({ where: { - email: req.query.userEmail as string, - NOT: { - isDeleted: true, + orgId_userId: { + userId: userInOrg.userId, + orgId: Number(req.query.orgId), }, }, }); - if (userByEmail && userByEmail.id) { - logger.log(`Looking up user in org with id '${req.query.orgId}'`); - const userInOrg = await prisma.usersInOrganisations.findUnique({ - where: { - orgId_userId: { - userId: Number(userByEmail?.id), - orgId: Number(req.query.orgId), - }, - }, - }); - - if (userInOrg && userInOrg.userId) { - logger.log( - `Deleting user with id '${userInOrg.userId}' from org with id '${req.query.orgId}'` - ); - const deletedUserInOrg = await prisma.usersInOrganisations.delete({ - where: { - orgId_userId: { - userId: userInOrg.userId, - orgId: Number(req.query.orgId), - }, - }, - }); - - return res.status(StatusCodes.OK).json(deletedUserInOrg); - } - } - - // if deletion is for a pending invitation - try { - logger.log( - `Deleting user direct invite for email '${req.query.userEmail}' for org with id '${req.query.orgId}'` - ); - const deletedUserInvite = await prisma.userInvitationToken.deleteMany({ - where: { - invitedEmail: req.query.userEmail as string, - isObsolete: false, - orgId: Number(req.query.orgId), - }, - }); - - return res.status(StatusCodes.OK).json(deletedUserInvite); - } catch (e) { - logger.log( - `No user invite for email '${req.query.userEmail}' found in organisation with id '${req.query.orgId}'` - ); - return res.status(StatusCodes.NOT_FOUND).json({ - message: - "No user invite for email " + - req.query.userEmail + - " found in organisation with id " + - req.query.orgId, - }); - } - break; + return res.status(StatusCodes.OK).json(deletedUserInOrg); + } + } - case "PUT": - if (user.role === "USER") { - logger.error( - `You are not allowed to update user invite with email '${req.query.userEmail}' in organisation with id '${req.query.orgId}'` - ); - return res.status(StatusCodes.FORBIDDEN).json({ - message: `You are not allowed to update user invite with email ${req.body.userEmail} in organisation with id ${req.query.orgId}`, - }); - } + // if deletion is for a pending invitation + try { + logger.log( + `Deleting user direct invite for email '${req.query.userEmail}' for org with id '${req.query.orgId}'` + ); + const deletedUserInvite = await prisma.userInvitationToken.deleteMany({ + where: { + invitedEmail: req.query.userEmail as string, + isObsolete: false, + orgId: Number(req.query.orgId), + }, + }); + + return res.status(StatusCodes.OK).json(deletedUserInvite); + } catch (e) { + logger.log( + `No user invite for email '${req.query.userEmail}' found in organisation with id '${req.query.orgId}'` + ); + return res.status(StatusCodes.NOT_FOUND).json({ + message: + "No user invite for email " + + req.query.userEmail + + " found in organisation with id " + + req.query.orgId, + }); + } +} - if (user.email === req.body.userEmail) { - logger.error("You cannot change your own role"); - return res - .status(StatusCodes.BAD_REQUEST) - .json({ message: "You cannot change your own role" }); - } +async function putHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User +) { + if (user.role === "USER") { + logger.error( + `You are not allowed to update user invite with email '${req.query.userEmail}' in organisation with id '${req.query.orgId}'` + ); + return res.status(StatusCodes.FORBIDDEN).json({ + message: `You are not allowed to update user invite with email ${req.body.userEmail} in organisation with id ${req.query.orgId}`, + }); + } - try { - logger.log( - `Updating role of user invite with email '${req.body.userEmail}' in organisation with id '${req.query.orgId}'` - ); - const updatedInvite = await prisma.userInvitationToken.updateMany({ - where: { - invitedEmail: req.body.userEmail as string, - orgId: Number(req.query.orgId), - isObsolete: false, - isArchived: false, - }, - data: { - role: req.body.role, - }, - }); + if (user.email === req.body.userEmail) { + logger.error("You cannot change your own role"); + return res + .status(StatusCodes.BAD_REQUEST) + .json({ message: "You cannot change your own role" }); + } - return res.status(StatusCodes.CREATED).json(updatedInvite); - } catch (e) { - if (e instanceof Prisma.PrismaClientKnownRequestError) { - logger.error( - `No user invite with email '${req.body.userEmail}' found in organisation with id '${req.query.orgId}'` - ); - return res.status(StatusCodes.NOT_FOUND).json({ - message: - "No user with email " + - req.body.userEmail + - " found in organisation with id " + - req.query.orgId, - }); - } - } - break; + try { + logger.log( + `Updating role of user invite with email '${req.body.userEmail}' in organisation with id '${req.query.orgId}'` + ); + const updatedInvite = await prisma.userInvitationToken.updateMany({ + where: { + invitedEmail: req.body.userEmail as string, + orgId: Number(req.query.orgId), + isObsolete: false, + isArchived: false, + }, + data: { + role: req.body.role, + }, + }); + + return res.status(StatusCodes.CREATED).json(updatedInvite); + } catch (e) { + if (e instanceof Prisma.PrismaClientKnownRequestError) { + logger.error( + `No user invite with email '${req.body.userEmail}' found in organisation with id '${req.query.orgId}'` + ); + return res.status(StatusCodes.NOT_FOUND).json({ + message: + "No user with email " + + req.body.userEmail + + " found in organisation with id " + + req.query.orgId, + }); + } - default: - return res - .status(StatusCodes.METHOD_NOT_ALLOWED) - .json({ message: "method not allowed" }); + logger.error(`Error: ${e}`); + return res + .status(StatusCodes.INTERNAL_SERVER_ERROR) + .json("An internal server error occurred, please try again later"); } } diff --git a/pages/api/frontend/v0.1/orgs/index.ts b/pages/api/frontend/v0.1/orgs/index.ts index b0d5daed..ba5d3a88 100644 --- a/pages/api/frontend/v0.1/orgs/index.ts +++ b/pages/api/frontend/v0.1/orgs/index.ts @@ -1,7 +1,9 @@ import { StatusCodes } from "http-status-codes"; import type { NextApiRequest, NextApiResponse } from "next"; import prisma from "../../../../../lib/services/db"; -import { generateToken, getUserFromRequest } from "../../../../../util/auth"; +import { User } from "../../../../../models/user"; +import { generateToken } from "../../../../../util/auth"; +import { authenticatedHandler } from "../../../../../util/authenticatedHandler"; import { Logger } from "../../../../../util/logger"; type OrganisationDto = { @@ -11,85 +13,98 @@ type OrganisationDto = { subName: string; }; -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - const logger = new Logger(__filename); - - const user = await getUserFromRequest(req, res); +const logger = new Logger(__filename); - if (!user) { - return; - } +export default function handler(req: NextApiRequest, res: NextApiResponse) { + return authenticatedHandler( + req, + res, + { method: "basic" }, + async (req, res, user) => { + switch (req.method) { + case "GET": + return getHandler(req, res, user); + case "POST": + return postHandler(req, res, user); + default: + return res + .status(StatusCodes.METHOD_NOT_ALLOWED) + .json({ message: "Method not allowed" }); + } + } + ); +} - switch (req.method) { - case "GET": - logger.log( - `Looking up organisations that user with id '${user.id}' is part of` - ); - const orgsForUser = await prisma.usersInOrganisations.findMany({ - where: { - user: { - id: user.id, - isDeleted: false, - }, - org: { - isDeleted: false, - }, - }, +async function getHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User +) { + logger.log( + `Looking up organisations that user with id '${user.id}' is part of` + ); + const orgsForUser = await prisma.usersInOrganisations.findMany({ + where: { + user: { + id: user.id, + isDeleted: false, + }, + org: { + isDeleted: false, + }, + }, + include: { + org: { include: { - org: { - include: { - subs: { - where: { - isDeleted: false, - }, - }, + subs: { + where: { + isDeleted: false, }, }, }, - }); + }, + }, + }); - return res.status(StatusCodes.OK).json( - orgsForUser.map((organisation): OrganisationDto => { - return { - id: organisation.orgId, - name: organisation.org.name, - role: organisation.role, - subName: - organisation.org.subs && organisation.org.subs.length > 0 - ? organisation.org.subs[0].subName - : "free", - }; - }) - ); + return res.status(StatusCodes.OK).json( + orgsForUser.map((organisation): OrganisationDto => { + return { + id: organisation.orgId, + name: organisation.org.name, + role: organisation.role, + subName: + organisation.org.subs && organisation.org.subs.length > 0 + ? organisation.org.subs[0].subName + : "free", + }; + }) + ); +} - case "POST": - const generatedToken = generateToken(); +async function postHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User +) { + const generatedToken = generateToken(); - logger.log(`Creating new organisation for user with id '${user.id}'`); - const userInOrg = await prisma.usersInOrganisations.create({ - data: { - user: { - connect: { - id: user.id, - }, - }, - role: "ADMIN", - org: { - create: { - name: req.body.name, - invitationToken: generatedToken, - }, - }, + logger.log(`Creating new organisation for user with id '${user.id}'`); + const userInOrg = await prisma.usersInOrganisations.create({ + data: { + user: { + connect: { + id: user.id, + }, + }, + role: "ADMIN", + org: { + create: { + name: req.body.name, + invitationToken: generatedToken, }, - }); - return res.status(StatusCodes.CREATED).json(userInOrg); + }, + }, + }); - default: - return res - .status(StatusCodes.METHOD_NOT_ALLOWED) - .json({ message: "method not allowed" }); - } + return res.status(StatusCodes.CREATED).json(userInOrg); } diff --git a/pages/api/frontend/v0.1/stripe/checkoutSession.ts b/pages/api/frontend/v0.1/stripe/checkoutSession.ts index 59c286a7..ded436e1 100644 --- a/pages/api/frontend/v0.1/stripe/checkoutSession.ts +++ b/pages/api/frontend/v0.1/stripe/checkoutSession.ts @@ -1,136 +1,150 @@ import { StatusCodes } from "http-status-codes"; import type { NextApiRequest, NextApiResponse } from "next"; import Stripe from "stripe"; +import { StripeConfig } from "../../../../../config/interfaces/StripeConfig"; import { loadConfig } from "../../../../../config/loadConfig"; import prisma from "../../../../../lib/services/db"; import { createStripeClient } from "../../../../../lib/services/stripe"; import { ProductType } from "../../../../../models/productType"; +import { User } from "../../../../../models/user"; import Routes from "../../../../../routes/routes"; -import { getUserWithRoleFromRequest } from "../../../../../util/auth"; +import { authenticatedHandler } from "../../../../../util/authenticatedHandler"; import { Logger } from "../../../../../util/logger"; +const logger = new Logger(__filename); + export default async function handler( req: NextApiRequest, - res: NextApiResponse + res: NextApiResponse, + user: User ) { - const logger = new Logger(__filename); - const stripeConfig = loadConfig().server.stripeConfig; + return authenticatedHandler( + req, + res, + { method: "withRole" }, + async (req, res, user) => { + const stripeConfig = loadConfig().server.stripeConfig; + + if (!stripeConfig.isEnabled) { + logger.error("Stripe is disabled but endpoint has been called"); + return res + .status(StatusCodes.SERVICE_UNAVAILABLE) + .json({ message: "Endpoint is disabled" }); + } - if (!stripeConfig.isEnabled) { - logger.error("Stripe is disabled but endpoint has been called"); - return res - .status(StatusCodes.SERVICE_UNAVAILABLE) - .json({ message: "Endpoint is disabled" }); - } + if (user.role !== "ADMIN") { + logger.error("User has no admin rights"); + return res + .status(StatusCodes.FORBIDDEN) + .json({ message: "You are not an admin" }); + } + + switch (req.method) { + case "POST": + return postHandler(req, res, user, stripeConfig); - const userInOrg = await getUserWithRoleFromRequest(req, res); + default: + return res + .status(StatusCodes.METHOD_NOT_ALLOWED) + .json({ message: "method not allowed" }); + } + } + ); +} - if (!userInOrg) { - return; +async function postHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User, + stripeConfig: StripeConfig +) { + const stripe = createStripeClient(); + + // check whether organisation has a stripe customer id with prisma + const org = await prisma.organisation.findUnique({ + where: { + id: user.orgId, + isDeleted: false, + }, + }); + + if (!org) { + logger.error(`No organisation found with id ${user.orgId}`); + return res.status(StatusCodes.NOT_FOUND).json({ + message: `No organisation found with id ${user.orgId}`, + }); } - if (userInOrg.role !== "ADMIN") { - logger.error("User has no admin rights"); + if (!req.body.products || !Array.isArray(req.body.products)) { + logger.error("No parameter products provided"); + return res + .status(StatusCodes.BAD_REQUEST) + .json({ message: "No parameter products provided" }); + } else if (!req.body.orgId) { + logger.error("No parameter orgId provided"); return res - .status(StatusCodes.FORBIDDEN) - .json({ message: "You are not an admin" }); + .status(StatusCodes.BAD_REQUEST) + .json({ message: "No parameter orgId provided" }); } - switch (req.method) { - case "POST": - const stripe = createStripeClient(); - - // check whether organisation has a stripe customer id with prisma - const org = await prisma.organisation.findUnique({ - where: { - id: userInOrg.orgId, - isDeleted: false, - }, - }); - - if (!org) { - logger.error(`No organisation found with id ${userInOrg.orgId}`); - return res.status(StatusCodes.NOT_FOUND).json({ - message: `No organisation found with id ${userInOrg.orgId}`, - }); - } + try { + logger.log("Creating checkout session for subscription"); - if (!req.body.products || !Array.isArray(req.body.products)) { - logger.error("No parameter products provided"); - return res - .status(StatusCodes.BAD_REQUEST) - .json({ message: "No parameter products provided" }); - } else if (!req.body.orgId) { - logger.error("No parameter orgId provided"); - return res - .status(StatusCodes.BAD_REQUEST) - .json({ message: "No parameter orgId provided" }); + const lineItems = req.body.products.map((product: ProductType) => { + // ensure that each product has a valid priceId + if (!product.priceId) { + throw new Error("Product is missing a priceId"); } - try { - logger.log("Creating checkout session for subscription"); - - const lineItems = req.body.products.map((product: ProductType) => { - // ensure that each product has a valid priceId - if (!product.priceId) { - throw new Error("Product is missing a priceId"); - } - - const lineItem: Stripe.Checkout.SessionCreateParams.LineItem = { - price: product.priceId, - }; - - if (!stripeConfig.useAutomaticTax) { - if (stripeConfig.dynamicTaxRates) { - lineItem.dynamic_tax_rates = stripeConfig.dynamicTaxRates; - } else if (stripeConfig.taxRates) { - lineItem.tax_rates = stripeConfig.taxRates; - } - } - - // if quantity is provided, include it in the line item - // note: metered prices do not get a quantity parameter - if (product.hasOwnProperty("quantity")) { - lineItem["quantity"] = product.quantity; - } - - return lineItem; - }); - - let sessionOptions: Stripe.Checkout.SessionCreateParams = { - allow_promotion_codes: true, - automatic_tax: { - enabled: !!stripeConfig.useAutomaticTax, - }, - billing_address_collection: "required", - client_reference_id: req.body.orgId, - line_items: lineItems, - mode: "subscription", - success_url: Routes.subscriptionPageSuccess(req.body.orgId), - cancel_url: Routes.subscriptionPageCancelled(), - tax_id_collection: { enabled: true }, - }; - - // if org already has a stripe id, add it to the options, else stripe will generate an id - if (org && org.stripeCustomerId) { - sessionOptions.customer = org.stripeCustomerId; - sessionOptions.customer_update = { - name: "auto", - }; - } + const lineItem: Stripe.Checkout.SessionCreateParams.LineItem = { + price: product.priceId, + }; - const session = await stripe.checkout.sessions.create(sessionOptions); + if (!stripeConfig.useAutomaticTax) { + if (stripeConfig.dynamicTaxRates) { + lineItem.dynamic_tax_rates = stripeConfig.dynamicTaxRates; + } else if (stripeConfig.taxRates) { + lineItem.tax_rates = stripeConfig.taxRates; + } + } - logger.log("Redirecting to Stripe checkout"); - return res.json(session.url); - } catch (error) { - logger.error(`Error during Stripe communication: ${error}`); - return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json(error); + // if quantity is provided, include it in the line item + // note: metered prices do not get a quantity parameter + if (product.hasOwnProperty("quantity")) { + lineItem["quantity"] = product.quantity; } - default: - return res - .status(StatusCodes.METHOD_NOT_ALLOWED) - .json({ message: "method not allowed" }); + return lineItem; + }); + + let sessionOptions: Stripe.Checkout.SessionCreateParams = { + allow_promotion_codes: true, + automatic_tax: { + enabled: !!stripeConfig.useAutomaticTax, + }, + billing_address_collection: "required", + client_reference_id: req.body.orgId, + line_items: lineItems, + mode: "subscription", + success_url: Routes.subscriptionPageSuccess(req.body.orgId), + cancel_url: Routes.subscriptionPageCancelled(), + tax_id_collection: { enabled: true }, + }; + + // if org already has a stripe id, add it to the options, else stripe will generate an id + if (org && org.stripeCustomerId) { + sessionOptions.customer = org.stripeCustomerId; + sessionOptions.customer_update = { + name: "auto", + }; + } + + const session = await stripe.checkout.sessions.create(sessionOptions); + + logger.log("Redirecting to Stripe checkout"); + return res.json(session.url); + } catch (error) { + logger.error(`Error during Stripe communication: ${error}`); + return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json(error); } } diff --git a/pages/api/frontend/v0.1/stripe/customerPortal.ts b/pages/api/frontend/v0.1/stripe/customerPortal.ts index 72ed0bf2..64922fb6 100644 --- a/pages/api/frontend/v0.1/stripe/customerPortal.ts +++ b/pages/api/frontend/v0.1/stripe/customerPortal.ts @@ -1,32 +1,54 @@ import { StatusCodes } from "http-status-codes"; import type { NextApiRequest, NextApiResponse } from "next"; +import { Config } from "../../../../../config/interfaces/Config"; import { loadConfig } from "../../../../../config/loadConfig"; import prisma from "../../../../../lib/services/db"; import { createStripeClient } from "../../../../../lib/services/stripe"; +import { User } from "../../../../../models/user"; import Routes from "../../../../../routes/routes"; -import { getUserWithRoleFromRequest } from "../../../../../util/auth"; +import { authenticatedHandler } from "../../../../../util/authenticatedHandler"; import { Logger } from "../../../../../util/logger"; +const logger = new Logger(__filename); + export default async function handler( req: NextApiRequest, - res: NextApiResponse + res: NextApiResponse, + user: User ) { - const config = loadConfig(); - const logger = new Logger(__filename); + return authenticatedHandler( + req, + res, + { method: "withRole" }, + async (req, res, user) => { + const config = loadConfig(); - if (!config.server.stripeConfig.isEnabled) { - logger.error("stripe is disabled but endpoint has been called"); - return res - .status(StatusCodes.SERVICE_UNAVAILABLE) - .json({ message: "Endpoint is disabled" }); - } + if (!config.server.stripeConfig.isEnabled) { + logger.error("stripe is disabled but endpoint has been called"); + return res + .status(StatusCodes.SERVICE_UNAVAILABLE) + .json({ message: "Endpoint is disabled" }); + } - const user = await getUserWithRoleFromRequest(req, res); + switch (req.method) { + case "POST": + return postHandler(req, res, user, config); - if (!user) { - return; - } + default: + return res + .status(StatusCodes.METHOD_NOT_ALLOWED) + .json({ message: "method not allowed" }); + } + } + ); +} +async function postHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User, + config: Config +) { const orgId = req.body.orgId; if (!orgId) { @@ -36,53 +58,45 @@ export default async function handler( .json({ message: "No parameter orgId provided" }); } - switch (req.method) { - case "POST": - const stripe = createStripeClient(); - - // check whether organisation has a stripe customer id with prisma - const orgFromDb = await prisma.organisation.findUnique({ - where: { - id: orgId, - isDeleted: false, - }, - }); + const stripe = createStripeClient(); - if (!orgFromDb) { - logger.error(`No org found with id ${orgId}`); - return res.status(StatusCodes.NOT_FOUND).json({ - message: `No organisation found with id ${orgId}`, - }); - } + // check whether organisation has a stripe customer id with prisma + const orgFromDb = await prisma.organisation.findUnique({ + where: { + id: orgId, + isDeleted: false, + }, + }); - if (!orgFromDb.stripeCustomerId) { - logger.error( - `No stripe customer id found for organisation with id ${orgId}` - ); - return res.status(StatusCodes.NOT_FOUND).json({ - message: `No stripe customer id found for organisation with id ${orgId}`, - }); - } + if (!orgFromDb) { + logger.error(`No org found with id ${orgId}`); + return res.status(StatusCodes.NOT_FOUND).json({ + message: `No organisation found with id ${orgId}`, + }); + } - try { - logger.log("Creating customer portal session for organisation"); - const session = await stripe.billingPortal.sessions.create({ - customer: orgFromDb.stripeCustomerId, - return_url: `${config.server.nextAuth.url}${Routes.DASHBOARD}`, - }); + if (!orgFromDb.stripeCustomerId) { + logger.error( + `No stripe customer id found for organisation with id ${orgId}` + ); + return res.status(StatusCodes.NOT_FOUND).json({ + message: `No stripe customer id found for organisation with id ${orgId}`, + }); + } - logger.log("Redirecting to Stripe customer portal"); - return res.json(session.url); - } catch (error) { - logger.error(`Error during Stripe communication: ${error}`); - return res - .status(StatusCodes.INTERNAL_SERVER_ERROR) - .json("An internal server error occurred, please try again later"); - } + try { + logger.log("Creating customer portal session for organisation"); + const session = await stripe.billingPortal.sessions.create({ + customer: orgFromDb.stripeCustomerId, + return_url: `${config.server.nextAuth.url}${Routes.DASHBOARD}`, + }); - default: - return res - .status(StatusCodes.METHOD_NOT_ALLOWED) - .json({ message: "method not allowed" }); + logger.log("Redirecting to Stripe customer portal"); + return res.json(session.url); + } catch (error) { + logger.error(`Error during Stripe communication: ${error}`); + return res + .status(StatusCodes.INTERNAL_SERVER_ERROR) + .json("An internal server error occurred, please try again later"); } } diff --git a/pages/api/frontend/v0.1/stripe/subscriptions/[orgId].ts b/pages/api/frontend/v0.1/stripe/subscriptions/[orgId].ts index c95df635..8e3e51b1 100644 --- a/pages/api/frontend/v0.1/stripe/subscriptions/[orgId].ts +++ b/pages/api/frontend/v0.1/stripe/subscriptions/[orgId].ts @@ -2,61 +2,75 @@ import { StatusCodes } from "http-status-codes"; import type { NextApiRequest, NextApiResponse } from "next"; import { loadConfig } from "../../../../../../config/loadConfig"; import prisma from "../../../../../../lib/services/db"; -import { getUserWithRoleFromRequest } from "../../../../../../util/auth"; +import { User } from "../../../../../../models/user"; +import { authenticatedHandler } from "../../../../../../util/authenticatedHandler"; import { Logger } from "../../../../../../util/logger"; +const logger = new Logger(__filename); + export default async function handler( req: NextApiRequest, - res: NextApiResponse + res: NextApiResponse, + user: User +) { + return authenticatedHandler( + req, + res, + { method: "withRole" }, + async (req, res, user) => { + const stripeConfig = loadConfig().server.stripeConfig; + + if (!stripeConfig.isEnabled) { + logger.error("stripe is disabled but endpoint has been called"); + return res + .status(StatusCodes.SERVICE_UNAVAILABLE) + .json({ message: "Endpoint is disabled" }); + } + + if (user.role !== "ADMIN") { + logger.error("User has no admin rights"); + return res + .status(StatusCodes.FORBIDDEN) + .json({ message: "You are not an admin" }); + } + + const orgId = Number(req.query.orgId); + + if (!orgId) { + logger.error("No orgId provided"); + return res + .status(StatusCodes.BAD_REQUEST) + .json({ message: "No orgId provided" }); + } + + switch (req.method) { + case "GET": + return getHandler(req, res, user, orgId); + + default: + return res + .status(StatusCodes.METHOD_NOT_ALLOWED) + .json({ message: "method not allowed" }); + } + } + ); +} + +async function getHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User, + orgId: number ) { - const logger = new Logger(__filename); - const stripeConfig = loadConfig().server.stripeConfig; - - if (!stripeConfig.isEnabled) { - logger.error("stripe is disabled but endpoint has been called"); - return res - .status(StatusCodes.SERVICE_UNAVAILABLE) - .json({ message: "Endpoint is disabled" }); - } - - const userInOrg = await getUserWithRoleFromRequest(req, res); - - if (!userInOrg) { - return; - } - - if (userInOrg.role !== "ADMIN") { - logger.error("User has no admin rights"); - return res - .status(StatusCodes.FORBIDDEN) - .json({ message: "You are not an admin" }); - } - - if (!req.query.orgId) { - logger.error("No orgId provided"); - return res - .status(StatusCodes.BAD_REQUEST) - .json({ message: "No orgId provided" }); - } - - switch (req.method) { - // get subscription data for logged in user - case "GET": - const subs = await prisma.subscription.findMany({ - where: { - orgId: Number(req.query.orgId), - isDeleted: false, - }, - include: { - org: true, - }, - }); - - return res.status(StatusCodes.OK).json(subs); - - default: - return res - .status(StatusCodes.METHOD_NOT_ALLOWED) - .json({ message: "method not allowed" }); - } + const subs = await prisma.subscription.findMany({ + where: { + orgId: Number(req.query.orgId), + isDeleted: false, + }, + include: { + org: true, + }, + }); + + return res.status(StatusCodes.OK).json(subs); } diff --git a/pages/api/frontend/v0.1/tokens/directInvitation/[token].ts b/pages/api/frontend/v0.1/tokens/directInvitation/[token].ts index 0cb90edc..9983e2bd 100644 --- a/pages/api/frontend/v0.1/tokens/directInvitation/[token].ts +++ b/pages/api/frontend/v0.1/tokens/directInvitation/[token].ts @@ -1,111 +1,137 @@ +import { Organisation, UserInvitationToken } from "@prisma/client"; import { StatusCodes } from "http-status-codes"; import type { NextApiRequest, NextApiResponse } from "next"; import prisma from "../../../../../../lib/services/db"; -import { getUserFromRequest } from "../../../../../../util/auth"; +import { User } from "../../../../../../models/user"; +import { authenticatedHandler } from "../../../../../../util/authenticatedHandler"; import { Logger } from "../../../../../../util/logger"; -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - const logger = new Logger(__filename); - - const data = req.query; +const logger = new Logger(__filename); - const { token } = data; - - const user = await getUserFromRequest(req, res); - - if (!user) { - return; - } +export default function handler(req: NextApiRequest, res: NextApiResponse) { + return authenticatedHandler( + req, + res, + { method: "basic" }, + async (req, res, user) => { + const data = req.query; - logger.log("Looking up user invitation token"); - const userInvitationToken = await prisma.userInvitationToken.findFirst({ - where: { - token: token as string, - }, - }); - - if (!userInvitationToken) { - logger.error(`Provided user invitation token not found`); - return res - .status(StatusCodes.BAD_REQUEST) - .json({ message: `No user invitation token found with ${token}` }); - } + const { token } = data; - if ( - userInvitationToken.isArchived || - userInvitationToken.isObsolete || - userInvitationToken.expiryDate < new Date() - ) { - logger.error(`Provided user invitation token is obsolete`); - return res - .status(StatusCodes.BAD_REQUEST) - .json({ message: `User invitation token is obsolete` }); - } + logger.log("Looking up user invitation token"); + const userInvitationToken = await prisma.userInvitationToken.findFirst({ + where: { + token: token as string, + }, + }); - logger.log(`Looking up organisation with id '${userInvitationToken.orgId}'`); - const organisation = await prisma.organisation.findFirst({ - where: { - id: userInvitationToken.orgId, - isDeleted: false, - }, - }); + if (!userInvitationToken) { + logger.error(`Provided user invitation token not found`); + return res + .status(StatusCodes.BAD_REQUEST) + .json({ message: `No user invitation token found with ${token}` }); + } - if (!organisation) { - logger.error( - `No organisation found with id '${userInvitationToken.orgId}'` - ); - return res.status(StatusCodes.BAD_REQUEST).json({ - message: `No organisation found with id ${userInvitationToken.orgId}`, - }); - } + if ( + userInvitationToken.isArchived || + userInvitationToken.isObsolete || + userInvitationToken.expiryDate < new Date() + ) { + logger.error(`Provided user invitation token is obsolete`); + return res + .status(StatusCodes.BAD_REQUEST) + .json({ message: `User invitation token is obsolete` }); + } - switch (req.method) { - case "GET": - return res.status(StatusCodes.OK).json({ - id: organisation.id, - name: organisation.name, - invitationToken: userInvitationToken.token, + logger.log( + `Looking up organisation with id '${userInvitationToken.orgId}'` + ); + const organisation = await prisma.organisation.findFirst({ + where: { + id: userInvitationToken.orgId, + isDeleted: false, + }, }); - case "POST": - try { - logger.log( - `Creating user with id '${user.id}' relation to organisation with id '${organisation.id}'` + if (!organisation) { + logger.error( + `No organisation found with id '${userInvitationToken.orgId}'` ); - await prisma.usersInOrganisations.create({ - data: { - userId: user.id, - orgId: organisation.id, - role: userInvitationToken.role, - }, + return res.status(StatusCodes.BAD_REQUEST).json({ + message: `No organisation found with id ${userInvitationToken.orgId}`, }); + } - logger.log(`Updating user invitation token as obsolete`); - await prisma.userInvitationToken.update({ - where: { - token: userInvitationToken.token, - }, - data: { - isArchived: true, - }, - }); - } catch (error) { - logger.error("User already in organisation"); - return res - .status(StatusCodes.BAD_REQUEST) - .json({ message: `User already in organisation` }); + switch (req.method) { + case "GET": + return getHandler( + req, + res, + user, + organisation, + userInvitationToken.token + ); + case "POST": + return postHandler(req, res, user, organisation, userInvitationToken); + default: + return res + .status(StatusCodes.METHOD_NOT_ALLOWED) + .json({ message: "Method not allowed" }); } + } + ); +} - return res - .status(StatusCodes.OK) - .json({ message: `User joined organisation` }); +async function getHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User, + organisation: Organisation, + token: string +) { + return res.status(StatusCodes.OK).json({ + id: organisation.id, + name: organisation.name, + invitationToken: token, + }); +} + +async function postHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User, + organisation: Organisation, + userInvitationToken: UserInvitationToken +) { + try { + logger.log( + `Creating user with id '${user.id}' relation to organisation with id '${organisation.id}'` + ); + await prisma.usersInOrganisations.create({ + data: { + userId: user.id!, + orgId: organisation.id, + role: userInvitationToken.role, + }, + }); - default: - return res - .status(StatusCodes.METHOD_NOT_ALLOWED) - .json({ message: "method not allowed" }); + logger.log(`Updating user invitation token as obsolete`); + await prisma.userInvitationToken.update({ + where: { + token: userInvitationToken.token, + }, + data: { + isArchived: true, + }, + }); + } catch (error) { + logger.error("User already in organisation"); + return res + .status(StatusCodes.BAD_REQUEST) + .json({ message: `User already in organisation` }); } + + return res + .status(StatusCodes.OK) + .json({ message: `User joined organisation` }); } diff --git a/pages/api/frontend/v0.1/tokens/organisationInvitation/[token].ts b/pages/api/frontend/v0.1/tokens/organisationInvitation/[token].ts index ec28fb4f..a76cb6ec 100644 --- a/pages/api/frontend/v0.1/tokens/organisationInvitation/[token].ts +++ b/pages/api/frontend/v0.1/tokens/organisationInvitation/[token].ts @@ -1,94 +1,117 @@ +import { Organisation } from "@prisma/client"; import { StatusCodes } from "http-status-codes"; import type { NextApiRequest, NextApiResponse } from "next"; import prisma from "../../../../../../lib/services/db"; -import { generateToken, getUserFromRequest } from "../../../../../../util/auth"; +import { User } from "../../../../../../models/user"; +import { generateToken } from "../../../../../../util/auth"; +import { authenticatedHandler } from "../../../../../../util/authenticatedHandler"; import { Logger } from "../../../../../../util/logger"; -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - const logger = new Logger(__filename); +const logger = new Logger(__filename); - const data = req.query; +export default function handler(req: NextApiRequest, res: NextApiResponse) { + return authenticatedHandler( + req, + res, + { method: "basic" }, + async (req, res, user) => { + const data = req.query; - const { token } = data; + const { token } = data; - const user = await getUserFromRequest(req, res); + logger.log("Looking up organisation invitation token"); + const organisation = await prisma.organisation.findFirst({ + where: { + invitationToken: token as string, + isDeleted: false, + }, + }); - if (!user) { - return; - } + if (!organisation) { + logger.error(`Provided organisation invite token not found`); + return res + .status(StatusCodes.BAD_REQUEST) + .json({ message: `No organisation found with invite ${token}!` }); + } - logger.log("Looking up organisation invitation token"); - const organisation = await prisma.organisation.findFirst({ - where: { - invitationToken: token as string, - isDeleted: false, - }, + switch (req.method) { + case "GET": + return getHandler(req, res, user, organisation); + case "POST": + return postHandler(req, res, user, organisation); + case "PUT": + return putHandler(req, res, user, organisation); + default: + return res + .status(StatusCodes.METHOD_NOT_ALLOWED) + .json({ message: "Method not allowed" }); + } + } + ); +} + +async function getHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User, + organisation: Organisation +) { + return res.status(StatusCodes.OK).json({ + id: organisation.id, + name: organisation.name, + invitationToken: organisation.invitationToken, }); +} - if (!organisation) { - logger.error(`Provided organisation invite token not found`); +async function postHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User, + organisation: Organisation +) { + try { + logger.log( + `Creating user with id '${user.id}' relation to organisation with id '${organisation.id}' (via org token)` + ); + await prisma.usersInOrganisations.create({ + data: { + userId: user.id!, + orgId: organisation.id, + role: "USER", + }, + }); + } catch (error) { + logger.error("User already in organisation"); return res .status(StatusCodes.BAD_REQUEST) - .json({ message: `No organisation found with invite ${token}!` }); + .json({ message: `User already in organisation` }); } - switch (req.method) { - case "GET": - return res.status(StatusCodes.OK).json({ - id: organisation.id, - name: organisation.name, - invitationToken: organisation.invitationToken, - }); - - case "POST": - try { - logger.log( - `Creating user with id '${user.id}' relation to organisation with id '${organisation.id}' (via org token)` - ); - await prisma.usersInOrganisations.create({ - data: { - userId: user.id, - orgId: organisation.id, - role: "USER", - }, - }); - } catch (error) { - logger.error("User already in organisation"); - return res - .status(StatusCodes.BAD_REQUEST) - .json({ message: `User already in organisation` }); - } - - return res - .status(StatusCodes.OK) - .json({ message: `User joined organisation` }); - - case "PUT": - const generatedToken = generateToken(); + return res + .status(StatusCodes.OK) + .json({ message: `User joined organisation` }); +} - logger.log( - `Updating new generated invite token for organisation with id '${organisation.id}'` - ); - await prisma.organisation.update({ - where: { - id: organisation.id, - isDeleted: false, - }, - data: { - invitationToken: generatedToken, - }, - }); +async function putHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User, + organisation: Organisation +) { + const generatedToken = generateToken(); - return res - .status(StatusCodes.OK) - .json({ message: `Updated organisation` }); + logger.log( + `Updating new generated invite token for organisation with id '${organisation.id}'` + ); + await prisma.organisation.update({ + where: { + id: organisation.id, + isDeleted: false, + }, + data: { + invitationToken: generatedToken, + }, + }); - default: - return res - .status(StatusCodes.METHOD_NOT_ALLOWED) - .json({ message: "method not allowed" }); - } + return res.status(StatusCodes.OK).json({ message: `Updated organisation` }); } diff --git a/pages/api/frontend/v0.1/tokens/resetPassword.ts b/pages/api/frontend/v0.1/tokens/resetPassword.ts index eeb4e8c0..bbadfd3d 100644 --- a/pages/api/frontend/v0.1/tokens/resetPassword.ts +++ b/pages/api/frontend/v0.1/tokens/resetPassword.ts @@ -10,141 +10,18 @@ import { } from "../../../../../util/auth"; import { Logger } from "../../../../../util/logger"; +const logger = new Logger(__filename); + export default async function handler( req: NextApiRequest, res: NextApiResponse ) { - const logger = new Logger(__filename); - - const data = req.body; - - const { token, email, password } = data; - switch (req.method) { - case "PUT": - if (!token || !password) { - logger.error("No token or password provided"); - return res - .status(StatusCodes.BAD_REQUEST) - .json({ message: "No token or password provided" }); - } - - if (!(await validatePassword(password))) { - logger.error("Password consists of less than 8 characters"); - return res.status(StatusCodes.UNPROCESSABLE_ENTITY).json({ - message: "Invalid data - password consists of less than 8 characters", - }); - } - - logger.log("Looking up password reset token"); - const lookupToken = await prisma.passwordResetToken.findFirst({ - where: { - token: token, - }, - }); - - if (!lookupToken) { - logger.error("Password reset token not found"); - return res - .status(StatusCodes.NOT_FOUND) - .json({ message: "Password reset token not found" }); - } - - if ( - lookupToken && - (lookupToken.isArchived || - lookupToken.isObsolete || - lookupToken.expiryDate < new Date()) - ) { - logger.log("Provided password reset token is obsolete"); - return res - .status(StatusCodes.BAD_REQUEST) - .json({ message: "Please restart the password reset process!" }); - } - - const { hashedSaltedPassword, salt } = await hashAndSaltPassword( - password - ); - - logger.log(`Updating password of user with id '${lookupToken.userId}'`); - const updatedUser = await prisma.user.update({ - where: { - id: lookupToken.userId, - }, - data: { - password: hashedSaltedPassword, - salt: salt, - }, - }); - - logger.log("Updating password reset token as archived"); - await prisma.passwordResetToken.update({ - where: { - id: lookupToken.id, - }, - data: { - isArchived: true, - }, - }); - - return res.status(StatusCodes.OK).json(updatedUser); - case "POST": - logger.log(`Looking up user with email '${email}'`); - const user = await prisma.user.findFirst({ - where: { - email: email, - NOT: { - isDeleted: true, - }, - }, - }); - - if (!user || (user && !user.id)) { - logger.error(`No user found with email '${email}'`); - return res - .status(StatusCodes.BAD_REQUEST) - .json({ message: "User not found" }); - } - - const generatedToken = generateToken(); - - var expiryDate = new Date(); - // set expiryDate one hour from now - expiryDate.setTime(expiryDate.getTime() + 60 * 60 * 1000); - - logger.log( - `Updating previous password reset tokens for user with id '${user.id}' as obsolete` - ); - await prisma.passwordResetToken.updateMany({ - where: { - userId: user.id, - isObsolete: false, - }, - data: { - isObsolete: true, - }, - }); - - logger.log( - `Create new password reset token for user with id '${user.id}'` - ); - await prisma.passwordResetToken.create({ - data: { - userId: user.id, - token: generatedToken, - expiryDate: expiryDate, - }, - }); - - sendTokenPerMail( - user.email as string, - user.firstName as string, - generatedToken, - MailType.ResetPassword - ); - - return res.status(StatusCodes.OK).json(user); + return postHandler(req, res); + + case "PUT": + return putHandler(req, res); default: return res @@ -152,3 +29,130 @@ export default async function handler( .json({ message: "method not allowed" }); } } + +async function putHandler(req: NextApiRequest, res: NextApiResponse) { + const { token, password } = req.body; + + if (!token || !password) { + logger.error("No token or password provided"); + return res + .status(StatusCodes.BAD_REQUEST) + .json({ message: "No token or password provided" }); + } + + if (!(await validatePassword(password))) { + logger.error("Password consists of less than 8 characters"); + return res.status(StatusCodes.UNPROCESSABLE_ENTITY).json({ + message: "Invalid data - password consists of less than 8 characters", + }); + } + + logger.log("Looking up password reset token"); + const lookupToken = await prisma.passwordResetToken.findFirst({ + where: { + token: token, + }, + }); + + if (!lookupToken) { + logger.error("Password reset token not found"); + return res + .status(StatusCodes.NOT_FOUND) + .json({ message: "Password reset token not found" }); + } + + if ( + lookupToken && + (lookupToken.isArchived || + lookupToken.isObsolete || + lookupToken.expiryDate < new Date()) + ) { + logger.log("Provided password reset token is obsolete"); + return res + .status(StatusCodes.BAD_REQUEST) + .json({ message: "Please restart the password reset process!" }); + } + + const { hashedSaltedPassword, salt } = await hashAndSaltPassword(password); + + logger.log(`Updating password of user with id '${lookupToken.userId}'`); + const updatedUser = await prisma.user.update({ + where: { + id: lookupToken.userId, + }, + data: { + password: hashedSaltedPassword, + salt: salt, + }, + }); + + logger.log("Updating password reset token as archived"); + await prisma.passwordResetToken.update({ + where: { + id: lookupToken.id, + }, + data: { + isArchived: true, + }, + }); + + return res.status(StatusCodes.OK).json(updatedUser); +} + +async function postHandler(req: NextApiRequest, res: NextApiResponse) { + const { email } = req.body; + + logger.log(`Looking up user with email '${email}'`); + const user = await prisma.user.findFirst({ + where: { + email: email, + NOT: { + isDeleted: true, + }, + }, + }); + + if (!user || (user && !user.id)) { + logger.error(`No user found with email '${email}'`); + return res + .status(StatusCodes.BAD_REQUEST) + .json({ message: "User not found" }); + } + + const generatedToken = generateToken(); + + var expiryDate = new Date(); + // set expiryDate one hour from now + expiryDate.setTime(expiryDate.getTime() + 60 * 60 * 1000); + + logger.log( + `Updating previous password reset tokens for user with id '${user.id}' as obsolete` + ); + await prisma.passwordResetToken.updateMany({ + where: { + userId: user.id, + isObsolete: false, + }, + data: { + isObsolete: true, + }, + }); + + logger.log(`Create new password reset token for user with id '${user.id}'`); + await prisma.passwordResetToken.create({ + data: { + userId: user.id, + token: generatedToken, + expiryDate: expiryDate, + }, + }); + + sendTokenPerMail( + user.email as string, + user.firstName as string, + generatedToken, + MailType.ResetPassword + ); + + return res.status(StatusCodes.OK).json(user); +} diff --git a/pages/api/frontend/v0.1/tokens/resetPassword/[token].ts b/pages/api/frontend/v0.1/tokens/resetPassword/[token].ts index d02897a3..b90af30f 100644 --- a/pages/api/frontend/v0.1/tokens/resetPassword/[token].ts +++ b/pages/api/frontend/v0.1/tokens/resetPassword/[token].ts @@ -3,46 +3,49 @@ import type { NextApiRequest, NextApiResponse } from "next"; import prisma from "../../../../../../lib/services/db"; import { Logger } from "../../../../../../util/logger"; +const logger = new Logger(__filename); + export default async function handler( req: NextApiRequest, res: NextApiResponse ) { - const logger = new Logger(__filename); - switch (req.method) { case "GET": - logger.log("Looking up password reset token"); - const resetToken = await prisma.passwordResetToken.findFirst({ - where: { - token: req.query.token as string, - }, - }); - - if (resetToken == null) { - logger.error("Provided password reset token not found"); - return res.status(StatusCodes.NOT_FOUND).json({ - message: "no token found that looks like this: " + req.query.token, - }); - } - - if ( - resetToken && - (resetToken.isArchived || - resetToken.isObsolete || - resetToken.expiryDate < new Date()) - ) { - logger.error("Provided password reset token is obsolete"); - return res.status(StatusCodes.NOT_FOUND).json({ - message: "token is obsolete", - }); - } - - return res.status(StatusCodes.OK).json(resetToken); + return await getHandler(req, res); default: - res + return res .status(StatusCodes.METHOD_NOT_ALLOWED) .json({ message: "method not allowed" }); - return; } } + +async function getHandler(req: NextApiRequest, res: NextApiResponse) { + logger.log("Looking up password reset token"); + const resetToken = await prisma.passwordResetToken.findFirst({ + where: { + token: req.query.token as string, + }, + }); + + if (resetToken == null) { + logger.error("Provided password reset token not found"); + return res.status(StatusCodes.NOT_FOUND).json({ + message: "no token found that looks like this: " + req.query.token, + }); + } + + if ( + resetToken && + (resetToken.isArchived || + resetToken.isObsolete || + resetToken.expiryDate < new Date()) + ) { + logger.error("Provided password reset token is obsolete"); + return res.status(StatusCodes.NOT_FOUND).json({ + message: "token is obsolete", + }); + } + + return res.status(StatusCodes.OK).json(resetToken); +} diff --git a/pages/api/frontend/v0.1/tokens/verification.ts b/pages/api/frontend/v0.1/tokens/verification.ts index 46d432f6..07e4210d 100644 --- a/pages/api/frontend/v0.1/tokens/verification.ts +++ b/pages/api/frontend/v0.1/tokens/verification.ts @@ -7,177 +7,17 @@ import { Logger } from "../../../../../util/logger"; require("dotenv").config(); +const logger = new Logger(__filename); + export default async function handler( req: NextApiRequest, res: NextApiResponse ) { - const logger = new Logger(__filename); - - const data = req.body; - - const { token, email } = data; - switch (req.method) { case "POST": - var id = -1; - if (token) { - logger.log(`Looking up verification token`); - const verificationToken = await prisma.verificationToken.findFirst({ - where: { - token: token, - }, - }); - - id = Number(verificationToken?.userId); - } else if (email) { - logger.log(`Looking up user with email '${email}'`); - var userByEmail = await prisma.user.findFirst({ - where: { - email: email, - }, - }); - - id = Number(userByEmail?.id); - } else { - logger.error("No token or email provided"); - return res - .status(StatusCodes.BAD_REQUEST) - .json({ message: "No token or email provided" }); - } - - logger.log(`Looking up user with id '${id}'`); - const unverifiedUser = await prisma.user.findUnique({ - where: { - id: id, - }, - }); - - if (unverifiedUser?.isVerified) { - logger.error("User already verified"); - return res - .status(StatusCodes.BAD_REQUEST) - .json({ message: "User already verified!" }); - } - - const generatedToken = generateToken(); - - var expiryDate = new Date(); - // set expiryDate one week from now - expiryDate.setDate(expiryDate.getDate() + 7); - - logger.log( - `Updating previos verification tokens for user with id '${id}' as obsolete` - ); - await prisma.verificationToken.updateMany({ - where: { - userId: id, - isObsolete: false, - }, - data: { - isObsolete: true, - }, - }); - - logger.log(`Create new verification token for user with id '${id}'`); - const verificationToken = await prisma.verificationToken.create({ - data: { - userId: id, - token: generatedToken, - expiryDate: expiryDate, - isArchived: false, - }, - }); - - sendTokenPerMail( - unverifiedUser?.email as string, - unverifiedUser?.firstName as string, - verificationToken.token, - MailType.Verification - ); - - return res - .status(StatusCodes.OK) - .json({ message: "Link was successfully resend!" }); - + return postHandler(req, res); case "PUT": - if (!token) { - logger.error("No token provided"); - return res - .status(StatusCodes.BAD_REQUEST) - .json({ message: "No token provided" }); - } - - logger.log(`Looking up verification token`); - const lookupToken = await prisma.verificationToken.findFirst({ - where: { - token: token, - }, - }); - - if (!lookupToken) { - logger.error("Provided verification token not found"); - return res - .status(StatusCodes.NOT_FOUND) - .json({ message: "Verification token not found" }); - } - - if (lookupToken && lookupToken.isArchived) { - logger.error("User already verified"); - return res - .status(StatusCodes.BAD_REQUEST) - .json({ message: "User already verified" }); - } - - if (lookupToken && lookupToken.isObsolete) { - logger.error("Verification token is obsolete"); - return res - .status(StatusCodes.BAD_REQUEST) - .json({ message: "Verification token is obsolete" }); - } - - logger.log(`Looking up user with id '${lookupToken.userId}'`); - const user = await prisma.user.findUnique({ - where: { - id: lookupToken.userId, - }, - }); - - if (user?.isVerified) { - logger.error("User already verified"); - return res - .status(StatusCodes.BAD_REQUEST) - .json({ message: "User already verified" }); - } - - // if token expired and user not verified, throw error - if (lookupToken && lookupToken.expiryDate < new Date()) { - logger.error("Provided verification token has expired"); - return res - .status(StatusCodes.BAD_REQUEST) - .json({ message: "Verification token has expired" }); - } - - logger.log(`Updating user with id '${lookupToken.userId}' as verified`); - await prisma.user.update({ - where: { - id: lookupToken.userId, - }, - data: { - isVerified: true, - }, - }); - - logger.log(`Updating verification token as archived`); - await prisma.verificationToken.update({ - where: { - id: lookupToken.id, - }, - data: { - isArchived: true, - }, - }); - - return res.status(StatusCodes.OK).json(user?.email); + return putHandler(req, res); default: return res @@ -185,3 +25,170 @@ export default async function handler( .json({ message: "method not allowed" }); } } + +async function postHandler(req: NextApiRequest, res: NextApiResponse) { + var id = -1; + const { token, email } = req.body; + + if (token) { + logger.log(`Looking up verification token`); + const verificationToken = await prisma.verificationToken.findFirst({ + where: { + token: token, + }, + }); + + id = Number(verificationToken?.userId); + } else if (email) { + logger.log(`Looking up user with email '${email}'`); + var userByEmail = await prisma.user.findFirst({ + where: { + email: email, + }, + }); + + id = Number(userByEmail?.id); + } else { + logger.error("No token or email provided"); + return res + .status(StatusCodes.BAD_REQUEST) + .json({ message: "No token or email provided" }); + } + + logger.log(`Looking up user with id '${id}'`); + const unverifiedUser = await prisma.user.findUnique({ + where: { + id: id, + }, + }); + + if (unverifiedUser?.isVerified) { + logger.error("User already verified"); + return res + .status(StatusCodes.BAD_REQUEST) + .json({ message: "User already verified!" }); + } + + const generatedToken = generateToken(); + + var expiryDate = new Date(); + // set expiryDate one week from now + expiryDate.setDate(expiryDate.getDate() + 7); + + logger.log( + `Updating previos verification tokens for user with id '${id}' as obsolete` + ); + await prisma.verificationToken.updateMany({ + where: { + userId: id, + isObsolete: false, + }, + data: { + isObsolete: true, + }, + }); + + logger.log(`Create new verification token for user with id '${id}'`); + const verificationToken = await prisma.verificationToken.create({ + data: { + userId: id, + token: generatedToken, + expiryDate: expiryDate, + isArchived: false, + }, + }); + + sendTokenPerMail( + unverifiedUser?.email as string, + unverifiedUser?.firstName as string, + verificationToken.token, + MailType.Verification + ); + + return res + .status(StatusCodes.OK) + .json({ message: "Link was successfully resend!" }); +} + +async function putHandler(req: NextApiRequest, res: NextApiResponse) { + const { token } = req.body; + + if (!token) { + logger.error("No token provided"); + return res + .status(StatusCodes.BAD_REQUEST) + .json({ message: "No token provided" }); + } + + logger.log(`Looking up verification token`); + const lookupToken = await prisma.verificationToken.findFirst({ + where: { + token: token, + }, + }); + + if (!lookupToken) { + logger.error("Provided verification token not found"); + return res + .status(StatusCodes.NOT_FOUND) + .json({ message: "Verification token not found" }); + } + + if (lookupToken && lookupToken.isArchived) { + logger.error("User already verified"); + return res + .status(StatusCodes.BAD_REQUEST) + .json({ message: "User already verified" }); + } + + if (lookupToken && lookupToken.isObsolete) { + logger.error("Verification token is obsolete"); + return res + .status(StatusCodes.BAD_REQUEST) + .json({ message: "Verification token is obsolete" }); + } + + logger.log(`Looking up user with id '${lookupToken.userId}'`); + const user = await prisma.user.findUnique({ + where: { + id: lookupToken.userId, + }, + }); + + if (user?.isVerified) { + logger.error("User already verified"); + return res + .status(StatusCodes.BAD_REQUEST) + .json({ message: "User already verified" }); + } + + // if token expired and user not verified, throw error + if (lookupToken && lookupToken.expiryDate < new Date()) { + logger.error("Provided verification token has expired"); + return res + .status(StatusCodes.BAD_REQUEST) + .json({ message: "Verification token has expired" }); + } + + logger.log(`Updating user with id '${lookupToken.userId}' as verified`); + await prisma.user.update({ + where: { + id: lookupToken.userId, + }, + data: { + isVerified: true, + }, + }); + + logger.log(`Updating verification token as archived`); + await prisma.verificationToken.update({ + where: { + id: lookupToken.id, + }, + data: { + isArchived: true, + }, + }); + + return res.status(StatusCodes.OK).json(user?.email); +} diff --git a/pages/api/frontend/v0.1/users.ts b/pages/api/frontend/v0.1/users.ts index 935d1e6e..54c2409b 100644 --- a/pages/api/frontend/v0.1/users.ts +++ b/pages/api/frontend/v0.1/users.ts @@ -1,151 +1,164 @@ import { StatusCodes } from "http-status-codes"; import type { NextApiRequest, NextApiResponse } from "next"; import prisma from "../../../../lib/services/db"; -import { getUserFromRequest } from "../../../../util/auth"; +import { User } from "../../../../models/user"; +import { authenticatedHandler } from "../../../../util/authenticatedHandler"; import { Logger } from "../../../../util/logger"; -export default async function handler( +const logger = new Logger(__filename); + +export default function handler(req: NextApiRequest, res: NextApiResponse) { + return authenticatedHandler( + req, + res, + { method: "basic" }, + async (req, res, user) => { + switch (req.method) { + case "GET": + return getHandler(req, res, user); + case "DELETE": + return deleteHandler(req, res, user); + default: + return res + .status(StatusCodes.METHOD_NOT_ALLOWED) + .json({ message: "Method not allowed" }); + } + } + ); +} + +async function getHandler( req: NextApiRequest, - res: NextApiResponse + res: NextApiResponse, + user: User ) { - const logger = new Logger(__filename); - - const user = await getUserFromRequest(req, res); - - if (!user) { - return; + logger.log(`Looking up user with id '${user.id}'`); + const userFromDb = await prisma.user.findFirst({ + where: { + id: Number(user.id), + NOT: { + isDeleted: true, + }, + }, + }); + + if (!userFromDb || (userFromDb && !userFromDb.id)) { + logger.error(`No user found with id '${user.id}'`); + return res + .status(StatusCodes.BAD_REQUEST) + .json({ message: "User not found!" }); } - switch (req.method) { - case "GET": - logger.log(`Looking up user with id '${user.id}'`); - const userFromDb = await prisma.user.findFirst({ - where: { - id: Number(user.id), - NOT: { - isDeleted: true, - }, - }, - }); - - if (!userFromDb || (userFromDb && !userFromDb.id)) { - logger.error(`No user found with id '${user.id}'`); - return res - .status(StatusCodes.BAD_REQUEST) - .json({ message: "User not found!" }); - } - - return res.status(StatusCodes.CREATED).json({ - email: userFromDb.email, - firstName: userFromDb.firstName, - lastName: userFromDb.lastName, - }); + return res.status(StatusCodes.CREATED).json({ + email: userFromDb.email, + firstName: userFromDb.firstName, + lastName: userFromDb.lastName, + }); +} - case "DELETE": - const userEmail = user.email as string; +async function deleteHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User +) { + const userEmail = user.email as string; + + logger.log(`Looking up user with email '${userEmail}'`); + const userByEmail = await prisma.user.findFirst({ + where: { + email: userEmail, + NOT: { + isDeleted: true, + }, + }, + }); + + if (!userByEmail || (userByEmail && !userByEmail.id)) { + logger.error(`No user found with email '${userEmail}'`); + return res + .status(StatusCodes.BAD_REQUEST) + .json({ message: "User not found!" }); + } - logger.log(`Looking up user with email '${userEmail}'`); - const userByEmail = await prisma.user.findFirst({ + logger.log( + `Looking up organisations that user with id '${userByEmail.id}' is part of` + ); + // check if user is qualified to be deleted + const userInOrgs = await prisma.usersInOrganisations.findMany({ + where: { + user: { + id: userByEmail.id, + }, + role: "ADMIN", + }, + include: { + org: true, + }, + }); + + let orgsToDeleteFirst: Array = []; + + logger.log( + `Looking up other admins in organisations that user with id '${userByEmail.id}' is part of` + ); + await Promise.all( + userInOrgs.map(async (userInOrg) => { + const otherAdminsInOrg = await prisma.usersInOrganisations.findMany({ where: { - email: userEmail, + orgId: userInOrg.orgId, + role: "ADMIN", NOT: { - isDeleted: true, - }, - }, - }); - - if (!userByEmail || (userByEmail && !userByEmail.id)) { - logger.error(`No user found with email '${userEmail}'`); - return res - .status(StatusCodes.BAD_REQUEST) - .json({ message: "User not found!" }); - } - - logger.log( - `Looking up organisations that user with id '${userByEmail.id}' is part of` - ); - // check if user is qualified to be deleted - const userInOrgs = await prisma.usersInOrganisations.findMany({ - where: { - user: { - id: userByEmail.id, + userId: userInOrg.userId, }, - role: "ADMIN", - }, - include: { - org: true, }, }); - let orgsToDeleteFirst: Array = []; - - logger.log( - `Looking up other admins in organisations that user with id '${userByEmail.id}' is part of` - ); - await Promise.all( - userInOrgs.map(async (userInOrg) => { - const otherAdminsInOrg = await prisma.usersInOrganisations.findMany({ - where: { - orgId: userInOrg.orgId, - role: "ADMIN", - NOT: { - userId: userInOrg.userId, - }, - }, - }); - - if (Array.isArray(otherAdminsInOrg) && !otherAdminsInOrg.length) { - orgsToDeleteFirst.push(userInOrg.org.name); - } - }) - ); - - if (orgsToDeleteFirst.length) { - logger.error( - `Before deleting user profile of user with id '${ - userByEmail.id - }', these organisations have to be deleted first: ${JSON.stringify( - orgsToDeleteFirst - )}` - ); - return res.status(StatusCodes.BAD_REQUEST).json({ - message: - "You have to delete these organisations first: " + - JSON.stringify(orgsToDeleteFirst), - }); + if (Array.isArray(otherAdminsInOrg) && !otherAdminsInOrg.length) { + orgsToDeleteFirst.push(userInOrg.org.name); } - - logger.log(`Updating user with id '${userByEmail.id}' as deleted`); - // if user qualifies to be deleted: - const deletedUser = await prisma.user.update({ - where: { - id: userByEmail.id, - }, - data: { - email: null, - firstName: null, - lastName: null, - password: null, - salt: null, - isDeleted: true, - }, - }); - - logger.log( - `Deleting relations for all organisations that user with id '${deletedUser.id}' is in` - ); - // delete user from organisations - await prisma.usersInOrganisations.deleteMany({ - where: { - userId: deletedUser.id, - }, - }); - - return res.status(StatusCodes.CREATED).json({ deletedUser }); - - default: - return res - .status(StatusCodes.METHOD_NOT_ALLOWED) - .json({ message: "method not allowed" }); + }) + ); + + if (orgsToDeleteFirst.length) { + logger.error( + `Before deleting user profile of user with id '${ + userByEmail.id + }', these organisations have to be deleted first: ${JSON.stringify( + orgsToDeleteFirst + )}` + ); + return res.status(StatusCodes.BAD_REQUEST).json({ + message: + "You have to delete these organisations first: " + + JSON.stringify(orgsToDeleteFirst), + }); } + + logger.log(`Updating user with id '${userByEmail.id}' as deleted`); + // if user qualifies to be deleted: + const deletedUser = await prisma.user.update({ + where: { + id: userByEmail.id, + }, + data: { + email: null, + firstName: null, + lastName: null, + password: null, + salt: null, + isDeleted: true, + }, + }); + + logger.log( + `Deleting relations for all organisations that user with id '${deletedUser.id}' is in` + ); + // delete user from organisations + await prisma.usersInOrganisations.deleteMany({ + where: { + userId: deletedUser.id, + }, + }); + + return res.status(StatusCodes.CREATED).json({ deletedUser }); } diff --git a/pages/api/frontend/v0.1/users/emailChange.ts b/pages/api/frontend/v0.1/users/emailChange.ts index 0ebeda1a..f367194b 100644 --- a/pages/api/frontend/v0.1/users/emailChange.ts +++ b/pages/api/frontend/v0.1/users/emailChange.ts @@ -9,159 +9,17 @@ import { } from "../../../../../util/auth"; import { Logger } from "../../../../../util/logger"; +const logger = new Logger(__filename); + export default async function handler( req: NextApiRequest, res: NextApiResponse ) { - const logger = new Logger(__filename); - - const data = req.body; - - const { emailNew, token } = data; - switch (req.method) { case "POST": - const user = await getUserFromRequest(req, res); - - if (!user) { - return; - } - - if (!emailNew || !emailNew.includes("@")) { - logger.error("Email is not valid"); - return res - .status(StatusCodes.UNPROCESSABLE_ENTITY) - .json({ message: "Invalid data - email not valid" }); - } - - logger.log(`Looking up user with email '${user.email}'`); - const userByEmail = await prisma.user.findFirst({ - where: { - email: user.email, - NOT: { - isDeleted: true, - }, - }, - }); - - if (!userByEmail || (userByEmail && !userByEmail.id)) { - logger.error(`No user found with email '${user.email}'`); - return res - .status(StatusCodes.BAD_REQUEST) - .json({ message: "User not found!" }); - } - - logger.log(`Looking up user with new email '${emailNew}'`); - const userWithNewEmail = await prisma.user.findFirst({ - where: { - email: emailNew, - NOT: { - isDeleted: true, - }, - }, - }); - - if (userWithNewEmail) { - logger.error(`New email '${emailNew}' is already taken`); - return res - .status(StatusCodes.BAD_REQUEST) - .json({ message: "Email address not available!" }); - } - - const generatedToken = generateToken(); - - var expiryDate = new Date(); - // set expiryDate one hour from now - expiryDate.setTime(expiryDate.getTime() + 60 * 60 * 1000); - - logger.log(`Updating previous email change tokens obsolete`); - await prisma.emailChangeToken.updateMany({ - where: { - userId: user.id, - isObsolete: false, - }, - data: { - isObsolete: true, - }, - }); - - logger.log( - `Creating new email change token for user with current email '${user.email}'` - ); - const emailToken = await prisma.emailChangeToken.create({ - data: { - userId: user.id, - token: generatedToken, - expiryDate: expiryDate, - newEmail: emailNew, - currentEmail: user.email, - }, - }); - - sendTokenPerMail( - emailToken.newEmail as string, - userByEmail.firstName as string, - generatedToken, - MailType.ChangeEmail - ); - - return res.status(StatusCodes.CREATED).json(user.email); - + return postHandler(req, res); case "PUT": - logger.log(`Looking up email change token`); - const lookupToken = await prisma.emailChangeToken.findFirst({ - where: { - token: token, - }, - }); - - if (!lookupToken) { - logger.error(`Provided email change token not found`); - return res - .status(StatusCodes.NOT_FOUND) - .json({ message: "Email change token not found" }); - } - - if ( - lookupToken && - (lookupToken.isArchived || - lookupToken.isObsolete || - lookupToken.expiryDate < new Date()) - ) { - logger.error(`Email change token is obsolete`); - return res - .status(StatusCodes.BAD_REQUEST) - .json({ message: "Email change token is obsolete!" }); - } - - logger.log(`Updating email of user with id '${lookupToken.userId}'`); - await prisma.user.update({ - where: { - id: lookupToken.userId, - }, - data: { - email: lookupToken.newEmail, - }, - }); - - sendTokenPerMail( - lookupToken.currentEmail as string, - "OnLaunch user", - "", - MailType.EmailChanged - ); - - logger.log(`Updating email change token as archived`); - await prisma.emailChangeToken.update({ - where: { - id: lookupToken.id, - }, - data: { - isArchived: true, - }, - }); - - return res.status(StatusCodes.OK).json(lookupToken.newEmail); + return putHandler(req, res); default: return res @@ -169,3 +27,152 @@ export default async function handler( .json({ message: "method not allowed" }); } } + +async function postHandler(req: NextApiRequest, res: NextApiResponse) { + const { emailNew } = req.body; + + const user = await getUserFromRequest(req, res); + + if (!user) { + return; + } + + if (!emailNew || !emailNew.includes("@")) { + logger.error("Email is not valid"); + return res + .status(StatusCodes.UNPROCESSABLE_ENTITY) + .json({ message: "Invalid data - email not valid" }); + } + + logger.log(`Looking up user with email '${user.email}'`); + const userByEmail = await prisma.user.findFirst({ + where: { + email: user.email, + NOT: { + isDeleted: true, + }, + }, + }); + + if (!userByEmail || (userByEmail && !userByEmail.id)) { + logger.error(`No user found with email '${user.email}'`); + return res + .status(StatusCodes.BAD_REQUEST) + .json({ message: "User not found!" }); + } + + logger.log(`Looking up user with new email '${emailNew}'`); + const userWithNewEmail = await prisma.user.findFirst({ + where: { + email: emailNew, + NOT: { + isDeleted: true, + }, + }, + }); + + if (userWithNewEmail) { + logger.error(`New email '${emailNew}' is already taken`); + return res + .status(StatusCodes.BAD_REQUEST) + .json({ message: "Email address not available!" }); + } + + const generatedToken = generateToken(); + + var expiryDate = new Date(); + // set expiryDate one hour from now + expiryDate.setTime(expiryDate.getTime() + 60 * 60 * 1000); + + logger.log(`Updating previous email change tokens obsolete`); + await prisma.emailChangeToken.updateMany({ + where: { + userId: user.id, + isObsolete: false, + }, + data: { + isObsolete: true, + }, + }); + + logger.log( + `Creating new email change token for user with current email '${user.email}'` + ); + const emailToken = await prisma.emailChangeToken.create({ + data: { + userId: user.id, + token: generatedToken, + expiryDate: expiryDate, + newEmail: emailNew, + currentEmail: user.email, + }, + }); + + sendTokenPerMail( + emailToken.newEmail as string, + userByEmail.firstName as string, + generatedToken, + MailType.ChangeEmail + ); + + return res.status(StatusCodes.CREATED).json(user.email); +} + +async function putHandler(req: NextApiRequest, res: NextApiResponse) { + const { token } = req.body; + + logger.log(`Looking up email change token`); + const lookupToken = await prisma.emailChangeToken.findFirst({ + where: { + token: token, + }, + }); + + if (!lookupToken) { + logger.error(`Provided email change token not found`); + return res + .status(StatusCodes.NOT_FOUND) + .json({ message: "Email change token not found" }); + } + + if ( + lookupToken && + (lookupToken.isArchived || + lookupToken.isObsolete || + lookupToken.expiryDate < new Date()) + ) { + logger.error(`Email change token is obsolete`); + return res + .status(StatusCodes.BAD_REQUEST) + .json({ message: "Email change token is obsolete!" }); + } + + logger.log(`Updating email of user with id '${lookupToken.userId}'`); + await prisma.user.update({ + where: { + id: lookupToken.userId, + }, + data: { + email: lookupToken.newEmail, + }, + }); + + sendTokenPerMail( + lookupToken.currentEmail as string, + "OnLaunch user", + "", + MailType.EmailChanged + ); + + logger.log(`Updating email change token as archived`); + await prisma.emailChangeToken.update({ + where: { + id: lookupToken.id, + }, + data: { + isArchived: true, + }, + }); + + return res.status(StatusCodes.OK).json(lookupToken.newEmail); +} diff --git a/pages/api/frontend/v0.1/users/passwordChange.ts b/pages/api/frontend/v0.1/users/passwordChange.ts index 02de0cd9..26362f29 100644 --- a/pages/api/frontend/v0.1/users/passwordChange.ts +++ b/pages/api/frontend/v0.1/users/passwordChange.ts @@ -1,91 +1,94 @@ import { StatusCodes } from "http-status-codes"; import type { NextApiRequest, NextApiResponse } from "next"; import prisma from "../../../../../lib/services/db"; +import { User } from "../../../../../models/user"; import { - getUserFromRequest, hashAndSaltPassword, validatePassword, verifyPassword, } from "../../../../../util/auth"; +import { authenticatedHandler } from "../../../../../util/authenticatedHandler"; import { Logger } from "../../../../../util/logger"; -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - const logger = new Logger(__filename); - - const data = req.body; - - const { password, passwordOld } = data; +const logger = new Logger(__filename); - switch (req.method) { - case "PUT": - const user = await getUserFromRequest(req, res); - - if (!user) { - return; +export default function handler(req: NextApiRequest, res: NextApiResponse) { + return authenticatedHandler( + req, + res, + { method: "basic" }, + async (req, res, user) => { + switch (req.method) { + case "PUT": + return putHandler(req, res, user); + default: + return res + .status(StatusCodes.METHOD_NOT_ALLOWED) + .json({ message: "Method not allowed" }); } + } + ); +} - const id = user.id; +async function putHandler( + req: NextApiRequest, + res: NextApiResponse, + user: User +) { + const { password, passwordOld } = req.body; - if (!(await validatePassword(password))) { - logger.error("New password is too short"); - return res.status(StatusCodes.UNPROCESSABLE_ENTITY).json({ - message: - "Invalid data - new password consists of less than 8 characters", - }); - } + const id = user.id; - logger.log(`Looking up user with id '${id}'`); - const userById = await prisma.user.findFirst({ - where: { - id: Number(id), - NOT: { - isDeleted: true, - }, - }, - }); + if (!(await validatePassword(password))) { + logger.error("New password is too short"); + return res.status(StatusCodes.UNPROCESSABLE_ENTITY).json({ + message: "Invalid data - new password consists of less than 8 characters", + }); + } - if (!userById || (userById && !userById.id)) { - logger.error(`No user found with id '${id}'`); - return res - .status(StatusCodes.BAD_REQUEST) - .json({ message: "User not found!" }); - } + logger.log(`Looking up user with id '${id}'`); + const userById = await prisma.user.findFirst({ + where: { + id: Number(id), + NOT: { + isDeleted: true, + }, + }, + }); - if ( - !(await verifyPassword( - passwordOld, - userById.salt as string, - userById.password as string - )) - ) { - logger.error("Current password is wrong"); - return res - .status(StatusCodes.BAD_REQUEST) - .json({ message: "Current password is wrong" }); - } + if (!userById || (userById && !userById.id)) { + logger.error(`No user found with id '${id}'`); + return res + .status(StatusCodes.BAD_REQUEST) + .json({ message: "User not found!" }); + } - const { hashedSaltedPassword: newHashedSaltedPassword, salt: newSalt } = - await hashAndSaltPassword(password); + if ( + !(await verifyPassword( + passwordOld, + userById.salt as string, + userById.password as string + )) + ) { + logger.error("Current password is wrong"); + return res + .status(StatusCodes.BAD_REQUEST) + .json({ message: "Current password is wrong" }); + } - logger.log(`Updating password of user with id '${id}'`); - const updatedUser = await prisma.user.update({ - where: { - id: userById.id, - }, - data: { - password: newHashedSaltedPassword, - salt: newSalt, - }, - }); + const { hashedSaltedPassword: newHashedSaltedPassword, salt: newSalt } = + await hashAndSaltPassword(password); - return res.status(StatusCodes.CREATED).json(updatedUser.email); + logger.log(`Updating password of user with id '${id}'`); + const updatedUser = await prisma.user.update({ + where: { + id: userById.id, + }, + data: { + password: newHashedSaltedPassword, + salt: newSalt, + }, + }); - default: - return res - .status(StatusCodes.METHOD_NOT_ALLOWED) - .json({ message: "method not allowed" }); - } + return res.status(StatusCodes.CREATED).json(updatedUser.email); } diff --git a/pages/api/frontend/v0.1/users/register.ts b/pages/api/frontend/v0.1/users/register.ts index 47de9043..158246bd 100644 --- a/pages/api/frontend/v0.1/users/register.ts +++ b/pages/api/frontend/v0.1/users/register.ts @@ -11,100 +11,15 @@ import { } from "../../../../../util/auth"; import { Logger } from "../../../../../util/logger"; +const logger = new Logger(__filename); + export default async function handler( req: NextApiRequest, res: NextApiResponse ) { - const logger = new Logger(__filename); - - const config = loadConfig(); - switch (req.method) { case "POST": - const data = req.body; - - const { email, password, firstName, lastName } = data; - - if (!config.server.signup.isEnabled) { - logger.error("Signups are currently disabled"); - return res - .status(StatusCodes.METHOD_NOT_ALLOWED) - .json({ message: "Not allowed - signups are currently disabled!" }); - } - - if (!email || !email.includes("@")) { - logger.error("Provided email is not valid"); - return res - .status(StatusCodes.UNPROCESSABLE_ENTITY) - .json({ message: "Invalid data - email is not valid" }); - } - - if (!(await validatePassword(password))) { - logger.error("Provided password is too short"); - return res.status(StatusCodes.UNPROCESSABLE_ENTITY).json({ - message: "Invalid data - password consists of less than 8 characters", - }); - } - - logger.log(`Looking up user with email '${email}'`); - const lookupUser = await prisma.user.findFirst({ - where: { - email: email, - NOT: { - isDeleted: true, - }, - }, - }); - - if (lookupUser) { - logger.error(`Email '${email}' is already in use`); - return res - .status(StatusCodes.CONFLICT) - .json({ message: "Conflict - email already in use" }); - } - - const { hashedSaltedPassword, salt } = await hashAndSaltPassword( - password - ); - - logger.log(`Creating new user with email '${email}'`); - const createdUser = await prisma.user.create({ - data: { - email: email, - password: hashedSaltedPassword, - salt: salt, - firstName: firstName, - lastName: lastName, - isVerified: false, - }, - }); - - const generatedToken = generateToken(); - - var expiryDate = new Date(); - // set expiryDate one week from now - expiryDate.setDate(expiryDate.getDate() + 7); - - logger.log( - `Creating verification token for user with id '${createdUser.id}'` - ); - const verificationToken = await prisma.verificationToken.create({ - data: { - userId: createdUser.id, - token: generatedToken, - expiryDate: expiryDate, - isArchived: false, - }, - }); - - sendTokenPerMail( - createdUser.email as string, - createdUser.firstName as string, - verificationToken.token, - MailType.Verification - ); - - return res.status(StatusCodes.CREATED).json(email); + return postHandler(req, res); default: return res @@ -112,3 +27,90 @@ export default async function handler( .json({ message: "method not allowed" }); } } + +async function postHandler(req: NextApiRequest, res: NextApiResponse) { + const config = loadConfig(); + + const data = req.body; + + const { email, password, firstName, lastName } = data; + + if (!config.server.signup.isEnabled) { + logger.error("Signups are currently disabled"); + return res + .status(StatusCodes.METHOD_NOT_ALLOWED) + .json({ message: "Not allowed - signups are currently disabled!" }); + } + + if (!email || !email.includes("@")) { + logger.error("Provided email is not valid"); + return res + .status(StatusCodes.UNPROCESSABLE_ENTITY) + .json({ message: "Invalid data - email is not valid" }); + } + + if (!(await validatePassword(password))) { + logger.error("Provided password is too short"); + return res.status(StatusCodes.UNPROCESSABLE_ENTITY).json({ + message: "Invalid data - password consists of less than 8 characters", + }); + } + + logger.log(`Looking up user with email '${email}'`); + const lookupUser = await prisma.user.findFirst({ + where: { + email: email, + NOT: { + isDeleted: true, + }, + }, + }); + + if (lookupUser) { + logger.error(`Email '${email}' is already in use`); + return res + .status(StatusCodes.CONFLICT) + .json({ message: "Conflict - email already in use" }); + } + + const { hashedSaltedPassword, salt } = await hashAndSaltPassword(password); + + logger.log(`Creating new user with email '${email}'`); + const createdUser = await prisma.user.create({ + data: { + email: email, + password: hashedSaltedPassword, + salt: salt, + firstName: firstName, + lastName: lastName, + isVerified: false, + }, + }); + + const generatedToken = generateToken(); + + var expiryDate = new Date(); + // set expiryDate one week from now + expiryDate.setDate(expiryDate.getDate() + 7); + + logger.log( + `Creating verification token for user with id '${createdUser.id}'` + ); + const verificationToken = await prisma.verificationToken.create({ + data: { + userId: createdUser.id, + token: generatedToken, + expiryDate: expiryDate, + isArchived: false, + }, + }); + + sendTokenPerMail( + createdUser.email as string, + createdUser.firstName as string, + verificationToken.token, + MailType.Verification + ); + + return res.status(StatusCodes.CREATED).json(email); +} diff --git a/pages/api/v0.1/messages.ts b/pages/api/v0.1/messages.ts index 638e282f..b8f0efb6 100644 --- a/pages/api/v0.1/messages.ts +++ b/pages/api/v0.1/messages.ts @@ -27,6 +27,8 @@ interface ErrorObjectDto { message: string; } +const logger = new Logger(__filename); + /** * @swagger * tags: @@ -85,209 +87,212 @@ export default async function handler( req: NextApiRequest, res: NextApiResponse ) { - const logger = new Logger(__filename); + switch (req.method) { + case "GET": + return getHandler(req, res); + + default: + return res + .status(StatusCodes.METHOD_NOT_ALLOWED) + .json({ message: "method not allowed" }); + } +} + +async function getHandler(req: NextApiRequest, res: NextApiResponse) { const config = loadConfig(); const FREE_SUB_REQUEST_LIMIT = config.server.freeSub.requestLimit; - switch (req.method) { - case "GET": - const publicKey = req.headers["x-api-key"] as string; + const publicKey = req.headers["x-api-key"] as string; - if (!publicKey) { - logger.error("No api key provided"); - return res - .status(StatusCodes.BAD_REQUEST) - .json({ message: "no api key provided" }); - } + if (!publicKey) { + logger.error("No api key provided"); + return res + .status(StatusCodes.BAD_REQUEST) + .json({ message: "no api key provided" }); + } - // Get app, org, (appIds) and sub information to retrieve product limit - logger.log(`Looking up api key '${publicKey as string}'`); - const app = await prisma.app.findFirst({ - where: { - publicKey: publicKey, - organisation: { - isDeleted: false, - }, - }, + // Get app, org, (appIds) and sub information to retrieve product limit + logger.log(`Looking up api key '${publicKey as string}'`); + const app = await prisma.app.findFirst({ + where: { + publicKey: publicKey, + organisation: { + isDeleted: false, + }, + }, + include: { + organisation: { include: { - organisation: { + subs: { + where: { + isDeleted: false, + }, include: { - subs: { - where: { - isDeleted: false, - }, - include: { - subItems: true, - }, - }, - apps: true, + subItems: true, }, }, + apps: true, }, - }); + }, + }, + }); - if (!app) { - logger.log(`No app found for api key '${publicKey as string}'`); - return res - .status(StatusCodes.NOT_FOUND) - .json({ message: "no app found for api key" }); - } + if (!app) { + logger.log(`No app found for api key '${publicKey as string}'`); + return res + .status(StatusCodes.NOT_FOUND) + .json({ message: "no app found for api key" }); + } - // Start of quota limitation - if (config.server.stripeConfig.isEnabled) { - try { - const products = await getProducts(); + // Start of quota limitation + if (config.server.stripeConfig.isEnabled) { + try { + const products = await getProducts(); - // Check if there is a subItem with isMetered set to true - // Metered subItems do not have a limit - let hasMeteredSubItem = false; - // There should be 0 or 1 sub - let subFromDb = app?.organisation?.subs[0]; + // Check if there is a subItem with isMetered set to true + // Metered subItems do not have a limit + let hasMeteredSubItem = false; + // There should be 0 or 1 sub + let subFromDb = app?.organisation?.subs[0]; - if (app?.organisation?.subs) { - for (const sub of app.organisation.subs) { - if (sub.subItems?.some((subItem) => subItem.metered === true)) { - hasMeteredSubItem = true; - break; - } - } + if (app?.organisation?.subs) { + for (const sub of app.organisation.subs) { + if (sub.subItems?.some((subItem) => subItem.metered === true)) { + hasMeteredSubItem = true; + break; } + } + } - // If not metered, check for the limit - if (!hasMeteredSubItem) { - let countingStartDate = new Date(); - - // Free version counts back plainly one month - if (!subFromDb) { - countingStartDate.setMonth(countingStartDate.getMonth() - 1); - } else { - // use current period start of active subscription - countingStartDate = subFromDb.currentPeriodStart; - } + // If not metered, check for the limit + if (!hasMeteredSubItem) { + let countingStartDate = new Date(); - // Prepare array of app ids of organisation - const appIds = app?.organisation?.apps?.map((app) => app.id) || []; + // Free version counts back plainly one month + if (!subFromDb) { + countingStartDate.setMonth(countingStartDate.getMonth() - 1); + } else { + // use current period start of active subscription + countingStartDate = subFromDb.currentPeriodStart; + } - // Count requests across all apps of the org - const requestCount = await prisma.loggedApiRequests.count({ - where: { - appId: { - in: appIds, - }, - createdAt: { - gte: countingStartDate, - }, - }, - }); - logger.log( - `Request count for org with id '${app.orgId}' is ${requestCount}` - ); + // Prepare array of app ids of organisation + const appIds = app?.organisation?.apps?.map((app) => app.id) || []; - let isLimitReached = false; + // Count requests across all apps of the org + const requestCount = await prisma.loggedApiRequests.count({ + where: { + appId: { + in: appIds, + }, + createdAt: { + gte: countingStartDate, + }, + }, + }); + logger.log( + `Request count for org with id '${app.orgId}' is ${requestCount}` + ); - // Check whether quota/limit for the request has been met (active subscription) - if (subFromDb) { - const targetProduct = products.find( - (product: { id: string | undefined }) => - product.id === subFromDb?.subItems[0].productId - ); + let isLimitReached = false; - if (!targetProduct) { - logger.error( - `No product found for org with id '${app.orgId}' and active sub with id '${subFromDb.subId}'` - ); - return res - .status(StatusCodes.INTERNAL_SERVER_ERROR) - .json({ message: "Please try again later" }); - } + // Check whether quota/limit for the request has been met (active subscription) + if (subFromDb) { + const targetProduct = products.find( + (product: { id: string | undefined }) => + product.id === subFromDb?.subItems[0].productId + ); - logger.log( - `Request limit for org with id '${app.orgId}' is ${targetProduct.requests}` - ); - if (requestCount >= Number(targetProduct.requests)) { - isLimitReached = true; - } - } else if (!subFromDb && requestCount >= FREE_SUB_REQUEST_LIMIT) { - // Check quota/limit for free version - isLimitReached = true; - } + if (!targetProduct) { + logger.error( + `No product found for org with id '${app.orgId}' and active sub with id '${subFromDb.subId}'` + ); + return res + .status(StatusCodes.INTERNAL_SERVER_ERROR) + .json({ message: "Please try again later" }); + } - // Return error if limit has been reached and the request cannot be served - if (isLimitReached) { - logger.log( - `The limit has been currently reached for org with id '${app?.orgId}'` - ); - return res.status(StatusCodes.PAYMENT_REQUIRED).json({ - message: "The limit for the current abo has been reached.", - }); - } + logger.log( + `Request limit for org with id '${app.orgId}' is ${targetProduct.requests}` + ); + if (requestCount >= Number(targetProduct.requests)) { + isLimitReached = true; } - } catch (error: any) { - return res - .status(StatusCodes.INTERNAL_SERVER_ERROR) - .json({ message: error.message }); + } else if (!subFromDb && requestCount >= FREE_SUB_REQUEST_LIMIT) { + // Check quota/limit for free version + isLimitReached = true; + } + + // Return error if limit has been reached and the request cannot be served + if (isLimitReached) { + logger.log( + `The limit has been currently reached for org with id '${app?.orgId}'` + ); + return res.status(StatusCodes.PAYMENT_REQUIRED).json({ + message: "The limit for the current abo has been reached.", + }); } } + } catch (error: any) { + return res + .status(StatusCodes.INTERNAL_SERVER_ERROR) + .json({ message: error.message }); + } + } - logger.log(`Looking up all messages for app with id '${app.id}'`); - const allMessages = await prisma.message.findMany({ - include: { - actions: true, + logger.log(`Looking up all messages for app with id '${app.id}'`); + const allMessages = await prisma.message.findMany({ + include: { + actions: true, + }, + where: { + AND: [ + { + appId: app.id, }, - where: { - AND: [ - { - appId: app.id, - }, - { - startDate: { - lte: new Date(), - }, - }, - { - endDate: { - gte: new Date(), - }, - }, - ], + { + startDate: { + lte: new Date(), + }, + }, + { + endDate: { + gte: new Date(), + }, }, - }); + ], + }, + }); - const ip = requestIp.getClientIp(req); + const ip = requestIp.getClientIp(req); - // logging the api requests after checking if the app exists, so it is only logged when the request could successfully be served so far - // as logged requests are used for tracking, only for successful requests should be tracked - logger.log( - `Creating logged API request for ip '${ip}' and app with id ${app.id} and public key ${publicKey}` - ); - await prisma.loggedApiRequests.create({ - data: { - ip: ip as string, - appId: app.id, - publicKey: publicKey, - }, - }); + // logging the api requests after checking if the app exists, so it is only logged when the request could successfully be served so far + // as logged requests are used for tracking, only for successful requests should be tracked + logger.log( + `Creating logged API request for ip '${ip}' and app with id ${app.id} and public key ${publicKey}` + ); + await prisma.loggedApiRequests.create({ + data: { + ip: ip as string, + appId: app.id, + publicKey: publicKey, + }, + }); - return res.status(StatusCodes.OK).json( - allMessages.map((message): ResponseDto => { + return res.status(StatusCodes.OK).json( + allMessages.map((message): ResponseDto => { + return { + id: message.id, + blocking: message.blocking, + title: message.title, + body: message.body, + actions: message.actions.map((action): ActionDto => { return { - id: message.id, - blocking: message.blocking, - title: message.title, - body: message.body, - actions: message.actions.map((action): ActionDto => { - return { - actionType: action.actionType as ActionType, - title: action.title, - }; - }), + actionType: action.actionType as ActionType, + title: action.title, }; - }) - ); - - default: - return res - .status(StatusCodes.METHOD_NOT_ALLOWED) - .json({ message: "method not allowed" }); - } + }), + }; + }) + ); } diff --git a/util/adminApi/auth.ts b/util/adminApi/auth.ts index 6b55a6aa..09d231e8 100644 --- a/util/adminApi/auth.ts +++ b/util/adminApi/auth.ts @@ -1,17 +1,10 @@ import { StatusCodes } from "http-status-codes"; import type { NextApiRequest } from "next"; import prisma from "../../lib/services/db"; +import { AuthResult } from "../../models/authResult"; import { Logger } from "../logger"; import { decodeToken } from "./tokenDecoding"; -interface AuthResult { - success: boolean; - authToken?: string; - id?: number; - statusCode: number; - errorMessage?: string; -} - export async function authenticate( req: NextApiRequest, type: string diff --git a/util/authenticatedHandler.ts b/util/authenticatedHandler.ts new file mode 100644 index 00000000..5b2543d2 --- /dev/null +++ b/util/authenticatedHandler.ts @@ -0,0 +1,36 @@ +import { NextApiRequest, NextApiResponse } from "next"; +import { User } from "../models/user"; +import { getUserFromRequest, getUserWithRoleFromRequest } from "./auth"; + +// Defines types for the handler to know which authentication method to use +type AuthMethod = "withRole" | "basic"; + +interface AuthenticatedHandlerOptions { + method: AuthMethod; +} + +export async function authenticatedHandler( + req: NextApiRequest, + res: NextApiResponse, + options: AuthenticatedHandlerOptions, + handler: ( + req: NextApiRequest, + res: NextApiResponse, + user: User + ) => Promise +): Promise { + let user; + + if (options.method === "withRole") { + user = await getUserWithRoleFromRequest(req, res); + } else { + user = await getUserFromRequest(req, res); + } + + if (!user) { + // The response is already set in the above called function getUser(WithRole)FromRequest + return; + } + + return handler(req, res, user); +}