mirror of
https://github.com/detleph/server.git
synced 2026-09-04 08:36:06 +02:00
Merge branch 'dev' into feature-email-verification
This commit is contained in:
@@ -50,6 +50,7 @@ if [ "$RECREATE" = true ]; then
|
|||||||
--network detleph_server_default \
|
--network detleph_server_default \
|
||||||
-e DATABASE_PASSWORD=server \
|
-e DATABASE_PASSWORD=server \
|
||||||
-e DATABASE_URL="postgresql://server:server@postgres:5432/management?schema=public" \
|
-e DATABASE_URL="postgresql://server:server@postgres:5432/management?schema=public" \
|
||||||
|
-e NODE_ENV="development" \
|
||||||
--entrypoint "/app/scripts/docker-entrypoint.dev.sh" \
|
--entrypoint "/app/scripts/docker-entrypoint.dev.sh" \
|
||||||
node
|
node
|
||||||
fi
|
fi
|
||||||
|
|||||||
+3
-1
@@ -30,11 +30,13 @@
|
|||||||
"dotenv": "^10.0.0",
|
"dotenv": "^10.0.0",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
|
"express-async-errors": "^3.1.1",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"mjml": "^4.11.0",
|
"mjml": "^4.11.0",
|
||||||
"nanoid": "^3.3.3",
|
"nanoid": "^3.3.3",
|
||||||
"nodemailer": "^6.7.0",
|
"nodemailer": "^6.7.0",
|
||||||
"redis": "^3.1.2"
|
"redis": "^3.1.2",
|
||||||
|
"winston": "^3.7.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/chai": "^4.2.22",
|
"@types/chai": "^4.2.22",
|
||||||
|
|||||||
@@ -19,13 +19,13 @@ export const getAllEvents = async (req: Request, res: Response) => {
|
|||||||
id: false,
|
id: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (events.length > 0)
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
type: "success",
|
type: "success",
|
||||||
payload: {
|
payload: {
|
||||||
events,
|
events,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getEvent = async (req: Request, res: Response) => {
|
export const getEvent = async (req: Request, res: Response) => {
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { NextFunction, Request, Response } from "express";
|
||||||
|
import logger from "../error/logger";
|
||||||
|
|
||||||
|
export default function debugLogger(req: Request, res: Response, next: NextFunction) {
|
||||||
|
// Only log when in development mode
|
||||||
|
if (process.env.NODE_ENV === "development") {
|
||||||
|
logger.debug(`Request to: ${req.url}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
export default class ForwardableError extends Error {
|
||||||
|
// If in different context
|
||||||
|
private readonly __id = "CUSTOM_ERROR";
|
||||||
|
|
||||||
|
public readonly status: number;
|
||||||
|
|
||||||
|
constructor(status: number, message: string) {
|
||||||
|
super(message);
|
||||||
|
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
static isForwardableError(error: any): error is ForwardableError {
|
||||||
|
return error.__id === "CUSTOM_ERROR";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import { PrismaClientUnknownRequestError } from "@prisma/client/runtime";
|
||||||
|
import { NextFunction, Request, Response } from "express";
|
||||||
|
import ForwardableError from "./ForwardableError";
|
||||||
|
import logger from "./logger";
|
||||||
|
|
||||||
|
const env = process.env.NODE_ENV || "production";
|
||||||
|
|
||||||
|
type ErrorHandler = (err: any, req: Request, res: Response) => boolean;
|
||||||
|
|
||||||
|
const customHandlers: ErrorHandler[] = [];
|
||||||
|
|
||||||
|
export function addCustomHandler(handler: ErrorHandler) {
|
||||||
|
customHandlers.push(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeCustomHandler(handler: ErrorHandler): Boolean {
|
||||||
|
const index = customHandlers.findIndex((h) => h === handler);
|
||||||
|
|
||||||
|
if (index < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
customHandlers.splice(index);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function defaultErrorHandler(err: any, req: Request, res: Response, next: NextFunction) {
|
||||||
|
for (const handler of customHandlers) {
|
||||||
|
// Run custom handler
|
||||||
|
if (handler(err, req, res)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ForwardableError.isForwardableError(err)) {
|
||||||
|
return res.status(err.status).json({
|
||||||
|
type: "error",
|
||||||
|
payload: {
|
||||||
|
message: err.message,
|
||||||
|
...(env === "development"
|
||||||
|
? {
|
||||||
|
stack: err.stack,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err instanceof PrismaClientUnknownRequestError) {
|
||||||
|
logger.warning(err);
|
||||||
|
|
||||||
|
return res.status(404).json({
|
||||||
|
type: "error",
|
||||||
|
payload: {
|
||||||
|
message: "An unknown error occured. This could be due to malformed IDs",
|
||||||
|
...(env === "development"
|
||||||
|
? {
|
||||||
|
prisma: err.message,
|
||||||
|
stack: err.stack,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.error("--- Unhandled error ---");
|
||||||
|
logger.error(err);
|
||||||
|
|
||||||
|
return res.status(err.status).json({
|
||||||
|
type: "error",
|
||||||
|
payload: {
|
||||||
|
message: err.message,
|
||||||
|
...(env === "development"
|
||||||
|
? {
|
||||||
|
notice: "This error was not caught by any handler, please add handling!",
|
||||||
|
stack: err.stack,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import winston, { createLogger } from "winston";
|
||||||
|
|
||||||
|
const {
|
||||||
|
format: { printf, colorize, combine, timestamp, json, errors, prettyPrint },
|
||||||
|
} = winston;
|
||||||
|
|
||||||
|
const defaultJsonFormat = combine(errors({ stack: true }), timestamp(), json({ space: 2 }));
|
||||||
|
|
||||||
|
const customCLIFormat = printf(({ level, message, label, timestamp, stack }) => {
|
||||||
|
let output = `${level}${stack ? `(1/2:message)` : ""} at ${timestamp}${label ? ` (#${label})` : ""}: ${message}${
|
||||||
|
stack ? "\n" : ""
|
||||||
|
}`;
|
||||||
|
|
||||||
|
if (stack) {
|
||||||
|
output += `\n${level}(2/2:stack)${label ? ` (#${label})` : ""}: ${stack}\n\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
});
|
||||||
|
|
||||||
|
export default createLogger({
|
||||||
|
levels: winston.config.syslog.levels,
|
||||||
|
format: combine(errors({ stack: true }), timestamp()),
|
||||||
|
transports: [
|
||||||
|
new winston.transports.Console({
|
||||||
|
level: "debug",
|
||||||
|
format: combine(timestamp(), colorize(), customCLIFormat),
|
||||||
|
}),
|
||||||
|
new winston.transports.File({
|
||||||
|
filename: "/app/error.log",
|
||||||
|
level: "error",
|
||||||
|
format: defaultJsonFormat,
|
||||||
|
}),
|
||||||
|
new winston.transports.File({
|
||||||
|
level: "debug",
|
||||||
|
filename: "/app/debug.log",
|
||||||
|
format: defaultJsonFormat,
|
||||||
|
silent: !(process.env.NODE_ENV === "development"), // Silent when not in development
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
+23
-11
@@ -11,11 +11,21 @@ import argon2 from "argon2";
|
|||||||
import adminRouter from "./Routes/admin.routes";
|
import adminRouter from "./Routes/admin.routes";
|
||||||
import organisationRouter from "./Routes/organisation.routes";
|
import organisationRouter from "./Routes/organisation.routes";
|
||||||
import groupRouter from "./Routes/group.routes";
|
import groupRouter from "./Routes/group.routes";
|
||||||
|
import defaultErrorHandler from "./Middleware/error/handler";
|
||||||
|
import logger from "./Middleware/error/logger";
|
||||||
|
import debugLogger from "./Middleware/debug/logger";
|
||||||
|
|
||||||
|
// Set up async error handling
|
||||||
|
require("express-async-errors");
|
||||||
|
|
||||||
require("dotenv").config(); // Load dotenv config
|
require("dotenv").config(); // Load dotenv config
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === "development") {
|
||||||
|
logger.info("Using development mode");
|
||||||
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
// Dev
|
// Dev
|
||||||
await prisma.admin.upsert({
|
await prisma.admin.upsert({
|
||||||
@@ -34,16 +44,7 @@ async function main() {
|
|||||||
app.use(express.urlencoded({ extended: true }));
|
app.use(express.urlencoded({ extended: true }));
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
|
||||||
app.use((err: any, req: Request, res: Response, next: NextFunction) => {
|
app.use(debugLogger);
|
||||||
if (err) {
|
|
||||||
res.status(400).send({
|
|
||||||
type: "error",
|
|
||||||
payload: "The body of your request did not contain valid data",
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Admin authentication endpoints
|
// Admin authentication endpoints
|
||||||
app.use("/api/authentication", adminAuthRouter);
|
app.use("/api/authentication", adminAuthRouter);
|
||||||
@@ -56,13 +57,24 @@ async function main() {
|
|||||||
app.use("/api/organisations", organisationRouter);
|
app.use("/api/organisations", organisationRouter);
|
||||||
|
|
||||||
app.use("/api/groups", groupRouter);
|
app.use("/api/groups", groupRouter);
|
||||||
|
|
||||||
|
// Error handling
|
||||||
|
app.use(defaultErrorHandler);
|
||||||
|
|
||||||
app.listen(process.env.PORT, () => {
|
app.listen(process.env.PORT, () => {
|
||||||
console.log(`Listening on Port: ${process.env.PORT}`);
|
logger.info(`Listening on port ${process.env.PORT}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info("Server started");
|
||||||
|
|
||||||
|
process.on("exit", () => {
|
||||||
|
logger.info("Server stopping...");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
main()
|
main()
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
|
logger.crit(e);
|
||||||
throw e;
|
throw e;
|
||||||
})
|
})
|
||||||
.finally(async () => {
|
.finally(async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user