Merge pull request #16 from mcndt/server-refactor
refactor: split Express app into controller pattern
This commit is contained in:
commit
9c2f52325a
@ -1,99 +0,0 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
require("dotenv/config");
|
||||
const express_1 = __importDefault(require("express"));
|
||||
const client_1 = require("@prisma/client");
|
||||
const util_1 = require("./util");
|
||||
const helmet_1 = __importDefault(require("helmet"));
|
||||
const express_rate_limit_1 = __importDefault(require("express-rate-limit"));
|
||||
// Initialize middleware clients
|
||||
const prisma = new client_1.PrismaClient();
|
||||
const app = (0, express_1.default)();
|
||||
app.use(express_1.default.json());
|
||||
app.use((0, helmet_1.default)({
|
||||
crossOriginResourcePolicy: {
|
||||
policy: process.env.ENVIRONMENT == "dev" ? "cross-origin" : "same-origin",
|
||||
},
|
||||
}));
|
||||
// Apply rate limiting
|
||||
const postLimiter = (0, express_rate_limit_1.default)({
|
||||
windowMs: parseInt(process.env.POST_LIMIT_WINDOW_SECONDS) * 1000,
|
||||
max: parseInt(process.env.POST_LIMIT),
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
|
||||
});
|
||||
// start the Express server
|
||||
app.listen(process.env.PORT, () => {
|
||||
console.log(`server started at port ${process.env.PORT}`);
|
||||
});
|
||||
// Post new encrypted note
|
||||
app.post("/note/", postLimiter, (req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
||||
try {
|
||||
const note = req.body;
|
||||
const savedNote = yield prisma.encryptedNote.create({
|
||||
data: Object.assign(Object.assign({}, note), { expire_time: (0, util_1.addDays)(new Date(), 30) }),
|
||||
});
|
||||
console.log(`[POST] Saved note <${savedNote.id}> for <${req.ip}>`);
|
||||
res.json({
|
||||
view_url: `${process.env.FRONTEND_URL}/note/${savedNote.id}`,
|
||||
expire_time: savedNote.expire_time,
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err.stack);
|
||||
res.status(500).send("Something went wrong.");
|
||||
}
|
||||
}));
|
||||
// Get encrypted note
|
||||
app.get("/note/:id", (req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
||||
const note = yield prisma.encryptedNote.findUnique({
|
||||
where: { id: req.params.id },
|
||||
});
|
||||
if (note != null) {
|
||||
res.send(note);
|
||||
console.log(`[GET] Retrieved note <${note.id}> for <${req.ip}>`);
|
||||
}
|
||||
res.status(404).send();
|
||||
}));
|
||||
// Default response for any other request
|
||||
app.use((req, res, next) => {
|
||||
res.status(500).send();
|
||||
});
|
||||
// // Error handling
|
||||
// app.use((err, req, res, next) => {
|
||||
// console.error(err.stack);
|
||||
// res.status(500).send("Something broke!");
|
||||
// });
|
||||
// Clean up expired notes periodically
|
||||
const interval = Math.max(parseInt(process.env.CLEANUP_INTERVAL_SECONDS) || 1, 1) *
|
||||
1000;
|
||||
setInterval(() => __awaiter(void 0, void 0, void 0, function* () {
|
||||
try {
|
||||
console.log("[Cleanup] Cleaning up expired notes...");
|
||||
const deleted = yield prisma.encryptedNote.deleteMany({
|
||||
where: {
|
||||
expire_time: {
|
||||
lte: new Date(),
|
||||
},
|
||||
},
|
||||
});
|
||||
console.log(`[Cleanup] Deleted ${deleted.count} expired notes.`);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(`[Cleanup] Error cleaning expired notes:`);
|
||||
console.error(err);
|
||||
}
|
||||
}), interval);
|
||||
//# sourceMappingURL=server.js.map
|
3117
server/package-lock.json
generated
3117
server/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -11,7 +11,7 @@
|
||||
"test:coverage": "dotenv -e .env.test -- vitest run --no-threads --coverage",
|
||||
"test:db:reset": "dotenv -e .env.test -- npx prisma migrate reset -f",
|
||||
"build": "npx tsc",
|
||||
"dev": "npx nodemon ./server.ts | npx pino-colada"
|
||||
"dev": "npx nodemon src/server.ts | npx pino-colada"
|
||||
},
|
||||
"author": "Maxime Cannoodt (mcndt)",
|
||||
"license": "MIT",
|
||||
@ -42,6 +42,7 @@
|
||||
"supertest": "^6.2.3",
|
||||
"ts-node": "^10.8.1",
|
||||
"typescript": "^4.7.4",
|
||||
"vite-tsconfig-paths": "^3.5.0",
|
||||
"vitest": "^0.17.1"
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
import app, { cleanExpiredNotes } from "./app";
|
||||
import request from "supertest";
|
||||
import { app } from "./app";
|
||||
import supertest from "supertest";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import prisma from "./client";
|
||||
import { EventType } from "./EventLogger";
|
||||
import prisma from "./db/client";
|
||||
import { cleanExpiredNotes } from "./tasks/deleteExpiredNotes";
|
||||
import { EventType } from "./logging/EventLogger";
|
||||
|
||||
// const testNote with base64 ciphertext and hmac
|
||||
const testNote = {
|
||||
@ -18,7 +19,7 @@ describe("GET /api/note", () => {
|
||||
});
|
||||
|
||||
// Make get request
|
||||
const res = await request(app).get(`/api/note/${id}`);
|
||||
const res = await supertest(app).get(`/api/note/${id}`);
|
||||
|
||||
// Validate returned note
|
||||
expect(res.statusCode).toBe(200);
|
||||
@ -44,7 +45,7 @@ describe("GET /api/note", () => {
|
||||
|
||||
it("responds 404 for invalid ID", async () => {
|
||||
// Make get request
|
||||
const res = await request(app).get(`/api/note/NaN`);
|
||||
const res = await supertest(app).get(`/api/note/NaN`);
|
||||
|
||||
// Validate returned note
|
||||
expect(res.statusCode).toBe(404);
|
||||
@ -66,7 +67,7 @@ describe("GET /api/note", () => {
|
||||
// Make get requests
|
||||
const requests = [];
|
||||
for (let i = 0; i < 51; i++) {
|
||||
requests.push(request(app).get(`/api/note/${id}`));
|
||||
requests.push(supertest(app).get(`/api/note/${id}`));
|
||||
}
|
||||
const responses = await Promise.all(requests);
|
||||
const responseCodes = responses.map((res) => res.statusCode);
|
||||
@ -81,7 +82,7 @@ describe("GET /api/note", () => {
|
||||
|
||||
describe("POST /api/note", () => {
|
||||
it("returns a view_url on correct POST body", async () => {
|
||||
const res = await request(app).post("/api/note").send(testNote);
|
||||
const res = await supertest(app).post("/api/note").send(testNote);
|
||||
expect(res.statusCode).toBe(200);
|
||||
|
||||
// Returned body has correct fields
|
||||
@ -109,13 +110,13 @@ describe("POST /api/note", () => {
|
||||
});
|
||||
|
||||
it("Returns a bad request on invalid POST body", async () => {
|
||||
const res = await request(app).post("/api/note").send({});
|
||||
const res = await supertest(app).post("/api/note").send({});
|
||||
expect(res.statusCode).toBe(400);
|
||||
});
|
||||
|
||||
it("returns a valid view_url on correct POST body", async () => {
|
||||
// Make post request
|
||||
let res = await request(app).post("/api/note").send(testNote);
|
||||
let res = await supertest(app).post("/api/note").send(testNote);
|
||||
|
||||
// Extract note id from post response
|
||||
expect(res.statusCode).toBe(200);
|
||||
@ -126,7 +127,7 @@ describe("POST /api/note", () => {
|
||||
const note_id = (match as RegExpMatchArray)[1];
|
||||
|
||||
// Make get request
|
||||
res = await request(app).get(`/api/note/${note_id}`);
|
||||
res = await supertest(app).get(`/api/note/${note_id}`);
|
||||
|
||||
// Validate returned note
|
||||
expect(res.statusCode).toBe(200);
|
||||
@ -140,12 +141,12 @@ describe("POST /api/note", () => {
|
||||
expect(res.body.hmac).toEqual(testNote.hmac);
|
||||
});
|
||||
|
||||
it("Applies upload limit to endpoint of 400kb", async () => {
|
||||
it("Applies upload limit to endpoint of 500kb", async () => {
|
||||
const largeNote = {
|
||||
ciphertext: "a".repeat(400 * 1024),
|
||||
ciphertext: "a".repeat(500 * 1024),
|
||||
hmac: "sample_hmac",
|
||||
};
|
||||
const res = await request(app).post("/api/note").send(largeNote);
|
||||
const res = await supertest(app).post("/api/note").send(largeNote);
|
||||
expect(res.statusCode).toBe(413);
|
||||
});
|
||||
|
||||
@ -153,7 +154,7 @@ describe("POST /api/note", () => {
|
||||
// make more requests than the post limit set in .env.test
|
||||
const requests = [];
|
||||
for (let i = 0; i < 51; i++) {
|
||||
requests.push(request(app).post("/api/note").send(testNote));
|
||||
requests.push(supertest(app).post("/api/note").send(testNote));
|
||||
}
|
||||
const responses = await Promise.all(requests);
|
||||
const responseCodes = responses.map((res) => res.statusCode);
|
||||
@ -177,7 +178,7 @@ describe("Clean expired notes", () => {
|
||||
});
|
||||
|
||||
// make request for note and check that response is 200
|
||||
let res = await request(app).get(`/api/note/${id}`);
|
||||
let res = await supertest(app).get(`/api/note/${id}`);
|
||||
expect(res.statusCode).toBe(200);
|
||||
|
||||
// run cleanup
|
||||
@ -185,7 +186,7 @@ describe("Clean expired notes", () => {
|
||||
expect(nDeleted).toBeGreaterThan(0);
|
||||
|
||||
// make sure note is gone
|
||||
res = await request(app).get(`/api/note/${id}`);
|
||||
res = await supertest(app).get(`/api/note/${id}`);
|
||||
expect(res.statusCode).toBe(404);
|
||||
|
||||
// sleep 100ms to allow all events to be logged
|
||||
|
@ -1,20 +1,16 @@
|
||||
import "dotenv/config";
|
||||
import express, { Express, Request, Response } from "express";
|
||||
import { EncryptedNote } from "@prisma/client";
|
||||
import { addDays, getConnectingIp } from "./util";
|
||||
import express, { type Express } from "express";
|
||||
import helmet from "helmet";
|
||||
import rateLimit from "express-rate-limit";
|
||||
import pinoHttp from "pino-http";
|
||||
import logger from "./logger";
|
||||
import prisma from "./client";
|
||||
import bodyParser from "body-parser";
|
||||
import { NotePostRequest } from "./model/NotePostRequest";
|
||||
import { validateOrReject } from "class-validator";
|
||||
import EventLogger from "./EventLogger";
|
||||
import logger from "./logging/logger";
|
||||
import { notesRoute } from "./controllers/note/note.router";
|
||||
import { cleanExpiredNotes, cleanInterval } from "./tasks/deleteExpiredNotes";
|
||||
|
||||
// Initialize middleware clients
|
||||
const app: Express = express();
|
||||
app.use(express.json());
|
||||
export const app: Express = express();
|
||||
|
||||
// Enable JSON body parsing
|
||||
app.use(express.json({ limit: "500k" }));
|
||||
|
||||
// configure logging
|
||||
app.use(
|
||||
@ -32,144 +28,8 @@ app.use(
|
||||
})
|
||||
);
|
||||
|
||||
// Apply rate limiting
|
||||
const postLimiter = rateLimit({
|
||||
windowMs: parseFloat(process.env.POST_LIMIT_WINDOW_SECONDS as string) * 1000,
|
||||
max: parseInt(process.env.POST_LIMIT as string), // Limit each IP to X requests per window
|
||||
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
|
||||
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
|
||||
});
|
||||
// Mount routes
|
||||
app.use("/api/note/", notesRoute);
|
||||
|
||||
const getLimiter = rateLimit({
|
||||
windowMs: parseFloat(process.env.GET_LIMIT_WINDOW_SECONDS as string) * 1000,
|
||||
max: parseInt(process.env.GET_LIMIT as string), // Limit each IP to X requests per window
|
||||
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
|
||||
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
|
||||
});
|
||||
|
||||
// Apply 400kB upload limit on POST
|
||||
app.use(bodyParser.json({ limit: "400k" }));
|
||||
|
||||
// Get encrypted note
|
||||
app.get("/api/note/:id", getLimiter, (req: Request, res: Response, next) => {
|
||||
const ip = getConnectingIp(req);
|
||||
prisma.encryptedNote
|
||||
.findUnique({
|
||||
where: { id: req.params.id },
|
||||
})
|
||||
.then(async (note) => {
|
||||
if (note != null) {
|
||||
await EventLogger.readEvent({
|
||||
success: true,
|
||||
host: ip,
|
||||
note_id: note.id,
|
||||
size_bytes: note.ciphertext.length + note.hmac.length,
|
||||
});
|
||||
res.send(note);
|
||||
} else {
|
||||
await EventLogger.readEvent({
|
||||
success: false,
|
||||
host: ip,
|
||||
note_id: req.params.id,
|
||||
error: "Note not found",
|
||||
});
|
||||
res.status(404).send();
|
||||
}
|
||||
})
|
||||
.catch(async (err) => {
|
||||
await EventLogger.readEvent({
|
||||
success: false,
|
||||
host: ip,
|
||||
note_id: req.params.id,
|
||||
error: err.message,
|
||||
});
|
||||
next(err);
|
||||
});
|
||||
});
|
||||
|
||||
// Post new encrypted note
|
||||
app.post("/api/note/", postLimiter, (req: Request, res: Response, next) => {
|
||||
const ip = getConnectingIp(req);
|
||||
|
||||
const notePostRequest = new NotePostRequest();
|
||||
Object.assign(notePostRequest, req.body);
|
||||
validateOrReject(notePostRequest).catch((err) => {
|
||||
res.status(400).send(err.message);
|
||||
});
|
||||
const note = notePostRequest as EncryptedNote;
|
||||
const EXPIRE_WINDOW_DAYS = 30;
|
||||
prisma.encryptedNote
|
||||
.create({
|
||||
data: {
|
||||
...note,
|
||||
expire_time: addDays(new Date(), EXPIRE_WINDOW_DAYS),
|
||||
},
|
||||
})
|
||||
.then(async (savedNote) => {
|
||||
await EventLogger.writeEvent({
|
||||
success: true,
|
||||
host: ip,
|
||||
note_id: savedNote.id,
|
||||
size_bytes: savedNote.ciphertext.length + savedNote.hmac.length,
|
||||
expire_window_days: EXPIRE_WINDOW_DAYS,
|
||||
});
|
||||
res.json({
|
||||
view_url: `${process.env.FRONTEND_URL}/note/${savedNote.id}`,
|
||||
expire_time: savedNote.expire_time,
|
||||
});
|
||||
})
|
||||
.catch(async (err) => {
|
||||
await EventLogger.writeEvent({
|
||||
success: false,
|
||||
host: ip,
|
||||
error: err.message,
|
||||
});
|
||||
next(err);
|
||||
});
|
||||
});
|
||||
|
||||
// Clean up expired notes periodically
|
||||
export async function cleanExpiredNotes(): Promise<number> {
|
||||
logger.info("[Cleanup] Cleaning up expired notes...");
|
||||
const toDelete = await prisma.encryptedNote.findMany({
|
||||
where: {
|
||||
expire_time: {
|
||||
lte: new Date(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return prisma.encryptedNote
|
||||
.deleteMany({
|
||||
where: { id: { in: toDelete.map((note) => note.id) } },
|
||||
})
|
||||
.then(async (deleted) => {
|
||||
const logs = toDelete.map(async (note) => {
|
||||
logger.info(
|
||||
`[Cleanup] Deleted note ${note.id} with size ${
|
||||
note.ciphertext.length + note.hmac.length
|
||||
} bytes`
|
||||
);
|
||||
return EventLogger.purgeEvent({
|
||||
success: true,
|
||||
note_id: note.id,
|
||||
size_bytes: note.ciphertext.length + note.hmac.length,
|
||||
});
|
||||
});
|
||||
await Promise.all(logs);
|
||||
logger.info(`[Cleanup] Deleted ${deleted.count} expired notes.`);
|
||||
return deleted.count;
|
||||
})
|
||||
.catch((err) => {
|
||||
logger.error(`[Cleanup] Error cleaning expired notes:`);
|
||||
logger.error(err);
|
||||
return -1;
|
||||
});
|
||||
}
|
||||
|
||||
const interval =
|
||||
Math.max(parseInt(<string>process.env.CLEANUP_INTERVAL_SECONDS) || 1, 1) *
|
||||
1000;
|
||||
setInterval(cleanExpiredNotes, interval);
|
||||
|
||||
export default app;
|
||||
// Run periodic tasks
|
||||
setInterval(cleanExpiredNotes, cleanInterval);
|
||||
|
34
server/src/controllers/note/note.dao.ts
Normal file
34
server/src/controllers/note/note.dao.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { EncryptedNote } from "@prisma/client";
|
||||
import prisma from "../../db/client";
|
||||
|
||||
export async function getNote(noteId: string): Promise<EncryptedNote | null> {
|
||||
return prisma.encryptedNote.findUnique({
|
||||
where: { id: noteId },
|
||||
});
|
||||
}
|
||||
|
||||
export async function createNote(note: EncryptedNote): Promise<EncryptedNote> {
|
||||
return prisma.encryptedNote.create({
|
||||
data: note,
|
||||
});
|
||||
}
|
||||
|
||||
export async function getExpiredNotes(): Promise<EncryptedNote[]> {
|
||||
return prisma.encryptedNote.findMany({
|
||||
where: {
|
||||
expire_time: {
|
||||
lte: new Date(),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function deleteNotes(noteIds: string[]): Promise<number> {
|
||||
return prisma.encryptedNote
|
||||
.deleteMany({
|
||||
where: { id: { in: noteIds } },
|
||||
})
|
||||
.then((deleted) => {
|
||||
return deleted.count;
|
||||
});
|
||||
}
|
41
server/src/controllers/note/note.get.controller.ts
Normal file
41
server/src/controllers/note/note.get.controller.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import EventLogger from "../../logging/EventLogger";
|
||||
import { getConnectingIp } from "../../util";
|
||||
import { getNote } from "./note.dao";
|
||||
|
||||
export async function getNoteController(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
const ip = getConnectingIp(req);
|
||||
getNote(req.params.id)
|
||||
.then(async (note) => {
|
||||
if (note != null) {
|
||||
await EventLogger.readEvent({
|
||||
success: true,
|
||||
host: ip,
|
||||
note_id: note.id,
|
||||
size_bytes: note.ciphertext.length + note.hmac.length,
|
||||
});
|
||||
res.send(note);
|
||||
} else {
|
||||
await EventLogger.readEvent({
|
||||
success: false,
|
||||
host: ip,
|
||||
note_id: req.params.id,
|
||||
error: "Note not found",
|
||||
});
|
||||
res.status(404).send();
|
||||
}
|
||||
})
|
||||
.catch(async (err) => {
|
||||
await EventLogger.readEvent({
|
||||
success: false,
|
||||
host: ip,
|
||||
note_id: req.params.id,
|
||||
error: err.message,
|
||||
});
|
||||
next(err);
|
||||
});
|
||||
}
|
59
server/src/controllers/note/note.post.controller.ts
Normal file
59
server/src/controllers/note/note.post.controller.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { EncryptedNote } from "@prisma/client";
|
||||
import { validateOrReject } from "class-validator";
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import { IsBase64 } from "class-validator";
|
||||
import { createNote } from "./note.dao";
|
||||
import { addDays, getConnectingIp } from "../../util";
|
||||
import EventLogger from "../../logging/EventLogger";
|
||||
|
||||
/**
|
||||
* Request body for creating a note
|
||||
*/
|
||||
export class NotePostRequest {
|
||||
@IsBase64()
|
||||
ciphertext: string | undefined;
|
||||
|
||||
@IsBase64()
|
||||
hmac: string | undefined;
|
||||
}
|
||||
|
||||
export async function postNoteController(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
const ip = getConnectingIp(req);
|
||||
|
||||
const notePostRequest = new NotePostRequest();
|
||||
Object.assign(notePostRequest, req.body);
|
||||
validateOrReject(notePostRequest).catch((err) => {
|
||||
res.status(400).send(err.message);
|
||||
});
|
||||
const note = notePostRequest as EncryptedNote;
|
||||
const EXPIRE_WINDOW_DAYS = 30;
|
||||
createNote({
|
||||
...note,
|
||||
expire_time: addDays(new Date(), EXPIRE_WINDOW_DAYS),
|
||||
})
|
||||
.then(async (savedNote) => {
|
||||
await EventLogger.writeEvent({
|
||||
success: true,
|
||||
host: ip,
|
||||
note_id: savedNote.id,
|
||||
size_bytes: savedNote.ciphertext.length + savedNote.hmac.length,
|
||||
expire_window_days: EXPIRE_WINDOW_DAYS,
|
||||
});
|
||||
res.json({
|
||||
view_url: `${process.env.FRONTEND_URL}/note/${savedNote.id}`,
|
||||
expire_time: savedNote.expire_time,
|
||||
});
|
||||
})
|
||||
.catch(async (err) => {
|
||||
await EventLogger.writeEvent({
|
||||
success: false,
|
||||
host: ip,
|
||||
error: err.message,
|
||||
});
|
||||
next(err);
|
||||
});
|
||||
}
|
26
server/src/controllers/note/note.router.ts
Normal file
26
server/src/controllers/note/note.router.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import express from "express";
|
||||
import rateLimit from "express-rate-limit";
|
||||
import { getNoteController } from "./note.get.controller";
|
||||
import { postNoteController } from "./note.post.controller";
|
||||
|
||||
export const notesRoute = express.Router();
|
||||
|
||||
const jsonParser = express.json({ limit: "500k" });
|
||||
|
||||
const postRateLimit = rateLimit({
|
||||
windowMs: parseFloat(process.env.POST_LIMIT_WINDOW_SECONDS as string) * 1000,
|
||||
max: parseInt(process.env.POST_LIMIT as string), // Limit each IP to X requests per window
|
||||
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
|
||||
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
|
||||
});
|
||||
|
||||
const getRateLimit = rateLimit({
|
||||
windowMs: parseFloat(process.env.GET_LIMIT_WINDOW_SECONDS as string) * 1000,
|
||||
max: parseInt(process.env.GET_LIMIT as string), // Limit each IP to X requests per window
|
||||
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
|
||||
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
|
||||
});
|
||||
|
||||
notesRoute.use(jsonParser);
|
||||
notesRoute.post("", postRateLimit, postNoteController);
|
||||
notesRoute.get("/:id", getRateLimit, getNoteController);
|
@ -1,6 +1,6 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import prisma from "../db/client";
|
||||
import EventLogger, { EventType } from "./EventLogger";
|
||||
import prisma from "./client";
|
||||
|
||||
describe("Logging write events", () => {
|
||||
it("Should write a write event to database", async () => {
|
@ -1,5 +1,5 @@
|
||||
import prisma from "./client";
|
||||
import { event } from "@prisma/client";
|
||||
import prisma from "../db/client";
|
||||
|
||||
export enum EventType {
|
||||
WRITE = "WRITE",
|
@ -1,12 +0,0 @@
|
||||
import { IsBase64 } from "class-validator";
|
||||
|
||||
/**
|
||||
* Request body for creating a note
|
||||
*/
|
||||
export class NotePostRequest {
|
||||
@IsBase64()
|
||||
ciphertext: string | undefined;
|
||||
|
||||
@IsBase64()
|
||||
hmac: string | undefined;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import "dotenv/config";
|
||||
import logger from "./src/logger";
|
||||
import app from "./src/app";
|
||||
import logger from "./logging/logger";
|
||||
import { app } from "./app";
|
||||
|
||||
// start the Express server
|
||||
app.listen(process.env.PORT, () => {
|
36
server/src/tasks/deleteExpiredNotes.ts
Normal file
36
server/src/tasks/deleteExpiredNotes.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { deleteNotes, getExpiredNotes } from "../controllers/note/note.dao";
|
||||
import EventLogger from "../logging/EventLogger";
|
||||
import logger from "../logging/logger";
|
||||
|
||||
export const cleanInterval =
|
||||
Math.max(parseInt(<string>process.env.CLEANUP_INTERVAL_SECONDS) || 1, 1) *
|
||||
1000;
|
||||
|
||||
export async function cleanExpiredNotes(): Promise<number> {
|
||||
logger.info("[Cleanup] Cleaning up expired notes...");
|
||||
const toDelete = await getExpiredNotes();
|
||||
|
||||
return deleteNotes(toDelete.map((n) => n.id))
|
||||
.then(async (deleteCount) => {
|
||||
const logs = toDelete.map(async (note) => {
|
||||
logger.info(
|
||||
`[Cleanup] Deleted note ${note.id} with size ${
|
||||
note.ciphertext.length + note.hmac.length
|
||||
} bytes`
|
||||
);
|
||||
return EventLogger.purgeEvent({
|
||||
success: true,
|
||||
note_id: note.id,
|
||||
size_bytes: note.ciphertext.length + note.hmac.length,
|
||||
});
|
||||
});
|
||||
await Promise.all(logs);
|
||||
logger.info(`[Cleanup] Deleted ${deleteCount} expired notes.`);
|
||||
return deleteCount;
|
||||
})
|
||||
.catch((err) => {
|
||||
logger.error(`[Cleanup] Error cleaning expired notes:`);
|
||||
logger.error(err);
|
||||
return -1;
|
||||
});
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
import { defineConfig } from "vitest/config";
|
||||
import tsconfigPaths from "vite-tsconfig-paths";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [tsconfigPaths()],
|
||||
test: {
|
||||
// ...
|
||||
include: ["src/**/*.test.ts"],
|
||||
},
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user