add testing libs, separate app from server
This commit is contained in:
1091
server/package-lock.json
generated
1091
server/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,8 +4,9 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo 'No tests set up for the server.'",
|
"test-watch": "vitest",
|
||||||
"coverage": "echo 'No coverage set up for the server.'",
|
"test": "vitest run",
|
||||||
|
"coverage": "vitest run --coverage",
|
||||||
"build": "npx tsc",
|
"build": "npx tsc",
|
||||||
"dev": "npx nodemon ./server.ts | npx pino-colada"
|
"dev": "npx nodemon ./server.ts | npx pino-colada"
|
||||||
},
|
},
|
||||||
@@ -31,6 +32,7 @@
|
|||||||
"prisma": "^4.0.0",
|
"prisma": "^4.0.0",
|
||||||
"supertest": "^6.2.3",
|
"supertest": "^6.2.3",
|
||||||
"ts-node": "^10.8.1",
|
"ts-node": "^10.8.1",
|
||||||
"typescript": "^4.7.4"
|
"typescript": "^4.7.4",
|
||||||
|
"vitest": "^0.17.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,98 +1,8 @@
|
|||||||
import "dotenv/config";
|
import "dotenv/config";
|
||||||
import express, { Express, Request } from "express";
|
import logger from "./src/logger";
|
||||||
import { PrismaClient, EncryptedNote } from "@prisma/client";
|
import app from "./src/app";
|
||||||
import { addDays } from "./util";
|
|
||||||
import helmet from "helmet";
|
|
||||||
import rateLimit from "express-rate-limit";
|
|
||||||
import pinoHttp from "pino-http";
|
|
||||||
import logger from "./logger";
|
|
||||||
|
|
||||||
// Initialize middleware clients
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
const app: Express = express();
|
|
||||||
app.use(express.json());
|
|
||||||
|
|
||||||
// configure logging
|
|
||||||
app.use(
|
|
||||||
pinoHttp({
|
|
||||||
logger: logger,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// configure Helmet and CORS
|
|
||||||
app.use(
|
|
||||||
helmet({
|
|
||||||
crossOriginResourcePolicy: {
|
|
||||||
policy: process.env.ENVIRONMENT == "dev" ? "cross-origin" : "same-origin",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Apply rate limiting
|
|
||||||
const postLimiter = rateLimit({
|
|
||||||
windowMs: parseInt(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
|
|
||||||
});
|
|
||||||
|
|
||||||
// start the Express server
|
// start the Express server
|
||||||
app.listen(process.env.PORT, () => {
|
app.listen(process.env.PORT, () => {
|
||||||
logger.info(`server started at port ${process.env.PORT}`);
|
logger.info(`server started at port ${process.env.PORT}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Post new encrypted note
|
|
||||||
app.post(
|
|
||||||
"/api/note/",
|
|
||||||
postLimiter,
|
|
||||||
(req: Request<{}, {}, EncryptedNote>, res, next) => {
|
|
||||||
const note = req.body;
|
|
||||||
prisma.encryptedNote
|
|
||||||
.create({
|
|
||||||
data: { ...note, expire_time: addDays(new Date(), 30) },
|
|
||||||
})
|
|
||||||
.then((savedNote) => {
|
|
||||||
res.json({
|
|
||||||
view_url: `${process.env.FRONTEND_URL}/note/${savedNote.id}`,
|
|
||||||
expire_time: savedNote.expire_time,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(next);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get encrypted note
|
|
||||||
app.get("/api/note/:id", (req, res, next) => {
|
|
||||||
prisma.encryptedNote
|
|
||||||
.findUnique({
|
|
||||||
where: { id: req.params.id },
|
|
||||||
})
|
|
||||||
.then((note) => {
|
|
||||||
if (note != null) {
|
|
||||||
res.send(note);
|
|
||||||
}
|
|
||||||
res.status(404).send();
|
|
||||||
})
|
|
||||||
.catch(next);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Clean up expired notes periodically
|
|
||||||
const interval =
|
|
||||||
Math.max(parseInt(<string>process.env.CLEANUP_INTERVAL_SECONDS) || 1, 1) *
|
|
||||||
1000;
|
|
||||||
setInterval(async () => {
|
|
||||||
try {
|
|
||||||
logger.info("[Cleanup] Cleaning up expired notes...");
|
|
||||||
const deleted = await prisma.encryptedNote.deleteMany({
|
|
||||||
where: {
|
|
||||||
expire_time: {
|
|
||||||
lte: new Date(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
logger.info(`[Cleanup] Deleted ${deleted.count} expired notes.`);
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(`[Cleanup] Error cleaning expired notes:`);
|
|
||||||
logger.error(err);
|
|
||||||
}
|
|
||||||
}, interval);
|
|
||||||
|
|||||||
95
server/src/app.ts
Normal file
95
server/src/app.ts
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import "dotenv/config";
|
||||||
|
import express, { Express, Request } from "express";
|
||||||
|
import { PrismaClient, EncryptedNote } from "@prisma/client";
|
||||||
|
import { addDays } from "./util";
|
||||||
|
import helmet from "helmet";
|
||||||
|
import rateLimit from "express-rate-limit";
|
||||||
|
import pinoHttp from "pino-http";
|
||||||
|
import logger from "./logger";
|
||||||
|
|
||||||
|
// Initialize middleware clients
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
const app: Express = express();
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
// configure logging
|
||||||
|
app.use(
|
||||||
|
pinoHttp({
|
||||||
|
logger: logger,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// configure Helmet and CORS
|
||||||
|
app.use(
|
||||||
|
helmet({
|
||||||
|
crossOriginResourcePolicy: {
|
||||||
|
policy: process.env.ENVIRONMENT == "dev" ? "cross-origin" : "same-origin",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Apply rate limiting
|
||||||
|
const postLimiter = rateLimit({
|
||||||
|
windowMs: parseInt(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
|
||||||
|
});
|
||||||
|
|
||||||
|
// Post new encrypted note
|
||||||
|
app.post(
|
||||||
|
"/api/note/",
|
||||||
|
postLimiter,
|
||||||
|
(req: Request<{}, {}, EncryptedNote>, res, next) => {
|
||||||
|
const note = req.body;
|
||||||
|
prisma.encryptedNote
|
||||||
|
.create({
|
||||||
|
data: { ...note, expire_time: addDays(new Date(), 30) },
|
||||||
|
})
|
||||||
|
.then((savedNote) => {
|
||||||
|
res.json({
|
||||||
|
view_url: `${process.env.FRONTEND_URL}/note/${savedNote.id}`,
|
||||||
|
expire_time: savedNote.expire_time,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(next);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get encrypted note
|
||||||
|
app.get("/api/note/:id", (req, res, next) => {
|
||||||
|
prisma.encryptedNote
|
||||||
|
.findUnique({
|
||||||
|
where: { id: req.params.id },
|
||||||
|
})
|
||||||
|
.then((note) => {
|
||||||
|
if (note != null) {
|
||||||
|
res.send(note);
|
||||||
|
}
|
||||||
|
res.status(404).send();
|
||||||
|
})
|
||||||
|
.catch(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clean up expired notes periodically
|
||||||
|
const interval =
|
||||||
|
Math.max(parseInt(<string>process.env.CLEANUP_INTERVAL_SECONDS) || 1, 1) *
|
||||||
|
1000;
|
||||||
|
setInterval(async () => {
|
||||||
|
try {
|
||||||
|
logger.info("[Cleanup] Cleaning up expired notes...");
|
||||||
|
const deleted = await prisma.encryptedNote.deleteMany({
|
||||||
|
where: {
|
||||||
|
expire_time: {
|
||||||
|
lte: new Date(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
logger.info(`[Cleanup] Deleted ${deleted.count} expired notes.`);
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(`[Cleanup] Error cleaning expired notes:`);
|
||||||
|
logger.error(err);
|
||||||
|
}
|
||||||
|
}, interval);
|
||||||
|
|
||||||
|
export default app;
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
|
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
|
||||||
"strict": true /* Enable all strict type-checking options. */,
|
"strict": true /* Enable all strict type-checking options. */,
|
||||||
"noImplicitAny": true /* Enable error reporting for expressions and declarations with an implied 'any' type. */,
|
"noImplicitAny": true /* Enable error reporting for expressions and declarations with an implied 'any' type. */,
|
||||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
"skipLibCheck": true /* Skip type checking all .d.ts files. */,
|
||||||
|
"types": ["vitest/globals"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
server/vite.config.ts
Normal file
7
server/vite.config.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { defineConfig } from "vitest/config";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
// ...
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user