aboutsummaryrefslogtreecommitdiff
path: root/src/api/structures
diff options
context:
space:
mode:
Diffstat (limited to 'src/api/structures')
-rw-r--r--src/api/structures/Database.js2
-rw-r--r--src/api/structures/Route.js3
-rw-r--r--src/api/structures/Server.js22
-rw-r--r--src/api/structures/Setting.js197
4 files changed, 216 insertions, 8 deletions
diff --git a/src/api/structures/Database.js b/src/api/structures/Database.js
index 39632a1..ed30c50 100644
--- a/src/api/structures/Database.js
+++ b/src/api/structures/Database.js
@@ -23,7 +23,7 @@ const db = Knex({
some things like different data types for booleans need to be considered like in
the implementation below where sqlite returns 1 and 0 instead of true and false.
*/
- const booleanFields = ['enabled', 'enableDownload', 'isAdmin', 'nsfw'];
+ const booleanFields = ['enabled', 'enableDownload', 'isAdmin', 'nsfw', 'generateZips', 'publicMode', 'userAccounts'];
const processResponse = row => {
Object.keys(row).forEach(key => {
diff --git a/src/api/structures/Route.js b/src/api/structures/Route.js
index 24d45b2..9496d0f 100644
--- a/src/api/structures/Route.js
+++ b/src/api/structures/Route.js
@@ -2,6 +2,7 @@ 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) {
@@ -30,7 +31,7 @@ class Route {
const token = req.headers.authorization.split(' ')[1];
if (!token) return res.status(401).json({ message: 'No authorization header provided' });
- return JWT.verify(token, process.env.SECRET, async (error, decoded) => {
+ return JWT.verify(token, Util.config.secret, async (error, decoded) => {
if (error) {
log.error(error);
return res.status(401).json({ message: 'Invalid token' });
diff --git a/src/api/structures/Server.js b/src/api/structures/Server.js
index 268ba68..f584fe8 100644
--- a/src/api/structures/Server.js
+++ b/src/api/structures/Server.js
@@ -5,6 +5,11 @@ if (!process.env.SERVER_PORT) {
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');
@@ -19,11 +24,10 @@ 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(process.env.RATE_LIMIT_WINDOW, 10),
- max: parseInt(process.env.RATE_LIMIT_MAX, 10),
+ windowMs: parseInt(Util.config.rateLimitWindow, 10),
+ max: parseInt(Util.config.rateLimitMax, 10),
delayMs: 0
});
@@ -72,8 +76,8 @@ class Server {
for (const File of routes) {
try {
const route = new File();
- this.server[route.method](process.env.ROUTE_PREFIX + route.path, route.authorize.bind(route));
- log.info(`Found route ${route.method.toUpperCase()} ${process.env.ROUTE_PREFIX}${route.path}`);
+ this.server[route.method](Util.config.routePrefix + route.path, route.authorize.bind(route));
+ log.info(`Found route ${route.method.toUpperCase()} ${Util.config.routePrefix}${route.path}`);
} catch (e) {
log.error(`Failed loading route from file ${routeFile} with error: ${e.message}`);
}
@@ -110,4 +114,10 @@ class Server {
}
}
-new Server().start();
+const start = async () => {
+ const conf = await Util.config;
+ new Server().start();
+};
+
+start();
+
diff --git a/src/api/structures/Setting.js b/src/api/structures/Setting.js
new file mode 100644
index 0000000..7650ccb
--- /dev/null
+++ b/src/api/structures/Setting.js
@@ -0,0 +1,197 @@
+require('dotenv').config();
+
+const Joi = require('joi');
+const { env } = process;
+
+const StatsGenerator = require('../utils/StatsGenerator');
+
+const Sections = Object.freeze({
+ SERVICE: 'Service',
+ FILE: 'File',
+ USERS: 'Users',
+ SOCIAL_AND_SHARING: 'Social and sharing',
+ INSTANCE: 'Instance',
+ STATISTICS: 'Statistics',
+ SERVER: 'Server',
+ OTHER: 'Other'
+});
+
+// use label to name them nicely
+// use meta to set custom rendering (render as radio instead of dropdown for example) and custom order
+// use description to add comments which will show up as a note somewhere next to the option
+const schema = Joi.object({
+ // Service settings
+ serviceName: Joi.string().default('change-me')
+ // .meta({ section })
+ .meta({
+ section: Sections.SERVICE
+ })
+ .label('Service name')
+ .description('Name of the service'),
+
+ domain: Joi.string().default(`http://localhost:${env.SERVER_PORT}`)
+ .meta({
+ section: Sections.SERVICE
+ })
+ .label('Domain')
+ .description('Full domain this instance is gonna be running on'),
+
+ // File related settings
+ chunkSize: Joi.number().integer().greater(0)
+ .default(90)
+ .meta({
+ section: Sections.FILE
+ })
+ .label('Chunk size')
+ .description('Maximum size of a chunk (files bigger than this limit will be split into multiple chunks)'),
+
+ maxSize: Joi.number().integer().min(0) // setting it to 0 disabled the limit
+ .default(5000)
+ .meta({
+ section: Sections.FILE
+ })
+ .label('Maximum file size')
+ .description('Maximum allowed upload file size in MB (0 to disable)'),
+
+ generateZips: Joi.boolean().default(true)
+ .meta({
+ section: Sections.FILE
+ })
+ .label('Generate zips')
+ .description('Allows users to download entire albums in ZIP format'),
+
+ generatedFileNameLength: Joi.number().integer().min(6)
+ .default(12)
+ .meta({
+ section: Sections.FILE
+ })
+ .label('Generated file name length')
+ .description('How long should the automatically generated file name be'),
+
+ generatedAlbumLength: Joi.number().integer().min(6)
+ .default(6)
+ .meta({
+ section: Sections.FILE
+ })
+ .label('Generated album name length')
+ .description('How long should the automatically generated album identifier be'),
+
+ maxLinksPerAlbum: Joi.number().integer().greater(0)
+ .default(5)
+ .meta({
+ section: Sections.FILE
+ })
+ .label('Maximum album links')
+ .description('Maximum allowed number of a distinct links for an album'),
+
+ uploadsFolder: Joi.string().default('uploads')
+ .meta({
+ section: Sections.FILE
+ })
+ .label('Uploads folder')
+ .description('Name of the folder where the uploads will be stored'),
+
+ blockedExtensions: Joi.array()
+ .items(Joi.string().pattern(/^(\.\w+)+$/, { name: 'file extension' }))
+ .default(['.jar', '.exe', '.msi', '.com', '.bat', '.cmd', '.scr', '.ps1', '.sh'])
+ .meta({
+ section: Sections.FILE
+ })
+ .label('Blocked extensions')
+ .description('List of extensions which will be rejected by the server'),
+
+ // User settings
+ publicMode: Joi.boolean().default(true)
+ .meta({
+ section: Sections.USERS
+ })
+ .label('Public mode')
+ .description('Allows people to upload files without an account'),
+
+ userAccount: Joi.boolean().default(true)
+ .meta({
+ section: Sections.USERS
+ })
+ .label('User creation')
+ .description('Allows people to create new accounts'),
+
+ // Social and sharing
+ metaThemeColor: Joi.string().pattern(/^#([0-9a-f]{6}|[0-9a-f]{3})$/i).min(4)
+ .max(7)
+ .default('#20222b')
+ .meta({
+ section: Sections.SOCIAL_AND_SHARING
+ })
+ .label('Meta theme color')
+ .description('Color that user agents should use to customize the display of the page/embeds'),
+
+ metaDescription: Joi.string().default('Blazing fast file uploader and bunker written in node! 🚀')
+ .meta({
+ section: Sections.SOCIAL_AND_SHARING
+ })
+ .label('Meta description')
+ .description('Short and accurate summary of the content of the page'),
+
+ metaKeyword: Joi.string().default('chibisafe,lolisafe,upload,uploader,file,vue,images,ssr,file uploader,free')
+ .meta({
+ section: Sections.SOCIAL_AND_SHARING
+ })
+ .label('Meta keywords')
+ .description('Words relevant to the page\'s content separated by commas'),
+
+ metaTwitterHandle: Joi.string().pattern(/^@\w{1,15}$/, { name: 'twitter handle' })
+ .meta({
+ section: Sections.SOCIAL_AND_SHARING
+ })
+ .label('Twitter handle')
+ .description('Your twitter handle'),
+
+ // Instance settings
+ backgroundImageURL: Joi.string().uri().default(p => `${p.domain}/assets/images/background.jpg`)
+ .meta({
+ section: Sections.INSTANCE
+ })
+ .label('Background image link')
+ .description('Background image that should be used instead of the default background'),
+
+ logoURL: Joi.string().uri().default(p => `${p.domain}/assets/images/logo.jpg`)
+ .meta({
+ section: Sections.INSTANCE
+ })
+ .label('Logo image link')
+ .description('Logo image that should be used instead of the default logo'),
+
+ // Statistics settings
+ // TODO: Pattern fails for patterns like 0 1,2-7 * * * * because of the mixing of lists and ranges
+ statisticsCron: Joi.string().pattern(/((((\d+,)+\d+|([\d\*]+(\/|-)\d+)|\d+|\*) ?){6})/, { name: 'cron' }).default('0 0 * * * *')
+ .meta({
+ section: Sections.STATISTICS
+ })
+ .label('Statistics schedule')
+ .description('Crontab like formated value which will be used to schedule generating and saving stats to the database'),
+
+ enabledStatistics: Joi.array().items(Joi.string().valid(...Object.keys(StatsGenerator.statGenerators)).optional())
+ .meta({ section: Sections.STATISTICS, displayType: 'checkbox' })
+ .label('Enabled statistics')
+ .description('Which statistics should be shown when opening the statistics page'),
+
+ savedStatistics: Joi.array().items(Joi.string().valid(...Object.keys(StatsGenerator.statGenerators)).optional())
+ .meta({ section: Sections.STATISTICS, displayType: 'checkbox' })
+ .label('Cached statistics')
+ .description('Which statistics should be saved to the database (refer to Statistics schedule for scheduling).')
+ .note('If a statistics is enabled but not set to be saved, it will be generated every time the statistics page is opened'),
+
+ // Server related settings
+ rateLimitWindow: Joi.number().integer().default(2)
+ .meta({ section: Sections.SERVER })
+ .label('API rate limit window')
+ .description('Timeframe for which requests are checked/remembered'),
+
+ rateLimitMax: Joi.number().integer().default(5)
+ .meta({ section: Sections.SERVER })
+ .label('API maximum limit')
+ .description('Max number of connections during windowMs milliseconds before sending a 429 response')
+});
+
+module.exports.schema = schema;
+module.exports.configSchema = schema.describe();