This commit is contained in:
Pitu 2021-06-24 19:13:48 +09:00
parent b924fbd314
commit 7114830582
35 changed files with 405 additions and 581 deletions

View File

@ -1,17 +1,11 @@
import type { FastifyRequest, FastifyReply, HookHandlerDoneFunction } from 'fastify';
import type { FastifyReply, HookHandlerDoneFunction } from 'fastify';
import type { RequestWithUser } from '../structures/interfaces';
import JWT from 'jsonwebtoken';
import prisma from '../structures/database';
interface Decoded {
sub: number;
}
export interface RequestWithUser extends FastifyRequest {
user: {
id: number;
username: string | null;
isAdmin: boolean | null;
};
}
export default (req: RequestWithUser, res: FastifyReply, next: HookHandlerDoneFunction) => {
if (!req.headers.authorization) return res.status(401).send({ message: 'No authorization header provided' });

View File

@ -0,0 +1,23 @@
import type { FastifyRequest, FastifyReply } from 'fastify';
import prisma from '../../../../structures/database';
interface body {
ip: string;
}
export const middlewares = ['auth', 'admin'];
export const run = async (req: FastifyRequest, res: FastifyReply) => {
if (!req.body) return res.status(400).send({ message: 'No body provided' });
// const { username, password }: { username: string; password: string } = req.body;
const { ip } = req.body as body;
if (!ip) return res.status(400).send({ message: 'No ip provided' });
await prisma.bans.create({
data: {
ip
}
});
return res.send({
message: 'Successfully banned the ip'
});
};

View File

@ -1,25 +0,0 @@
const Route = require('../../structures/Route');
class banIP extends Route {
constructor() {
super('/admin/ban/ip', 'post', { adminOnly: true });
}
async run(req, res, db) {
if (!req.body) return res.status(400).json({ message: 'No body provided' });
const { ip } = req.body;
if (!ip) return res.status(400).json({ message: 'No ip provided' });
try {
await db.table('bans').insert({ ip });
} catch (error) {
return super.error(res, error);
}
return res.json({
message: 'Successfully banned the ip'
});
}
}
module.exports = banIP;

View File

@ -0,0 +1,56 @@
import type { FastifyRequest, FastifyReply } from 'fastify';
import prisma from '../../../../structures/database';
import { constructFilePublicLink } from '../../../../utils/Util';
import type { User } from '../../../../structures/interfaces';
interface params {
id: number;
}
interface UserWithFileCount extends User {
fileCount?: number;
}
export const middlewares = ['auth', 'admin'];
export const run = async (req: FastifyRequest, res: FastifyReply) => {
const { id } = req.params as params;
if (!id) return res.status(400).send({ message: 'Invalid file ID supplied' });
const file = await prisma.files.findUnique({
where: {
id
}
});
if (!file) return res.status(404).send({ message: 'File doesn\'t exist' });
let user;
if (file.userId) {
user = await prisma.users.findUnique({
where: {
id: file.userId
},
select: {
id: true,
username: true,
enabled: true,
createdAt: true,
editedAt: true,
isAdmin: true
}
});
if (user) {
// TODO: ???
(user as unknown as UserWithFileCount).fileCount = await prisma.files.count({
where: {
userId: user.id
}
});
}
}
const extendedFile = constructFilePublicLink(req, file);
return res.send({
message: 'Successfully retrieved file',
file: extendedFile,
user
});
};

View File

@ -1,32 +0,0 @@
const Route = require('../../structures/Route');
const Util = require('../../utils/Util');
class filesGET extends Route {
constructor() {
super('/admin/file/:id', 'get', { adminOnly: true });
}
async run(req, res, db) {
const { id } = req.params;
if (!id) return res.status(400).json({ message: 'Invalid file ID supplied' });
let file = await db.table('files').where({ id }).first();
const user = await db.table('users')
.select('id', 'username', 'enabled', 'createdAt', 'editedAt', 'apiKeyEditedAt', 'isAdmin')
.where({ id: file.userId })
.first();
file = Util.constructFilePublicLink(req, file);
// Additional relevant data
const filesFromUser = await db.table('files').where({ userId: user.id }).select('id');
user.fileCount = filesFromUser.length;
return res.json({
message: 'Successfully retrieved file',
file,
user
});
}
}
module.exports = filesGET;

View File

@ -1,27 +0,0 @@
const Route = require('../../structures/Route');
class unBanIP extends Route {
constructor() {
super('/admin/unban/ip', 'post', { adminOnly: true });
}
async run(req, res, db) {
if (!req.body) return res.status(400).json({ message: 'No body provided' });
const { ip } = req.body;
if (!ip) return res.status(400).json({ message: 'No ip provided' });
try {
await db.table('bans')
.where({ ip })
.delete();
} catch (error) {
return super.error(res, error);
}
return res.json({
message: 'Successfully unbanned the ip'
});
}
}
module.exports = unBanIP;

View File

@ -0,0 +1,31 @@
import type { FastifyRequest, FastifyReply } from 'fastify';
import prisma from '../../../../structures/database';
interface body {
ip: string;
}
export const middlewares = ['auth', 'admin'];
export const run = async (req: FastifyRequest, res: FastifyReply) => {
if (!req.body) return res.status(400).send({ message: 'No body provided' });
// const { username, password }: { username: string; password: string } = req.body;
const { ip } = req.body as body;
if (!ip) return res.status(400).send({ message: 'No ip provided' });
const record = await prisma.bans.findFirst({
where: {
ip
}
});
if (record) {
await prisma.bans.delete({
where: {
id: record.id
}
});
}
return res.send({
message: 'Successfully unbanned the ip'
});
};

View File

@ -0,0 +1,29 @@
import type { FastifyReply } from 'fastify';
import type { RequestWithUser } from '../../../../structures/interfaces';
import prisma from '../../../../structures/database';
interface body {
id: number;
}
export const middlewares = ['auth', 'admin'];
export const run = async (req: RequestWithUser, res: FastifyReply) => {
if (!req.body) return res.status(400).send({ message: 'No body provided' });
const { id } = req.body as body;
if (!id) return res.status(400).send({ message: 'No id provided' });
if (id === req.user.id) return res.status(400).send({ message: 'You can\'t apply this action to yourself' });
await prisma.users.update({
where: {
id
},
data: {
isAdmin: false
}
});
return res.send({
message: 'Successfully demoted user'
});
};

View File

@ -0,0 +1,29 @@
import type { FastifyReply } from 'fastify';
import type { RequestWithUser } from '../../../../structures/interfaces';
import prisma from '../../../../structures/database';
interface body {
id: number;
}
export const middlewares = ['auth', 'admin'];
export const run = async (req: RequestWithUser, res: FastifyReply) => {
if (!req.body) return res.status(400).send({ message: 'No body provided' });
const { id } = req.body as body;
if (!id) return res.status(400).send({ message: 'No id provided' });
if (id === req.user.id) return res.status(400).send({ message: 'You can\'t apply this action to yourself' });
await prisma.users.update({
where: {
id
},
data: {
enabled: false
}
});
return res.send({
message: 'Successfully disabled user'
});
};

View File

@ -0,0 +1,29 @@
import type { FastifyReply } from 'fastify';
import type { RequestWithUser } from '../../../../structures/interfaces';
import prisma from '../../../../structures/database';
interface body {
id: number;
}
export const middlewares = ['auth', 'admin'];
export const run = async (req: RequestWithUser, res: FastifyReply) => {
if (!req.body) return res.status(400).send({ message: 'No body provided' });
const { id } = req.body as body;
if (!id) return res.status(400).send({ message: 'No id provided' });
if (id === req.user.id) return res.status(400).send({ message: 'You can\'t apply this action to yourself' });
await prisma.users.update({
where: {
id
},
data: {
enabled: true
}
});
return res.send({
message: 'Successfully enabled user'
});
};

View File

@ -0,0 +1,29 @@
import type { FastifyReply } from 'fastify';
import type { RequestWithUser } from '../../../../structures/interfaces';
import prisma from '../../../../structures/database';
interface body {
id: number;
}
export const middlewares = ['auth', 'admin'];
export const run = async (req: RequestWithUser, res: FastifyReply) => {
if (!req.body) return res.status(400).send({ message: 'No body provided' });
const { id } = req.body as body;
if (!id) return res.status(400).send({ message: 'No id provided' });
if (id === req.user.id) return res.status(400).send({ message: 'You can\'t apply this action to yourself' });
await prisma.users.update({
where: {
id
},
data: {
isAdmin: true
}
});
return res.send({
message: 'Successfully promoted user'
});
};

View File

@ -0,0 +1,20 @@
import type { FastifyRequest, FastifyReply } from 'fastify';
import { deleteAllFilesFromUser } from '../../../../utils/Util';
interface body {
id: number;
}
export const middlewares = ['auth', 'admin'];
export const run = async (req: FastifyRequest, res: FastifyReply) => {
if (!req.body) return res.status(400).send({ message: 'No body provided' });
const { id } = req.body as body;
if (!id) return res.status(400).send({ message: 'No id provided' });
await deleteAllFilesFromUser(id);
return res.send({
message: 'Successfully demoted user'
});
};

View File

@ -1,29 +0,0 @@
const Route = require('../../structures/Route');
class userDemote extends Route {
constructor() {
super('/admin/users/demote', 'post', { adminOnly: true });
}
async run(req, res, db, user) {
if (!req.body) return res.status(400).json({ message: 'No body provided' });
const { id } = req.body;
if (!id) return res.status(400).json({ message: 'No id provided' });
if (id === user.id) return res.status(400).json({ message: 'You can\'t apply this action to yourself' });
try {
await db.table('users')
.where({ id })
.update({ isAdmin: false })
.wasMutated();
} catch (error) {
return super.error(res, error);
}
return res.json({
message: 'Successfully demoted user'
});
}
}
module.exports = userDemote;

View File

@ -1,29 +0,0 @@
const Route = require('../../structures/Route');
class userDisable extends Route {
constructor() {
super('/admin/users/disable', 'post', { adminOnly: true });
}
async run(req, res, db, user) {
if (!req.body) return res.status(400).json({ message: 'No body provided' });
const { id } = req.body;
if (!id) return res.status(400).json({ message: 'No id provided' });
if (id === user.id) return res.status(400).json({ message: 'You can\'t apply this action to yourself' });
try {
await db.table('users')
.where({ id })
.update({ enabled: false })
.wasMutated();
} catch (error) {
return super.error(res, error);
}
return res.json({
message: 'Successfully disabled user'
});
}
}
module.exports = userDisable;

View File

@ -1,29 +0,0 @@
const Route = require('../../structures/Route');
class userEnable extends Route {
constructor() {
super('/admin/users/enable', 'post', { adminOnly: true });
}
async run(req, res, db, user) {
if (!req.body) return res.status(400).json({ message: 'No body provided' });
const { id } = req.body;
if (!id) return res.status(400).json({ message: 'No id provided' });
if (id === user.id) return res.status(400).json({ message: 'You can\'t apply this action to yourself' });
try {
await db.table('users')
.where({ id })
.update({ enabled: true })
.wasMutated();
} catch (error) {
return super.error(res, error);
}
return res.json({
message: 'Successfully enabled user'
});
}
}
module.exports = userEnable;

View File

@ -1,28 +0,0 @@
const Route = require('../../structures/Route');
class userPromote extends Route {
constructor() {
super('/admin/users/promote', 'post', { adminOnly: true });
}
async run(req, res, db, user) {
if (!req.body) return res.status(400).json({ message: 'No body provided' });
const { id } = req.body;
if (!id) return res.status(400).json({ message: 'No id provided' });
if (id === user.id) return res.status(400).json({ message: 'You can\'t apply this action to yourself' });
try {
await db.table('users')
.where({ id })
.update({ isAdmin: true });
} catch (error) {
return super.error(res, error);
}
return res.json({
message: 'Successfully promoted user'
});
}
}
module.exports = userPromote;

View File

@ -1,26 +0,0 @@
const Route = require('../../structures/Route');
const Util = require('../../utils/Util');
class userDemote extends Route {
constructor() {
super('/admin/users/purge', 'post', { adminOnly: true });
}
async run(req, res) {
if (!req.body) return res.status(400).json({ message: 'No body provided' });
const { id } = req.body;
if (!id) return res.status(400).json({ message: 'No id provided' });
try {
await Util.deleteAllFilesFromUser(id);
} catch (error) {
return super.error(res, error);
}
return res.json({
message: 'Successfully deleted the user\'s files'
});
}
}
module.exports = userDemote;

View File

@ -0,0 +1,21 @@
import type { FastifyRequest, FastifyReply } from 'fastify';
import prisma from '../../../structures/database';
export const middlewares = ['auth', 'admin'];
export const run = async (req: FastifyRequest, res: FastifyReply) => {
const users = await prisma.users.findMany({
select: {
id: true,
username: true,
enabled: true,
isAdmin: true,
createdAt: true
}
});
return res.send({
message: 'Successfully retrieved users',
users
});
};

View File

@ -1,23 +0,0 @@
const Route = require('../../structures/Route');
class usersGET extends Route {
constructor() {
super('/admin/users', 'get', { adminOnly: true });
}
async run(req, res, db) {
try {
const users = await db.table('users')
.select('id', 'username', 'enabled', 'isAdmin', 'createdAt');
return res.json({
message: 'Successfully retrieved users',
users
});
} catch (error) {
return super.error(res, error);
}
}
}
module.exports = usersGET;

View File

@ -22,7 +22,7 @@ export const run = async (req: FastifyRequest, res: FastifyReply) => {
if (!user) return res.status(401).send({ message: 'User doesn\'t exist' });
const comparePassword = await bcrypt.compare(password, user.password ?? '');
const comparePassword = await bcrypt.compare(password, user.password);
if (!comparePassword) return res.status(401).send({ message: 'Invalid authorization.' });
const jwt = JWT.sign({

View File

@ -1,34 +0,0 @@
const randomstring = require('randomstring');
const moment = require('moment');
const { dump } = require('dumper.js');
const Route = require('../../structures/Route');
class apiKeyPOST extends Route {
constructor() {
super('/user/apikey/change', 'post');
}
async run(req, res, db, user) {
const now = moment.utc().toDate();
const apiKey = randomstring.generate(64);
try {
await db.table('users')
.where({ id: user.id })
.update({
apiKey,
apiKeyEditedAt: now
});
} catch (error) {
dump(error);
return res.status(401).json({ message: 'There was a problem processing your account' });
}
return res.json({
message: 'Successfully created new api key',
apiKey
});
}
}
module.exports = apiKeyPOST;

View File

@ -0,0 +1,26 @@
import type { FastifyReply } from 'fastify';
import type { RequestWithUser } from '../../../../structures/interfaces';
import prisma from '../../../../structures/database';
import { utc } from 'moment';
import randomstring from 'randomstring';
export const middlewares = ['auth'];
export const run = async (req: RequestWithUser, res: FastifyReply) => {
const now = utc().toDate();
const apiKey = randomstring.generate(64);
await prisma.users.update({
where: {
id: req.user.id
},
data: {
apiKey,
apiKeyEditedAt: now
}
});
return res.send({
message: 'Successfully created new api key',
apiKey
});
};

View File

@ -1,46 +0,0 @@
const bcrypt = require('bcrypt');
const moment = require('moment');
const Route = require('../../structures/Route');
const log = require('../../utils/Log');
class changePasswordPOST extends Route {
constructor() {
super('/user/password/change', 'post');
}
async run(req, res, db, user) {
if (!req.body) return res.status(400).json({ message: 'No body provided' });
const { password, newPassword } = req.body;
if (!password || !newPassword) return res.status(401).json({ message: 'Invalid body provided' });
if (password === newPassword) return res.status(400).json({ message: 'Passwords have to be different' });
/*
Checks if the password is right
*/
const comparePassword = await bcrypt.compare(password, user.password);
if (!comparePassword) return res.status(401).json({ message: 'Current password is incorrect' });
if (newPassword.length < 6 || newPassword.length > 64) {
return res.status(400).json({ message: 'Password must have 6-64 characters' });
}
let hash;
try {
hash = await bcrypt.hash(newPassword, 10);
} catch (error) {
log.error('Error generating password hash');
log.error(error);
return res.status(401).json({ message: 'There was a problem processing your account' });
}
const now = moment.utc().toDate();
await db.table('users').where('id', user.id).update({
password: hash,
passwordEditedAt: now
});
return res.json({ message: 'The password was changed successfully' });
}
}
module.exports = changePasswordPOST;

View File

@ -0,0 +1,11 @@
import type { FastifyReply } from 'fastify';
import type { RequestWithUser } from '../../../structures/interfaces';
export const middlewares = ['auth'];
export const run = async (req: RequestWithUser, res: FastifyReply) => res.send({
message: 'Successfully retrieved user',
user: {
...req.user
}
});

View File

@ -0,0 +1,54 @@
import type { FastifyReply } from 'fastify';
import type { RequestWithUser } from '../../../../structures/interfaces';
import prisma from '../../../../structures/database';
import bcrypt from 'bcryptjs';
import { utc } from 'moment';
interface body {
password: string;
newPassword: string;
}
export const run = async (req: RequestWithUser, res: FastifyReply) => {
if (!req.body) return res.status(400).send({ message: 'No body provided' });
const { password, newPassword } = req.body as body;
if (!password || !newPassword) return res.status(400).send({ message: 'Invalid body provided' });
if (password === newPassword) return res.status(400).send({ message: 'Passwords have to be different' });
const user = await prisma.users.findUnique({
where: {
id: req.user.id
},
select: {
password: true
}
});
const comparePassword = await bcrypt.compare(password, user?.password ?? '');
if (!comparePassword) return res.status(401).send({ message: 'Current password is incorrect' });
if (newPassword.length < 6 || newPassword.length > 64) {
return res.status(400).send({ message: 'Password must have 6-64 characters' });
}
let hash;
try {
hash = await bcrypt.hash(newPassword, 10);
} catch (err) {
req.log.error(err);
return res.status(401).send({ message: 'There was a problem processing your account' });
}
const now = utc().toDate();
await prisma.users.update({
where: {
id: req.user.id
},
data: {
password: hash,
passwordEditedAt: now
}
});
return res.send({ message: 'TThe password was changed successfully' });
};

View File

@ -1,21 +0,0 @@
const Route = require('../../structures/Route');
class usersGET extends Route {
constructor() {
super('/users/me', 'get');
}
run(req, res, db, user) {
return res.json({
message: 'Successfully retrieved user',
user: {
id: user.id,
username: user.username,
isAdmin: user.isAdmin,
apiKey: user.apiKey
}
});
}
}
module.exports = usersGET;

View File

@ -1,5 +1,5 @@
import type { FastifyReply } from 'fastify';
import { RequestWithUser } from '../../middlewares/auth';
import { RequestWithUser } from '../../structures/interfaces';
export const middlewares = ['auth'];

View File

@ -1,15 +0,0 @@
require('dotenv').config();
const Util = require('../utils/Util');
const start = async () => {
try {
await Util.writeConfigToDb(Util.getEnvironmentDefaults());
console.log('Configuration overwriten, you can now start chibisafe');
process.exit(0);
} catch (error) {
console.error(error);
}
};
start();

View File

@ -0,0 +1,23 @@
import dotenv from 'dotenv';
dotenv.config();
import { writeConfigToDb, getEnvironmentDefaults } from '../utils/Util';
const start = async () => {
try {
const defaults = getEnvironmentDefaults();
const keys = Object.keys(defaults);
for (const item of keys) {
await writeConfigToDb({
key: item,
value: defaults[item]
});
}
console.log('Configuration overwriten, you can now start chibisafe');
process.exit(0);
} catch (error) {
console.error(error);
}
};
void start();

View File

@ -1,77 +0,0 @@
const JWT = require('jsonwebtoken');
const db = require('./Database');
const moment = require('moment');
const log = require('../utils/Log');
const Util = require('../utils/Util');
class Route {
constructor(path, method, options) {
if (!path) throw new Error('Every route needs a URL associated with it.');
if (!method) throw new Error('Every route needs its method specified.');
this.path = path;
this.method = method;
this.options = options || {};
}
async authorize(req, res) {
const banned = await db
.table('bans')
.where({ ip: req.ip })
.first();
if (banned) return res.status(401).json({ message: 'This IP has been banned from using the service.' });
if (this.options.bypassAuth) return this.run(req, res, db);
// The only reason I call it token here and not Api Key is to be backwards compatible
// with the uploader and sharex
// Small price to pay.
if (req.headers.token) return this.authorizeApiKey(req, res, req.headers.token);
if (!req.headers.authorization) return res.status(401).json({ message: 'No authorization header provided' });
const token = req.headers.authorization.split(' ')[1];
if (!token) return res.status(401).json({ message: 'No authorization header provided' });
return JWT.verify(token, Util.config.secret, async (error, decoded) => {
if (error) {
log.error(error);
return res.status(401).json({ message: 'Invalid token' });
}
const id = decoded ? decoded.sub : '';
const iat = decoded ? decoded.iat : '';
const user = await db
.table('users')
.where({ id })
.first();
if (!user) return res.status(401).json({ message: 'Invalid authorization' });
if (iat && iat < moment(user.passwordEditedAt).format('x')) {
return res.status(401).json({ message: 'Token expired' });
}
if (!user.enabled) return res.status(401).json({ message: 'This account has been disabled' });
if (this.options.adminOnly && !user.isAdmin) { return res.status(401).json({ message: 'Invalid authorization' }); }
return this.run(req, res, db, user);
});
}
async authorizeApiKey(req, res, apiKey) {
if (!this.options.canApiKey) return res.status(401).json({ message: 'Api Key not allowed for this resource' });
const user = await db
.table('users')
.where({ apiKey })
.first();
if (!user) return res.status(401).json({ message: 'Invalid authorization' });
if (!user.enabled) return res.status(401).json({ message: 'This account has been disabled' });
return this.run(req, res, db, user);
}
run() {}
error(res, error) {
log.error(error);
return res.status(500).json({ message: 'There was a problem parsing the request' });
}
}
module.exports = Route;

View File

@ -1,123 +0,0 @@
require('dotenv').config();
if (!process.env.SERVER_PORT) {
console.log('Run the setup script first or fill the .env file manually before starting');
process.exit(0);
}
if (!process.env.DOMAIN) {
console.log('You failed to provide a domain for your instance. Edit the .env file manually and fix it.');
process.exit(0);
}
const { loadNuxt, build } = require('nuxt');
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const RateLimit = require('express-rate-limit');
const bodyParser = require('body-parser');
const jetpack = require('fs-jetpack');
const path = require('path');
const morgan = require('morgan');
const rfs = require('rotating-file-stream');
const CronJob = require('cron').CronJob;
const log = require('../utils/Log');
const Util = require('../utils/Util');
// eslint-disable-next-line no-unused-vars
const rateLimiter = new RateLimit({
windowMs: parseInt(Util.config.rateLimitWindow, 10),
max: parseInt(Util.config.rateLimitMax, 10),
delayMs: 0
});
class Server {
constructor() {
this.port = parseInt(process.env.SERVER_PORT, 10);
this.server = express();
this.server.set('trust proxy', 1);
this.server.use(helmet());
this.server.use(cors({ allowedHeaders: ['Accept', 'Authorization', 'Cache-Control', 'X-Requested-With', 'Content-Type', 'albumId', 'finishedChunks'] }));
this.server.use((req, res, next) => {
// This bypasses the headers.accept for album download, since it's accesed directly through the browser.
if ((req.url.includes('/api/album/') || req.url.includes('/zip')) && req.method === 'GET') return next();
// This bypasses the headers.accept if we are accessing the frontend
if (!req.url.includes('/api/') && req.method === 'GET') return next();
if (req.headers.accept && req.headers.accept.includes('application/vnd.chibisafe.json')) return next();
return res.status(405).json({ message: 'Incorrect `Accept` header provided' });
});
this.server.use(bodyParser.urlencoded({ extended: true }));
this.server.use(bodyParser.json());
if (process.env.NODE_ENV === 'production') {
const accessLogStream = rfs.createStream('access.log', {
interval: '1d', // rotate daily
path: path.join(__dirname, '../../../logs', 'log')
});
this.server.use(morgan('combined', { stream: accessLogStream }));
}
// Apply rate limiting to the api only
this.server.use('/api/', rateLimiter);
// Serve the uploads
this.server.use(express.static(path.join(__dirname, '../../../uploads')));
this.routesFolder = path.join(__dirname, '../routes');
// Save the cron job instances in case we want to stop them later
this.jobs = {};
}
registerAllTheRoutes() {
jetpack.find(this.routesFolder, { matching: '*.js' }).forEach(routeFile => {
const RouteClass = require(path.join('../../../', routeFile));
let routes = [RouteClass];
if (Array.isArray(RouteClass)) routes = RouteClass;
for (const File of routes) {
try {
const route = new File();
this.server[route.method](`/api${route.path}`, route.authorize.bind(route));
log.info(`Found route ${route.method.toUpperCase()} /api${route.path}`);
} catch (e) {
log.error(`Failed loading route from file ${routeFile} with error: ${e.message}`);
}
}
});
}
async serveNuxt() {
const isProd = process.env.NODE_ENV === 'production';
const nuxt = await loadNuxt(isProd ? 'start' : 'dev');
this.server.use(nuxt.render);
if (!isProd) {
build(nuxt);
}
}
createJobs() {
// TODO: move into the database config. (we can just show the crontab line for start, later on we can add dropdowns and stuff)
this.jobs.stats = new CronJob('0 0 * * * *', Util.saveStatsToDb, null, true);
}
start() {
jetpack.dir('uploads/chunks');
jetpack.dir('uploads/thumbs/square');
jetpack.dir('uploads/thumbs/preview');
this.registerAllTheRoutes();
this.serveNuxt();
const server = this.server.listen(this.port, () => {
log.success(`Backend ready and listening on port ${this.port}`);
});
server.setTimeout(600000);
this.createJobs();
}
}
const start = async () => {
const conf = await Util.config;
new Server().start();
};
start();

View File

@ -1,3 +1,14 @@
import type { FastifyRequest } from 'fastify';
export interface RequestWithUser extends FastifyRequest {
user: {
id: number;
username: string;
isAdmin: boolean;
apiKey?: string;
};
}
export interface User {
id: number;
username: string;

View File

@ -2,7 +2,7 @@ import jetpack from 'fs-jetpack';
import randomstring from 'randomstring';
import path from 'path';
import prisma from '../structures/database';
import moment from 'moment';
import { utc } from 'moment';
import Zip from 'adm-zip';
import { generateThumbnails, getFileThumbnail, removeThumbs } from './ThumbUtil';
import { getStats } from './StatsGenerator';
@ -273,7 +273,7 @@ export const storeFileToDb = async (req: FastifyRequest, res: FastifyReply, user
return;
}
const now = moment.utc().toDate();
const now = utc().toDate();
const data = {
userId: user.id ? user.id : undefined,
name: file.data.filename,
@ -300,7 +300,7 @@ export const storeFileToDb = async (req: FastifyRequest, res: FastifyReply, user
export const saveFileToAlbum = async (albumId: number, insertedId: number) => {
if (!albumId) return;
const now = moment.utc().toDate();
const now = utc().toDate();
try {
await prisma.albumsFiles.create({
data: {
@ -367,7 +367,7 @@ export const saveStatsToDb = async (force: boolean) => {
return;
}
const now = moment.utc().toDate();
const now = utc().toDate();
const stats = await getStats(db);
let batchId = 1;

View File

@ -1,6 +1,7 @@
/* eslint-disable no-console */
const jetpack = require('fs-jetpack');
const qoa = require('qoa');
import jetpack from 'fs-jetpack';
// @ts-ignore no typings for qoa
import qoa from 'qoa';
qoa.config({
prefix: '>',
@ -86,7 +87,8 @@ async function start() {
console.log('== MAKE SURE TO CHANGE IT AFTER YOUR FIRST LOGIN ==');
console.log('=====================================================');
console.log();
// eslint-disable-next-line @typescript-eslint/no-empty-function
setTimeout(() => {}, 1000);
}
start();
void start();

0
types/index.d.ts vendored
View File