This commit is contained in:
Pitu 2021-06-18 02:49:12 +09:00
parent c8f19898ae
commit 01e85b5e2f
7 changed files with 281 additions and 18 deletions

View File

@ -10,6 +10,7 @@
"license": "MIT",
"dependencies": {
"@prisma/client": "^2.21.2",
"@types/cron": "^1.7.2",
"adm-zip": "^0.5.5",
"axios": "^0.21.1",
"bcryptjs": "^2.4.3",
@ -42,6 +43,7 @@
"@types/bcryptjs": "^2.4.2",
"@types/cors": "^2.8.10",
"@types/express": "^4.17.11",
"@types/express-rate-limit": "^5.1.2",
"@types/jsonwebtoken": "^8.5.1",
"@types/morgan": "^1.9.2",
"@types/node": "^14.14.41",
@ -315,6 +317,15 @@
"integrity": "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ==",
"dev": true
},
"node_modules/@types/cron": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/@types/cron/-/cron-1.7.2.tgz",
"integrity": "sha512-AEpNLRcsVSc5AdseJKNHpz0d4e8+ow+abTaC0fKDbAU86rF1evoFF0oC2fV9FdqtfVXkG2LKshpLTJCFOpyvTg==",
"dependencies": {
"@types/node": "*",
"moment": ">=2.14.0"
}
},
"node_modules/@types/debug": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz",
@ -332,6 +343,15 @@
"@types/serve-static": "*"
}
},
"node_modules/@types/express-rate-limit": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/@types/express-rate-limit/-/express-rate-limit-5.1.2.tgz",
"integrity": "sha512-loN1dcr0WEKsbVtXNQKDef4Fmh25prfy+gESrwTDofIhAt17ngTxrsDiEZxLemmfHH279x206CdUB9XHXS9E6Q==",
"dev": true,
"dependencies": {
"@types/express": "*"
}
},
"node_modules/@types/express-serve-static-core": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.21.tgz",
@ -398,8 +418,7 @@
"node_modules/@types/node": {
"version": "14.17.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.3.tgz",
"integrity": "sha512-e6ZowgGJmTuXa3GyaPbTGxX17tnThl2aSSizrFthQ7m9uLGZBXiGhgE55cjRZTF5kjZvYn9EOPOMljdjwbflxw==",
"dev": true
"integrity": "sha512-e6ZowgGJmTuXa3GyaPbTGxX17tnThl2aSSizrFthQ7m9uLGZBXiGhgE55cjRZTF5kjZvYn9EOPOMljdjwbflxw=="
},
"node_modules/@types/normalize-package-data": {
"version": "2.4.0",
@ -7556,6 +7575,15 @@
"integrity": "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ==",
"dev": true
},
"@types/cron": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/@types/cron/-/cron-1.7.2.tgz",
"integrity": "sha512-AEpNLRcsVSc5AdseJKNHpz0d4e8+ow+abTaC0fKDbAU86rF1evoFF0oC2fV9FdqtfVXkG2LKshpLTJCFOpyvTg==",
"requires": {
"@types/node": "*",
"moment": ">=2.14.0"
}
},
"@types/debug": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz",
@ -7573,6 +7601,15 @@
"@types/serve-static": "*"
}
},
"@types/express-rate-limit": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/@types/express-rate-limit/-/express-rate-limit-5.1.2.tgz",
"integrity": "sha512-loN1dcr0WEKsbVtXNQKDef4Fmh25prfy+gESrwTDofIhAt17ngTxrsDiEZxLemmfHH279x206CdUB9XHXS9E6Q==",
"dev": true,
"requires": {
"@types/express": "*"
}
},
"@types/express-serve-static-core": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.21.tgz",
@ -7639,8 +7676,7 @@
"@types/node": {
"version": "14.17.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.3.tgz",
"integrity": "sha512-e6ZowgGJmTuXa3GyaPbTGxX17tnThl2aSSizrFthQ7m9uLGZBXiGhgE55cjRZTF5kjZvYn9EOPOMljdjwbflxw==",
"dev": true
"integrity": "sha512-e6ZowgGJmTuXa3GyaPbTGxX17tnThl2aSSizrFthQ7m9uLGZBXiGhgE55cjRZTF5kjZvYn9EOPOMljdjwbflxw=="
},
"@types/normalize-package-data": {
"version": "2.4.0",

View File

@ -23,6 +23,7 @@
},
"dependencies": {
"@prisma/client": "^2.21.2",
"@types/cron": "^1.7.2",
"adm-zip": "^0.5.5",
"axios": "^0.21.1",
"bcryptjs": "^2.4.3",
@ -55,6 +56,7 @@
"@types/bcryptjs": "^2.4.2",
"@types/cors": "^2.8.10",
"@types/express": "^4.17.11",
"@types/express-rate-limit": "^5.1.2",
"@types/jsonwebtoken": "^8.5.1",
"@types/morgan": "^1.9.2",
"@types/node": "^14.14.41",

View File

@ -4,7 +4,9 @@ import cors from 'cors';
import morgan from 'morgan';
import path from 'path';
import rfs from 'rotating-file-stream';
import ratelimit from 'express-rate-limit';
import rateLimit from 'express-rate-limit';
import jetpack from 'fs-jetpack';
import cron from 'cron';
// import { loadNuxt, build } from 'nuxt';
import Routes from './structures/routes';
@ -12,6 +14,12 @@ import Routes from './structures/routes';
const server = express();
const start = async () => {
// Create the folders needed for uploads
jetpack.dir('uploads/chunks');
jetpack.dir('uploads/thumbs/square');
jetpack.dir('uploads/thumbs/preview');
// Create the server and set it up
server.use('trust proxy');
server.use(helmet());
server.use(cors());
@ -28,12 +36,6 @@ const start = async () => {
return res.status(405).json({ message: 'Incorrect `Accept` header provided' });
});
const rateLimiter = new RateLimit({
windowMs: parseInt(process.env.RATE_LIMIT_WINDOW ?? '2000', 10),
max: parseInt(process.env.RATE_LIMIT_MAX ?? '5', 10),
delayMs: 0
});
// Set up logs for production and dev environments
if (process.env.NODE_ENV === 'production') {
const accessLogStream = rfs.createStream('access.log', {
@ -46,19 +48,28 @@ const start = async () => {
}
// Apply rate limiting to the api only
server.use('/api/', rateLimiter);
server.use('/api/', rateLimit({
windowMs: parseInt(process.env.RATE_LIMIT_WINDOW ?? '2000', 10),
max: parseInt(process.env.RATE_LIMIT_MAX ?? '5', 10),
message: 'Too many requests from this IP. Slow down dude.'
}));
// Scan and load routes into express
await Routes.load(server);
server.listen(process.env.port, () => {
// Listen for incoming connections
const listen = server.listen(process.env.port, () => {
console.log(`> Chibisafe Server started on port ${process.env.port ?? 5000}.`);
});
listen.setTimeout(600000);
if (process.env.nuxtStatic) {
server.use(express.static(path.join(__dirname, '..', '..', 'frontend', 'dist')));
} else {
// void serveNuxt();
}
// Serve the uploads
server.use(express.static(path.join(__dirname, '../../uploads')));
// void serveNuxt();
// TODO: move into the database config. (we can just show the crontab line for start, later on we can add dropdowns and stuff)
new cron.CronJob('0 0 * * * *', Util.saveStatsToDb, null, true);
};
/*

View File

@ -0,0 +1,120 @@
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = "file:../../../database/database.sqlite"
shadowDatabaseUrl = "file:../../../database/shadow.sqlite"
}
model albums {
id Int @id @default(autoincrement())
userId Int?
name String?
zippedAt DateTime?
createdAt DateTime?
editedAt DateTime?
nsfw Boolean? @default(false)
@@unique([userId, name], name: "albums_userid_name_unique")
}
model albumsFiles {
id Int @id @default(autoincrement())
albumId Int?
fileId Int?
@@unique([albumId, fileId], name: "albumsfiles_albumid_fileid_unique")
}
model albumsLinks {
id Int @id @default(autoincrement())
albumId Int?
linkId Int? @unique
}
model bans {
id Int @id @default(autoincrement())
ip String?
createdAt DateTime?
}
model fileTags {
id Int @id @default(autoincrement())
fileId Int?
tagId Int?
@@unique([fileId, tagId], name: "filetags_fileid_tagid_unique")
}
model files {
id Int @id @default(autoincrement())
userId Int?
name String?
original String?
type String?
size Int?
hash String?
ip String?
createdAt DateTime?
editedAt DateTime?
}
model links {
id Int @id @default(autoincrement())
userId Int?
albumId Int?
identifier String?
views Int?
enabled Boolean?
enableDownload Boolean?
expiresAt DateTime?
createdAt DateTime?
editedAt DateTime?
@@unique([userId, albumId, identifier], name: "links_userid_albumid_identifier_unique")
}
model settings {
id Int @id @default(autoincrement())
key String?
value String?
}
model statistics {
id Int @id @default(autoincrement())
batchId Int?
type String?
// TODO: This was JSON before so make sure to stringify and parse when saving stats
data String?
createdAt DateTime?
@@unique([batchId, type], name: "statistics_batchid_type_unique")
}
model tags {
id Int @id @default(autoincrement())
uuid String?
userId Int?
name String?
createdAt DateTime?
editedAt DateTime?
@@unique([userId, name], name: "tags_userid_name_unique")
}
model users {
id Int @id @default(autoincrement())
username String?
password String?
enabled Boolean?
isAdmin Boolean?
apiKey String?
passwordEditedAt DateTime?
apiKeyEditedAt DateTime?
createdAt DateTime?
editedAt DateTime?
@@unique([username, apiKey], name: "users_username_apikey_unique")
}

View File

@ -0,0 +1,3 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export default prisma;

View File

@ -0,0 +1,37 @@
export interface User {
id: number;
name: string;
email: string;
password: string;
stravaLink: string;
}
export interface Segment {
id: number;
name: string;
description: string;
route: string;
stravaLink: string;
flagCount: number;
removed: boolean;
chunkyness: number;
wayType: string;
userId: number;
}
export interface Rating {
id: number;
rating: number;
comment: string;
segmentId: number;
}
export interface Picture {
id: number;
name: string;
description?: string;
path?: string;
lat?: string;
lng?: string;
segmentId: number;
}

View File

@ -0,0 +1,54 @@
import { Application, Request, Response } from 'express';
import jetpack from 'fs-jetpack';
import path from 'path';
export default {
load: async (server: Application) => {
// eslint-disable-next-line @typescript-eslint/no-misused-promises
for (const routeFile of await jetpack.findAsync(path.join(__dirname, '..', 'routes'), { matching: '*.{ts,js}' })) {
try {
const slash = process.platform === 'win32' ? '\\' : '/';
const replace = process.env.NODE_ENV === 'production' ? `dist${slash}` : `src${slash}`;
const route = await import(routeFile.replace(replace, `..${slash}`));
const paths: Array<string> = routeFile.split(slash);
const method = paths[paths.length - 1].split('.')[0];
// Get rid of the filename
paths.pop();
// Get rid of the src/routes part
paths.splice(0, 2);
let routePath: string = paths.join(slash);
// Transform path variables to express variables
routePath = routePath.replace('_', ':');
// Append the missing /
routePath = `/${routePath}`;
// Build final route
const prefix = route.options?.ignoreRoutePrefix ? '' : process.env.routePrefix ?? '';
routePath = `${prefix}${routePath}`;
// Run middlewares if any
const middlewares: any[] = [];
if (route.middlewares?.length) {
// eslint-disable-next-line @typescript-eslint/no-misused-promises
for (const middlewareFile of await jetpack.findAsync(path.join(__dirname, '..', 'middlewares'), { matching: '*.{ts,js}' })) {
const middleware = await import(middlewareFile.replace(replace, `..${slash}`));
middlewares.push(middleware.default);
}
}
// Register the route in Express
(server as any)[method](routePath, ...middlewares, (req: Request, res: Response) => route.run(req, res));
console.log(`Found route ${method.toUpperCase()} ${routePath}`);
} catch (error) {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
console.log(`${routeFile} :: ERROR`);
console.log(error);
}
}
}
};