From 3c303241e13593c1aa578e544a77324f1db50399 Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Sat, 9 Jan 2021 01:03:03 +0200 Subject: feat: create settings schema (used for rendering and validating) --- src/api/structures/Setting.js | 126 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 src/api/structures/Setting.js (limited to 'src/api/structures') diff --git a/src/api/structures/Setting.js b/src/api/structures/Setting.js new file mode 100644 index 0000000..068ebf1 --- /dev/null +++ b/src/api/structures/Setting.js @@ -0,0 +1,126 @@ +require('dotenv').config(); + +const Joi = require('joi'); +const { env } = process; + +const StatsGenerator = require('../utils/StatsGenerator'); + +// 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({ + // Server related settings + rateLimitWindow: Joi.number().integer().default(2) + .label('API rate limit window') + .description('Timeframe for which requests are checked/remembered'), + + rateLimitMax: Joi.number().integer().default(5) + .label('API maximum limit') + .description('Max number of connections during windowMs milliseconds before sending a 429 response'), + + // Service settings + serviceName: Joi.string().default('change-me') + .label('Service name') + .description('Name of the service'), + + domain: Joi.string().default(`http://localhost:${env.SERVER_PORT}`) + .label('Domain') + .description('Full domain this instance is gonna be running on'), + + // File related settings + chunkSize: Joi.number().integer().greater(0) + .default(90) + .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) + .label('Maximum file size') + .description('Maximum allowed upload file size in MB (0 to disable)'), + + generateZips: Joi.boolean().default(true) + .label('Generate zips') + .description('Allows users to download entire albums in ZIP format'), + + generatedFileNameLength: Joi.number().integer().min(6) + .default(12) + .label('Generated file name length') + .description('How long should the automatically generated file name be'), + + generatedAlbumLength: Joi.number().integer().min(6) + .default(6) + .label('Generated album name length') + .description('How long should the automatically generated album identifier be'), + + maxLinksPerAlbum: Joi.number().integer().greater(0) + .default(5) + .label('Maximum album links') + .description('Maximum allowed number of a distinct links for an album'), + + uploadsFolder: Joi.string().default('uploads') + .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']) + .label('Blocked extensions') + .description('List of extensions which will be rejected by the server'), + + // User settings + publicMode: Joi.boolean().default(true) + .label('Public mode') + .description('Allows people to upload files without an account'), + + userAccount: Joi.boolean().default(true) + .label('User creation') + .description('Allows people to create new accounts'), + + // Social and sharing + metaThemeColor: Joi.string().hex().min(3) + .max(6) + .default('20222b') + .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! 🚀') + .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') + .label('Meta keywords') + .description('Words relevant to the page\'s content separated by commas'), + + metaTwitterHandle: Joi.string().pattern(/^@\w{1,15}$/, { name: 'twitter handle' }) + .label('Twitter handle') + .description('Your twitter handle'), + + // Instance settings + backgroundImageURL: Joi.string().uri().default(p => `${p.domain}/assets/images/background.jpg`) + .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`) + .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 * * * *') + .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({ 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({ displayType: 'checkbox' }) + .label('Cached statistics') + .description('Which statistics should be saved to the database (refer to Statistics schedule for scheduling). If a statistics is enabled but not set to be saved, it will be generated every time the statistics page is opened') +}); + +// schema._ids._byKey.keys() + +module.exports.schema = schema; -- cgit v1.2.3 From 46ef63fb9f3c2688118a9d1511293128cfdfe4c9 Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Sun, 10 Jan 2021 02:04:35 +0200 Subject: feat: add dynamic settings page rendering based on the Joi object --- src/api/structures/Setting.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Setting.js b/src/api/structures/Setting.js index 068ebf1..7b2041d 100644 --- a/src/api/structures/Setting.js +++ b/src/api/structures/Setting.js @@ -9,15 +9,6 @@ const StatsGenerator = require('../utils/StatsGenerator'); // 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({ - // Server related settings - rateLimitWindow: Joi.number().integer().default(2) - .label('API rate limit window') - .description('Timeframe for which requests are checked/remembered'), - - rateLimitMax: Joi.number().integer().default(5) - .label('API maximum limit') - .description('Max number of connections during windowMs milliseconds before sending a 429 response'), - // Service settings serviceName: Joi.string().default('change-me') .label('Service name') @@ -118,9 +109,18 @@ const schema = Joi.object({ savedStatistics: Joi.array().items(Joi.string().valid(...Object.keys(StatsGenerator.statGenerators)).optional()) .meta({ displayType: 'checkbox' }) .label('Cached statistics') - .description('Which statistics should be saved to the database (refer to Statistics schedule for scheduling). If a statistics is enabled but not set to be saved, it will be generated every time the statistics page is opened') -}); + .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'), -// schema._ids._byKey.keys() + // Server related settings + rateLimitWindow: Joi.number().integer().default(2) + .label('API rate limit window') + .description('Timeframe for which requests are checked/remembered'), + + rateLimitMax: Joi.number().integer().default(5) + .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(); -- cgit v1.2.3 From d69fcd856a47b04f964c658edfa2bed3e6f0abc1 Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Mon, 11 Jan 2021 12:51:45 +0200 Subject: feat: add sections to settings object meta --- src/api/structures/Setting.js | 75 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 2 deletions(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Setting.js b/src/api/structures/Setting.js index 7b2041d..ff98339 100644 --- a/src/api/structures/Setting.js +++ b/src/api/structures/Setting.js @@ -5,65 +5,113 @@ 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'), @@ -71,53 +119,76 @@ const schema = Joi.object({ metaThemeColor: Joi.string().hex().min(3) .max(6) .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({ displayType: 'checkbox' }) + .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({ displayType: 'checkbox' }) + .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') }); -- cgit v1.2.3 From 3f223a9dbfd3f79c1a8f01c6a95d14035cddeefe Mon Sep 17 00:00:00 2001 From: Pitu Date: Thu, 25 Mar 2021 02:03:57 +0900 Subject: feat: implement initial values and saving to db logic --- src/api/structures/Database.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/api/structures') 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 => { -- cgit v1.2.3 From d3c80127ecdc83cffb9ba9a05f47452fec60287c Mon Sep 17 00:00:00 2001 From: Pitu Date: Tue, 15 Jun 2021 00:12:26 +0900 Subject: chore: update process.env usage --- src/api/structures/Route.js | 3 ++- src/api/structures/Server.js | 19 +++++++++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) (limited to 'src/api/structures') 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..53be9fb 100644 --- a/src/api/structures/Server.js +++ b/src/api/structures/Server.js @@ -19,11 +19,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 }); @@ -65,6 +64,7 @@ class Server { } registerAllTheRoutes() { + console.log(Util.config); jetpack.find(this.routesFolder, { matching: '*.js' }).forEach(routeFile => { const RouteClass = require(path.join('../../../', routeFile)); let routes = [RouteClass]; @@ -72,8 +72,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 +110,11 @@ class Server { } } -new Server().start(); +const start = async () => { + const conf = await Util.config; + console.log(conf); + new Server().start(); +}; + +start(); + -- cgit v1.2.3 From 0cae7e9eda3b62c17cfa7ec620913f4a504bc5ee Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Thu, 17 Jun 2021 16:06:53 +0300 Subject: feat: show validation errors from joi on the frontend --- src/api/structures/Setting.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Setting.js b/src/api/structures/Setting.js index ff98339..7650ccb 100644 --- a/src/api/structures/Setting.js +++ b/src/api/structures/Setting.js @@ -116,9 +116,9 @@ const schema = Joi.object({ .description('Allows people to create new accounts'), // Social and sharing - metaThemeColor: Joi.string().hex().min(3) - .max(6) - .default('20222b') + 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 }) -- cgit v1.2.3 From ed9fa0fa7255ea8cbd1537449f2bc28f6f992468 Mon Sep 17 00:00:00 2001 From: Pitu Date: Thu, 17 Jun 2021 23:31:28 +0900 Subject: chore: remove console.log --- src/api/structures/Server.js | 2 -- 1 file changed, 2 deletions(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Server.js b/src/api/structures/Server.js index 53be9fb..0ee3ea3 100644 --- a/src/api/structures/Server.js +++ b/src/api/structures/Server.js @@ -64,7 +64,6 @@ class Server { } registerAllTheRoutes() { - console.log(Util.config); jetpack.find(this.routesFolder, { matching: '*.js' }).forEach(routeFile => { const RouteClass = require(path.join('../../../', routeFile)); let routes = [RouteClass]; @@ -112,7 +111,6 @@ class Server { const start = async () => { const conf = await Util.config; - console.log(conf); new Server().start(); }; -- cgit v1.2.3 From 940ab07d35e739c6479da9f312c6ee760e107309 Mon Sep 17 00:00:00 2001 From: Pitu Date: Fri, 18 Jun 2021 01:45:19 +0900 Subject: feat: prevent running without a domain --- src/api/structures/Server.js | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/api/structures') diff --git a/src/api/structures/Server.js b/src/api/structures/Server.js index 0ee3ea3..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'); -- cgit v1.2.3