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') 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/routes/service/configSchemaGET.js | 17 +++++++++++++++++ src/api/structures/Setting.js | 24 ++++++++++++------------ 2 files changed, 29 insertions(+), 12 deletions(-) create mode 100644 src/api/routes/service/configSchemaGET.js (limited to 'src/api') diff --git a/src/api/routes/service/configSchemaGET.js b/src/api/routes/service/configSchemaGET.js new file mode 100644 index 0000000..90befa9 --- /dev/null +++ b/src/api/routes/service/configSchemaGET.js @@ -0,0 +1,17 @@ +const Route = require('../../structures/Route'); +const { configSchema } = require('../../structures/Setting'); + +class configGET extends Route { + constructor() { + super('/service/config/schema', 'get', { adminOnly: true }); + } + + run(req, res) { + return res.json({ + message: 'Successfully retrieved schema', + schema: configSchema + }); + } +} + +module.exports = configGET; 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') 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 541bfedb924ba80f126236656713f769c2194ef2 Mon Sep 17 00:00:00 2001 From: Pitu Date: Tue, 23 Mar 2021 22:35:29 +0900 Subject: wip --- .../migrations/20210112011802_addSettingsTable.js | 10 ++++++++ src/api/utils/Util.js | 28 ++++++++++++++++++---- 2 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 src/api/database/migrations/20210112011802_addSettingsTable.js (limited to 'src/api') diff --git a/src/api/database/migrations/20210112011802_addSettingsTable.js b/src/api/database/migrations/20210112011802_addSettingsTable.js new file mode 100644 index 0000000..2708352 --- /dev/null +++ b/src/api/database/migrations/20210112011802_addSettingsTable.js @@ -0,0 +1,10 @@ +exports.up = async knex => { + await knex.schema.createTable('settings', table => { + table.string('key').unique(); + table.json('value').notNullable(); + }); +}; + +exports.down = async knex => { + await knex.schema.dropTableIfExists('settings'); +}; diff --git a/src/api/utils/Util.js b/src/api/utils/Util.js index 6feedd4..b60fca3 100644 --- a/src/api/utils/Util.js +++ b/src/api/utils/Util.js @@ -15,10 +15,30 @@ const StatsGenerator = require('./StatsGenerator'); const blockedExtensions = process.env.BLOCKED_EXTENSIONS.split(','); const preserveExtensions = ['.tar.gz', '.tar.z', '.tar.bz2', '.tar.lzma', '.tar.lzo', '.tar.xz']; -let statsLastSavedTime = null; - class Util { static uploadPath = path.join(__dirname, '../../../', process.env.UPLOAD_FOLDER); + static statsLastSavedTime = null; + static _config = null; + + static get config() { + return (async () => { + if (this._config === null) { + const conf = await db('config').select('key', 'value'); + this._config = conf.reduce((acc, { key, value }) => { + if (typeof value === 'string' || value instanceof String) { + acc[key] = JSON.parse(value); + } else { + acc[key] = value; + } + }, {}); + } + return this._config; + })(); + } + + static invalidateConfigCache() { + this._config = null; + } static uuid() { return uuidv4(); @@ -320,7 +340,7 @@ class Util { // skip generating and saving new stats. if (!force && (!db.userParams.lastMutationTime || - (statsLastSavedTime && statsLastSavedTime > db.userParams.lastMutationTime) + (Util.statsLastSavedTime && Util.statsLastSavedTime > db.userParams.lastMutationTime) ) ) { return; @@ -341,7 +361,7 @@ class Util { await db.table('statistics').insert({ type, data: JSON.stringify(data), createdAt: now, batchId }); } - statsLastSavedTime = now.getTime(); + Util.statsLastSavedTime = now.getTime(); } catch (error) { console.error(error); } -- cgit v1.2.3 From abd7a1ca2e058854e6335b54f4119f009961a5ab Mon Sep 17 00:00:00 2001 From: Pitu Date: Thu, 25 Mar 2021 00:09:33 +0900 Subject: chore: move database migration script --- src/api/databaseMigration.js | 140 ----------------------------------- src/api/scripts/databaseMigration.js | 135 +++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 140 deletions(-) delete mode 100644 src/api/databaseMigration.js create mode 100644 src/api/scripts/databaseMigration.js (limited to 'src/api') diff --git a/src/api/databaseMigration.js b/src/api/databaseMigration.js deleted file mode 100644 index 1e3518e..0000000 --- a/src/api/databaseMigration.js +++ /dev/null @@ -1,140 +0,0 @@ -require('dotenv').config(); - -const nodePath = require('path'); -const moment = require('moment'); -const jetpack = require('fs-jetpack'); -const ThumbUtil = require('./utils/ThumbUtil'); - -const oldDb = require('knex')({ - client: 'sqlite3', - connection: { - filename: nodePath.join(__dirname, '../../', 'db') - }, - useNullAsDefault: true -}); - -const newDb = require('knex')({ - client: 'sqlite3', - connection: { - filename: nodePath.join(__dirname, '../../database/', 'database.sqlite') - }, - postProcessResponse: result => { - const booleanFields = [ - 'enabled', - 'enableDownload', - 'isAdmin', - 'nsfw' - ]; - - const processResponse = row => { - Object.keys(row).forEach(key => { - if (booleanFields.includes(key)) { - if (row[key] === 0) row[key] = false; - else if (row[key] === 1) row[key] = true; - } - }); - return row; - }; - - if (Array.isArray(result)) return result.map(row => processResponse(row)); - if (typeof result === 'object') return processResponse(result); - return result; - }, - useNullAsDefault: true -}); - -const start = async () => { - console.log('Starting migration, this may take a few minutes...'); // Because I half assed it - console.log('Please do NOT kill the process. Wait for it to finish.'); - - await jetpack.removeAsync(nodePath.join(__dirname, '../../uploads/thumbs')); - await jetpack.dirAsync(nodePath.join(__dirname, '../../uploads/thumbs/square')); - console.log('Finished deleting old thumbnails to create new ones'); - - const users = await oldDb.table('users').where('username', '<>', 'root'); - for (const user of users) { - const now = moment.utc().toDate(); - const userToInsert = { - id: user.id, - username: user.username, - password: user.password, - enabled: user.enabled, - isAdmin: false, - apiKey: user.token, - passwordEditedAt: now, - apiKeyEditedAt: now, - createdAt: now, - editedAt: now - }; - await newDb.table('users').insert(userToInsert); - } - console.log('Finished migrating users...'); - - const albums = await oldDb.table('albums'); - for (const album of albums) { - if (!album.enabled || album.enabled == 0) continue; - const now = moment.utc().toDate(); - const albumToInsert = { - id: album.id, - userId: album.userid, - name: album.name, - zippedAt: album.zipGeneratedAt ? moment.unix(album.zipGeneratedAt).toDate() : null, - createdAt: moment.unix(album.timestamp).toDate(), - editedAt: moment.unix(album.editedAt).toDate() - }; - const linkToInsert = { - userId: album.userid, - albumId: album.id, - identifier: album.identifier, - views: 0, - enabled: true, - enableDownload: true, - createdAt: now, - editedAt: now - }; - await newDb.table('albums').insert(albumToInsert); - const insertedId = await newDb.table('links').insert(linkToInsert); - await newDb.table('albumsLinks').insert({ - albumId: album.id, - linkId: insertedId[0] - }); - } - console.log('Finished migrating albums...'); - - const files = await oldDb.table('files'); - const filesToInsert = []; - const albumsFilesToInsert = []; - for (const file of files) { - const fileToInsert = { - id: file.id, - userId: file.userid, - name: file.name, - original: file.original, - type: file.type, - size: file.size, - hash: file.hash, - ip: file.ip, - createdAt: moment.unix(file.timestamp).toDate(), - editedAt: moment.unix(file.timestamp).toDate() - }; - filesToInsert.push(fileToInsert); - if (file.albumid) { - albumsFilesToInsert.push({ - albumId: file.albumid, - fileId: file.id - }); - } - - const filename = file.name; - if (!jetpack.exists(nodePath.join(__dirname, '../../uploads', filename))) continue; - ThumbUtil.generateThumbnails(filename); - } - await newDb.batchInsert('files', filesToInsert, 20); - await newDb.batchInsert('albumsFiles', albumsFilesToInsert, 20); - console.log('Finished migrating files...'); - - console.log('Finished migrating everything. '); - process.exit(0); -}; - -start(); diff --git a/src/api/scripts/databaseMigration.js b/src/api/scripts/databaseMigration.js new file mode 100644 index 0000000..8fab0ac --- /dev/null +++ b/src/api/scripts/databaseMigration.js @@ -0,0 +1,135 @@ +require('dotenv').config(); + +const nodePath = require('path'); +const moment = require('moment'); +const jetpack = require('fs-jetpack'); +const ThumbUtil = require('../utils/ThumbUtil'); + +const oldDb = require('knex')({ + client: 'sqlite3', + connection: { + filename: nodePath.join(__dirname, '../../', 'db') + }, + useNullAsDefault: true +}); + +const newDb = require('knex')({ + client: 'sqlite3', + connection: { + filename: nodePath.join(__dirname, '../../database/', 'database.sqlite') + }, + postProcessResponse: result => { + const booleanFields = ['enabled', 'enableDownload', 'isAdmin', 'nsfw', 'generateZips', 'publicMode', 'userAccounts']; + + const processResponse = row => { + Object.keys(row).forEach(key => { + if (booleanFields.includes(key)) { + if (row[key] === 0) row[key] = false; + else if (row[key] === 1) row[key] = true; + } + }); + return row; + }; + + if (Array.isArray(result)) return result.map(row => processResponse(row)); + if (typeof result === 'object') return processResponse(result); + return result; + }, + useNullAsDefault: true +}); + +const start = async () => { + console.log('Starting migration, this may take a few minutes...'); // Because I half assed it + console.log('Please do NOT kill the process. Wait for it to finish.'); + + await jetpack.removeAsync(nodePath.join(__dirname, '../../uploads/thumbs')); + await jetpack.dirAsync(nodePath.join(__dirname, '../../uploads/thumbs/square')); + console.log('Finished deleting old thumbnails to create new ones'); + + const users = await oldDb.table('users').where('username', '<>', 'root'); + for (const user of users) { + const now = moment.utc().toDate(); + const userToInsert = { + id: user.id, + username: user.username, + password: user.password, + enabled: user.enabled, + isAdmin: false, + apiKey: user.token, + passwordEditedAt: now, + apiKeyEditedAt: now, + createdAt: now, + editedAt: now + }; + await newDb.table('users').insert(userToInsert); + } + console.log('Finished migrating users...'); + + const albums = await oldDb.table('albums'); + for (const album of albums) { + if (!album.enabled || album.enabled == 0) continue; + const now = moment.utc().toDate(); + const albumToInsert = { + id: album.id, + userId: album.userid, + name: album.name, + zippedAt: album.zipGeneratedAt ? moment.unix(album.zipGeneratedAt).toDate() : null, + createdAt: moment.unix(album.timestamp).toDate(), + editedAt: moment.unix(album.editedAt).toDate() + }; + const linkToInsert = { + userId: album.userid, + albumId: album.id, + identifier: album.identifier, + views: 0, + enabled: true, + enableDownload: true, + createdAt: now, + editedAt: now + }; + await newDb.table('albums').insert(albumToInsert); + const insertedId = await newDb.table('links').insert(linkToInsert); + await newDb.table('albumsLinks').insert({ + albumId: album.id, + linkId: insertedId[0] + }); + } + console.log('Finished migrating albums...'); + + const files = await oldDb.table('files'); + const filesToInsert = []; + const albumsFilesToInsert = []; + for (const file of files) { + const fileToInsert = { + id: file.id, + userId: file.userid, + name: file.name, + original: file.original, + type: file.type, + size: file.size, + hash: file.hash, + ip: file.ip, + createdAt: moment.unix(file.timestamp).toDate(), + editedAt: moment.unix(file.timestamp).toDate() + }; + filesToInsert.push(fileToInsert); + if (file.albumid) { + albumsFilesToInsert.push({ + albumId: file.albumid, + fileId: file.id + }); + } + + const filename = file.name; + if (!jetpack.exists(nodePath.join(__dirname, '../../uploads', filename))) continue; + ThumbUtil.generateThumbnails(filename); + } + await newDb.batchInsert('files', filesToInsert, 20); + await newDb.batchInsert('albumsFiles', albumsFilesToInsert, 20); + console.log('Finished migrating files...'); + + console.log('Finished migrating everything. '); + process.exit(0); +}; + +start(); -- 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 --- .../migrations/20210112011802_addSettingsTable.js | 24 +++++++++++-- src/api/database/seeds/initial.js | 13 ++++++++ src/api/scripts/overwriteConfig.js | 15 +++++++++ src/api/structures/Database.js | 2 +- src/api/utils/Util.js | 39 ++++++++++++++++++++++ 5 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 src/api/scripts/overwriteConfig.js (limited to 'src/api') diff --git a/src/api/database/migrations/20210112011802_addSettingsTable.js b/src/api/database/migrations/20210112011802_addSettingsTable.js index 2708352..3acd845 100644 --- a/src/api/database/migrations/20210112011802_addSettingsTable.js +++ b/src/api/database/migrations/20210112011802_addSettingsTable.js @@ -1,7 +1,27 @@ exports.up = async knex => { await knex.schema.createTable('settings', table => { - table.string('key').unique(); - table.json('value').notNullable(); + table.string('routePrefix'); + table.integer('rateLimitWindow'); + table.integer('rateLimitMax'); + table.string('secret'); + table.string('serviceName'); + table.string('domain'); + table.integer('chunkSize'); + table.integer('maxSize'); + table.boolean('generateZips'); + table.integer('generatedFilenameLength'); + table.integer('generatedAlbumLength'); + table.integer('maxLinksPerAlbum'); + table.string('uploadFolder'); + table.json('blockedExtensions'); + table.boolean('publicMode'); + table.boolean('userAccounts'); + table.string('adminAccount'); + table.string('adminPassword'); + table.string('metaThemeColor'); + table.string('metaDescription'); + table.string('metaKeywords'); + table.string('metaTwitterHandle'); }); }; diff --git a/src/api/database/seeds/initial.js b/src/api/database/seeds/initial.js index edc1949..bb60b2c 100644 --- a/src/api/database/seeds/initial.js +++ b/src/api/database/seeds/initial.js @@ -1,9 +1,22 @@ /* eslint-disable no-console */ const bcrypt = require('bcrypt'); const moment = require('moment'); +const Util = require('../../utils/Util'); exports.seed = async db => { const now = moment.utc().toDate(); + + // Save environment variables to the database + try { + const settings = await db.table('settings').first(); + if (!settings) { + await Util.writeConfigToDb(Util.getEnvironmentDefaults(), false); + } + } catch (error) { + console.error(error); + } + + // Create admin user if it doesnt exist const user = await db.table('users').where({ username: process.env.ADMIN_ACCOUNT }).first(); if (user) return; try { diff --git a/src/api/scripts/overwriteConfig.js b/src/api/scripts/overwriteConfig.js new file mode 100644 index 0000000..0355ea6 --- /dev/null +++ b/src/api/scripts/overwriteConfig.js @@ -0,0 +1,15 @@ +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(); 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/utils/Util.js b/src/api/utils/Util.js index b60fca3..e760679 100644 --- a/src/api/utils/Util.js +++ b/src/api/utils/Util.js @@ -40,6 +40,45 @@ class Util { this._config = null; } + static getEnvironmentDefaults() { + return { + routePrefix: process.env.ROUTE_PREFIX || '/api', + rateLimitWindow: process.env.RATE_LIMIT_WINDOW || 2, + rateLimitMax: process.env.RATE_LIMIT_MAX || 5, + secret: process.env.SECRET || randomstring.generate(64), + serviceName: process.env.SERVICE_NAME || 'change-me', + domain: process.env.DOMAIN || `http://localhost:${process.env.SERVER_PORT}`, + chunkSize: process.env.CHUNK_SIZE || 90, + maxSize: process.env.MAX_SIZE || 5000, + generateZips: process.env.GENERATE_ZIPS == undefined ? true : false, + generatedFilenameLength: process.env.GENERATED_FILENAME_LENGTH || 12, + generatedAlbumLength: process.env.GENERATED_ALBUM_LENGTH || 6, + maxLinksPerAlbum: process.env.MAX_LINKS_PER_ALBUM || 5, + uploadFolder: process.env.UPLOAD_FOLDER || 'uploads', + blockedExtensions: process.env.BLOCKED_EXTENSIONS || ['.jar', '.exe', '.msi', '.com', '.bat', '.cmd', '.scr', '.ps1', '.sh'], + publicMode: process.env.PUBLIC_MODE == undefined ? true : false, + userAccounts: process.env.USER_ACCOUNTS == undefined ? true : false, + adminAccount: process.env.ADMIN_ACCOUNT || 'admin', + adminPassword: process.env.ADMIN_PASSWORD || 'admin', + metaThemeColor: process.env.META_THEME_COLOR || '#20222b', + metaDescription: process.env.META_DESCRIPTION || 'Blazing fast file uploader and bunker written in node! 🚀', + metaKeywords: process.env.META_KEYWORDS || 'chibisafe,lolisafe,upload,uploader,file,vue,images,ssr,file uploader,free', + metaTwitterHandle: process.env.META_TWITTER_HANDLE || '@your-handle' + }; + } + + static async writeConfigToDb(config, overwrite = true) { + try { + if (overwrite) { + await db.table('settings').first().update(config); + } else { + await db.table('settings').insert(config); + } + } catch (error) { + console.error(error); + } + } + static uuid() { return uuidv4(); } -- cgit v1.2.3 From 5f5716963dfda3bcae5dab1398d78c933b762c95 Mon Sep 17 00:00:00 2001 From: Pitu Date: Mon, 7 Jun 2021 16:25:02 +0900 Subject: wip --- src/api/utils/Util.js | 1 + 1 file changed, 1 insertion(+) (limited to 'src/api') diff --git a/src/api/utils/Util.js b/src/api/utils/Util.js index e760679..878a542 100644 --- a/src/api/utils/Util.js +++ b/src/api/utils/Util.js @@ -68,6 +68,7 @@ class Util { } static async writeConfigToDb(config, overwrite = true) { + // TODO: Check that the config passes the joi schema validation try { if (overwrite) { await db.table('settings').first().update(config); -- cgit v1.2.3 From 9b28e56e09ef31052935c00c830ceafd481b94f3 Mon Sep 17 00:00:00 2001 From: Pitu Date: Tue, 8 Jun 2021 00:33:01 +0900 Subject: chore: get host from req instead of config --- src/api/routes/admin/fileGET.js | 2 +- src/api/routes/admin/userGET.js | 2 +- src/api/routes/albums/albumFullGET.js | 2 +- src/api/routes/albums/albumGET.js | 2 +- src/api/routes/albums/albumsGET.js | 2 +- src/api/routes/albums/link/linkPOST.js | 10 ---------- src/api/routes/files/fileGET.js | 2 +- src/api/routes/files/filesGET.js | 2 +- src/api/routes/search/searchGET.js | 2 +- src/api/routes/service/configGET.js | 1 - src/api/routes/uploads/uploadPOST.js | 4 ++-- src/api/utils/Util.js | 25 ++++++++++++++----------- 12 files changed, 24 insertions(+), 32 deletions(-) (limited to 'src/api') diff --git a/src/api/routes/admin/fileGET.js b/src/api/routes/admin/fileGET.js index 9605da4..72b96f1 100644 --- a/src/api/routes/admin/fileGET.js +++ b/src/api/routes/admin/fileGET.js @@ -15,7 +15,7 @@ class filesGET extends Route { .select('id', 'username', 'enabled', 'createdAt', 'editedAt', 'apiKeyEditedAt', 'isAdmin') .where({ id: file.userId }) .first(); - file = Util.constructFilePublicLink(file); + file = Util.constructFilePublicLink(req, file); // Additional relevant data const filesFromUser = await db.table('files').where({ userId: user.id }).select('id'); diff --git a/src/api/routes/admin/userGET.js b/src/api/routes/admin/userGET.js index 430dfd7..bf4f912 100644 --- a/src/api/routes/admin/userGET.js +++ b/src/api/routes/admin/userGET.js @@ -37,7 +37,7 @@ class usersGET extends Route { } for (let file of files) { - file = Util.constructFilePublicLink(file); + file = Util.constructFilePublicLink(req, file); } return res.json({ diff --git a/src/api/routes/albums/albumFullGET.js b/src/api/routes/albums/albumFullGET.js index d25fe15..32c7326 100644 --- a/src/api/routes/albums/albumFullGET.js +++ b/src/api/routes/albums/albumFullGET.js @@ -43,7 +43,7 @@ class albumGET extends Route { // eslint-disable-next-line no-restricted-syntax for (let file of files) { - file = Util.constructFilePublicLink(file); + file = Util.constructFilePublicLink(req, file); } return res.json({ diff --git a/src/api/routes/albums/albumGET.js b/src/api/routes/albums/albumGET.js index 4ac7089..e121a31 100644 --- a/src/api/routes/albums/albumGET.js +++ b/src/api/routes/albums/albumGET.js @@ -44,7 +44,7 @@ class albumGET extends Route { } for (let file of files) { - file = Util.constructFilePublicLink(file); + file = Util.constructFilePublicLink(req, file); } // Add 1 more view to the link diff --git a/src/api/routes/albums/albumsGET.js b/src/api/routes/albums/albumsGET.js index 3c18d8f..98cc82e 100644 --- a/src/api/routes/albums/albumsGET.js +++ b/src/api/routes/albums/albumsGET.js @@ -37,7 +37,7 @@ class albumsGET extends Route { // Fetch thumbnails and stuff for (let file of files) { - file = Util.constructFilePublicLink(file); + file = Util.constructFilePublicLink(req, file); } album.fileCount = fileCount[0].count; diff --git a/src/api/routes/albums/link/linkPOST.js b/src/api/routes/albums/link/linkPOST.js index 42eac58..7bc8051 100644 --- a/src/api/routes/albums/link/linkPOST.js +++ b/src/api/routes/albums/link/linkPOST.js @@ -20,16 +20,6 @@ class linkPOST extends Route { .first(); if (!exists) return res.status(400).json({ message: 'Album doesn\t exist' }); - /* - Count the amount of links created for that album already and error out if max was reached - */ - const count = await db - .table('links') - .where('albumId', albumId) - .count({ count: 'id' }) - .first(); - if (count >= parseInt(process.env.MAX_LINKS_PER_ALBUM, 10)) return res.status(400).json({ message: 'Maximum links per album reached' }); - let { identifier } = req.body; if (identifier) { if (!user.isAdmin) return res.status(401).json({ message: 'Only administrators can create custom links' }); diff --git a/src/api/routes/files/fileGET.js b/src/api/routes/files/fileGET.js index 9ec6f22..2e6f0b8 100644 --- a/src/api/routes/files/fileGET.js +++ b/src/api/routes/files/fileGET.js @@ -16,7 +16,7 @@ class fileGET extends Route { let file = await db.table('files').where({ id, userId: user.id }).first(); if (!file) return res.status(400).json({ message: 'The file doesn\'t exist or doesn\'t belong to the user' }); - file = Util.constructFilePublicLink(file); + file = Util.constructFilePublicLink(req, file); /* Fetch the albums diff --git a/src/api/routes/files/filesGET.js b/src/api/routes/files/filesGET.js index 9e90633..20ccbc5 100644 --- a/src/api/routes/files/filesGET.js +++ b/src/api/routes/files/filesGET.js @@ -30,7 +30,7 @@ class filesGET extends Route { // For each file, create the public link to be able to display the file for (let file of files) { - file = Util.constructFilePublicLink(file); + file = Util.constructFilePublicLink(req, file); } return res.json({ diff --git a/src/api/routes/search/searchGET.js b/src/api/routes/search/searchGET.js index 187fcab..3cfcfef 100644 --- a/src/api/routes/search/searchGET.js +++ b/src/api/routes/search/searchGET.js @@ -53,7 +53,7 @@ class configGET extends Route { // For each file, create the public link to be able to display the file for (let file of files) { - file = Util.constructFilePublicLink(file); + file = Util.constructFilePublicLink(req, file); } return res.json({ diff --git a/src/api/routes/service/configGET.js b/src/api/routes/service/configGET.js index bc91a7e..291f0a4 100644 --- a/src/api/routes/service/configGET.js +++ b/src/api/routes/service/configGET.js @@ -11,7 +11,6 @@ class configGET extends Route { config: { serviceName: process.env.SERVICE_NAME, uploadFolder: process.env.UPLOAD_FOLDER, - linksPerAlbum: parseInt(process.env.MAX_LINKS_PER_ALBUM, 10), maxUploadSize: parseInt(process.env.MAX_SIZE, 10), filenameLength: parseInt(process.env.GENERATED_FILENAME_LENGTH, 10), albumLinkLength: parseInt(process.env.GENERATED_ALBUM_LENGTH, 10), diff --git a/src/api/routes/uploads/uploadPOST.js b/src/api/routes/uploads/uploadPOST.js index a0dba27..7386490 100644 --- a/src/api/routes/uploads/uploadPOST.js +++ b/src/api/routes/uploads/uploadPOST.js @@ -282,8 +282,8 @@ class uploadPOST extends Route { if (albumId) await Util.saveFileToAlbum(db, albumId, result.id); - result.file = Util.constructFilePublicLink(result.file); - result.deleteUrl = `${process.env.DOMAIN}/api/file/${result.id[0]}`; + result.file = Util.constructFilePublicLink(req, result.file); + result.deleteUrl = `${Util.getHost(req)}/api/file/${result.id[0]}`; return res.status(201).send({ message: 'Sucessfully uploaded the file.', diff --git a/src/api/utils/Util.js b/src/api/utils/Util.js index 878a542..727851e 100644 --- a/src/api/utils/Util.js +++ b/src/api/utils/Util.js @@ -47,13 +47,11 @@ class Util { rateLimitMax: process.env.RATE_LIMIT_MAX || 5, secret: process.env.SECRET || randomstring.generate(64), serviceName: process.env.SERVICE_NAME || 'change-me', - domain: process.env.DOMAIN || `http://localhost:${process.env.SERVER_PORT}`, chunkSize: process.env.CHUNK_SIZE || 90, maxSize: process.env.MAX_SIZE || 5000, generateZips: process.env.GENERATE_ZIPS == undefined ? true : false, generatedFilenameLength: process.env.GENERATED_FILENAME_LENGTH || 12, generatedAlbumLength: process.env.GENERATED_ALBUM_LENGTH || 6, - maxLinksPerAlbum: process.env.MAX_LINKS_PER_ALBUM || 5, uploadFolder: process.env.UPLOAD_FOLDER || 'uploads', blockedExtensions: process.env.BLOCKED_EXTENSIONS || ['.jar', '.exe', '.msi', '.com', '.bat', '.cmd', '.scr', '.ps1', '.sh'], publicMode: process.env.PUBLIC_MODE == undefined ? true : false, @@ -92,17 +90,18 @@ class Util { return fileTypeMimeObj ? fileTypeMimeObj.mime : undefined; } - static constructFilePublicLink(file) { + static constructFilePublicLink(req, file) { /* TODO: This wont work without a reverse proxy serving both the site and the API under the same domain. Pls fix. */ - file.url = `${process.env.DOMAIN}/${file.name}`; + const host = this.getHost(req); + file.url = `${host}/${file.name}`; const { thumb, preview } = ThumbUtil.getFileThumbnail(file.name) || {}; if (thumb) { - file.thumb = `${process.env.DOMAIN}/thumbs/${thumb}`; - file.thumbSquare = `${process.env.DOMAIN}/thumbs/square/${thumb}`; - file.preview = preview && `${process.env.DOMAIN}/thumbs/preview/${preview}`; + file.thumb = `${host}/thumbs/${thumb}`; + file.thumbSquare = `${host}/thumbs/square/${thumb}`; + file.preview = preview && `${host}/thumbs/preview/${preview}`; } return file; } @@ -265,8 +264,8 @@ class Util { static generateThumbnails = ThumbUtil.generateThumbnails; - static async fileExists(res, exists, filename) { - exists = Util.constructFilePublicLink(exists); + static async fileExists(req, res, exists, filename) { + exists = Util.constructFilePublicLink(req, exists); res.json({ message: 'Successfully uploaded the file.', name: exists.name, @@ -274,7 +273,7 @@ class Util { size: exists.size, url: exists.url, thumb: exists.thumb, - deleteUrl: `${process.env.DOMAIN}/api/file/${exists.id}`, + deleteUrl: `${this.getHost(req)}/api/file/${exists.id}`, repeated: true }); @@ -298,7 +297,7 @@ class Util { .first(); if (dbFile) { - await this.fileExists(res, dbFile, file.data.filename); + await this.fileExists(req, res, dbFile, file.data.filename); return; } @@ -406,6 +405,10 @@ class Util { console.error(error); } } + + static getHost(req) { + return `${req.protocol}://${req.headers.host}`; + } } module.exports = Util; -- 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/routes/albums/albumZipGET.js | 8 +++--- src/api/routes/auth/loginPOST.js | 3 ++- src/api/routes/auth/registerPOST.js | 2 +- src/api/routes/service/configGET.js | 17 ++++++------ src/api/routes/uploads/uploadPOST.js | 8 +++--- src/api/structures/Route.js | 3 ++- src/api/structures/Server.js | 19 +++++++++----- src/api/utils/ThumbUtil.js | 11 ++++---- src/api/utils/Util.js | 51 +++++++++++++++--------------------- src/api/utils/generateThumbs.js | 2 +- 10 files changed, 62 insertions(+), 62 deletions(-) (limited to 'src/api') diff --git a/src/api/routes/albums/albumZipGET.js b/src/api/routes/albums/albumZipGET.js index 22b0b6f..8def099 100644 --- a/src/api/routes/albums/albumZipGET.js +++ b/src/api/routes/albums/albumZipGET.js @@ -38,13 +38,13 @@ class albumGET extends Route { If the date when the album was zipped is greater than the album's last edit, we just send the zip to the user */ if (album.zippedAt > album.editedAt) { - const filePath = path.join(__dirname, '../../../../', process.env.UPLOAD_FOLDER, 'zips', `${album.userId}-${album.id}.zip`); + const filePath = path.join(__dirname, '../../../../uploads', 'zips', `${album.userId}-${album.id}.zip`); const exists = await jetpack.existsAsync(filePath); /* Make sure the file exists just in case, and if not, continue to it's generation. */ if (exists) { - const fileName = `${process.env.SERVICE_NAME}-${identifier}.zip`; + const fileName = `${Util.config.serviceName}-${identifier}.zip`; return res.download(filePath, fileName); } } @@ -77,8 +77,8 @@ class albumGET extends Route { .update('zippedAt', db.fn.now()) .wasMutated(); - const filePath = path.join(__dirname, '../../../../', process.env.UPLOAD_FOLDER, 'zips', `${album.userId}-${album.id}.zip`); - const fileName = `${process.env.SERVICE_NAME}-${identifier}.zip`; + const filePath = path.join(__dirname, '../../../../uploads', 'zips', `${album.userId}-${album.id}.zip`); + const fileName = `${Util.config.serviceName}-${identifier}.zip`; return res.download(filePath, fileName); } catch (error) { log.error(error); diff --git a/src/api/routes/auth/loginPOST.js b/src/api/routes/auth/loginPOST.js index 373252b..cc72145 100644 --- a/src/api/routes/auth/loginPOST.js +++ b/src/api/routes/auth/loginPOST.js @@ -2,6 +2,7 @@ const bcrypt = require('bcrypt'); const moment = require('moment'); const JWT = require('jsonwebtoken'); const Route = require('../../structures/Route'); +const Util = require('../../utils/Util'); class loginPOST extends Route { constructor() { @@ -37,7 +38,7 @@ class loginPOST extends Route { iss: 'chibisafe', sub: user.id, iat: moment.utc().valueOf() - }, process.env.SECRET, { expiresIn: '30d' }); + }, Util.config.secret, { expiresIn: '30d' }); return res.json({ message: 'Successfully logged in.', diff --git a/src/api/routes/auth/registerPOST.js b/src/api/routes/auth/registerPOST.js index 7b9eb3c..e740c83 100644 --- a/src/api/routes/auth/registerPOST.js +++ b/src/api/routes/auth/registerPOST.js @@ -12,7 +12,7 @@ class registerPOST extends Route { async run(req, res, db) { // Only allow admins to create new accounts if the sign up is deactivated const user = await Util.isAuthorized(req); - if ((!user || !user.isAdmin) && process.env.USER_ACCOUNTS === 'false') return res.status(401).json({ message: 'Creation of new accounts is currently disabled' }); + if ((!user || !user.isAdmin) && !Util.config.userAccounts) return res.status(401).json({ message: 'Creation of new accounts is currently disabled' }); if (!req.body) return res.status(400).json({ message: 'No body provided' }); const { username, password } = req.body; diff --git a/src/api/routes/service/configGET.js b/src/api/routes/service/configGET.js index 291f0a4..b0a9c16 100644 --- a/src/api/routes/service/configGET.js +++ b/src/api/routes/service/configGET.js @@ -1,4 +1,5 @@ const Route = require('../../structures/Route'); +const Util = require('../../utils/Util'); class configGET extends Route { constructor() { @@ -9,15 +10,13 @@ class configGET extends Route { return res.json({ message: 'Successfully retrieved config', config: { - serviceName: process.env.SERVICE_NAME, - uploadFolder: process.env.UPLOAD_FOLDER, - maxUploadSize: parseInt(process.env.MAX_SIZE, 10), - filenameLength: parseInt(process.env.GENERATED_FILENAME_LENGTH, 10), - albumLinkLength: parseInt(process.env.GENERATED_ALBUM_LENGTH, 10), - generateThumbnails: process.env.GENERATE_THUMBNAILS === 'true', - generateZips: process.env.GENERATE_ZIPS === 'true', - publicMode: process.env.PUBLIC_MODE === 'true', - enableAccounts: process.env.USER_ACCOUNTS === 'true' + serviceName: Util.config.serviceName, + maxUploadSize: Util.config.maxSize, + filenameLength: Util.config.generatedFilenameLength, + albumLinkLength: Util.config.generatedAlbumLength, + generateZips: Util.config.generateZips, + publicMode: Util.config.publicMode, + enableAccounts: Util.config.userAccounts } }); } diff --git a/src/api/routes/uploads/uploadPOST.js b/src/api/routes/uploads/uploadPOST.js index 7386490..4e96c80 100644 --- a/src/api/routes/uploads/uploadPOST.js +++ b/src/api/routes/uploads/uploadPOST.js @@ -8,8 +8,8 @@ const multerStorage = require('../../utils/multerStorage'); const chunksData = {}; const chunkedUploadsTimeout = 1800000; -const chunksDir = path.join(__dirname, '../../../../', process.env.UPLOAD_FOLDER, 'chunks'); -const uploadDir = path.join(__dirname, '../../../../', process.env.UPLOAD_FOLDER); +const chunksDir = path.join(__dirname, '../../../../uploads/chunks'); +const uploadDir = path.join(__dirname, '../../../../uploads'); const cleanUpChunks = async (uuid, onTimeout) => { @@ -72,7 +72,7 @@ const initChunks = async uuid => { const executeMulter = multer({ // Guide: https://github.com/expressjs/multer#limits limits: { - fileSize: parseInt(process.env.MAX_SIZE, 10) * (1000 * 1000), + fileSize: Util.config.maxSize * (1000 * 1000), // Maximum number of non-file fields. // Dropzone.js will add 6 extra fields for chunked uploads. // We don't use them for anything else. @@ -257,7 +257,7 @@ class uploadPOST extends Route { async run(req, res, db) { const user = await Util.isAuthorized(req); - if (!user && process.env.PUBLIC_MODE === 'false') return res.status(401).json({ message: 'Not authorized to use this resource' }); + if (!user && !Util.config.publicMode) return res.status(401).json({ message: 'Not authorized to use this resource' }); const { finishedchunks } = req.headers; const albumId = req.headers.albumid ? req.headers.albumid === 'null' ? null : req.headers.albumid : null; if (albumId && !user) return res.status(401).json({ message: 'Only registered users can upload files to an album' }); 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(); + diff --git a/src/api/utils/ThumbUtil.js b/src/api/utils/ThumbUtil.js index d08ecab..fb6e47f 100644 --- a/src/api/utils/ThumbUtil.js +++ b/src/api/utils/ThumbUtil.js @@ -10,11 +10,12 @@ class ThumbUtil { static imageExtensions = ['.jpg', '.jpeg', '.gif', '.png', '.webp']; static videoExtensions = ['.webm', '.mp4', '.wmv', '.avi', '.mov']; - static thumbPath = path.join(__dirname, '../../../', process.env.UPLOAD_FOLDER, 'thumbs'); - static squareThumbPath = path.join(__dirname, '../../../', process.env.UPLOAD_FOLDER, 'thumbs', 'square'); - static videoPreviewPath = path.join(__dirname, '../../../', process.env.UPLOAD_FOLDER, 'thumbs', 'preview'); + static thumbPath = path.join(__dirname, '../../../', 'uploads', 'thumbs'); + static squareThumbPath = path.join(__dirname, '../../../', 'uploads', 'thumbs', 'square'); + static videoPreviewPath = path.join(__dirname, '../../../', 'uploads', 'thumbs', 'preview'); static generateThumbnails(filename) { + if (!filename) return; const ext = path.extname(filename).toLowerCase(); const output = `${filename.slice(0, -ext.length)}.webp`; const previewOutput = `${filename.slice(0, -ext.length)}.webm`; @@ -27,7 +28,7 @@ class ThumbUtil { } static async generateThumbnailForImage(filename, output) { - const filePath = path.join(__dirname, '../../../', process.env.UPLOAD_FOLDER, filename); + const filePath = path.join(__dirname, '../../../', 'uploads', filename); const file = await jetpack.readAsync(filePath, 'buffer'); await sharp(file) @@ -41,7 +42,7 @@ class ThumbUtil { } static async generateThumbnailForVideo(filename, output) { - const filePath = path.join(__dirname, '../../../', process.env.UPLOAD_FOLDER, filename); + const filePath = path.join(__dirname, '../../../', 'uploads', filename); ffmpeg(filePath) .thumbnail({ diff --git a/src/api/utils/Util.js b/src/api/utils/Util.js index 727851e..3780460 100644 --- a/src/api/utils/Util.js +++ b/src/api/utils/Util.js @@ -12,25 +12,22 @@ const log = require('./Log'); const ThumbUtil = require('./ThumbUtil'); const StatsGenerator = require('./StatsGenerator'); -const blockedExtensions = process.env.BLOCKED_EXTENSIONS.split(','); const preserveExtensions = ['.tar.gz', '.tar.z', '.tar.bz2', '.tar.lzma', '.tar.lzo', '.tar.xz']; class Util { - static uploadPath = path.join(__dirname, '../../../', process.env.UPLOAD_FOLDER); + static uploadPath = path.join(__dirname, '../../../', 'uploads'); static statsLastSavedTime = null; static _config = null; static get config() { + if (this._config) return this._config; return (async () => { if (this._config === null) { - const conf = await db('config').select('key', 'value'); - this._config = conf.reduce((acc, { key, value }) => { - if (typeof value === 'string' || value instanceof String) { - acc[key] = JSON.parse(value); - } else { - acc[key] = value; - } - }, {}); + const conf = await db('settings').select('key', 'value'); + this._config = conf.reduce((obj, item) => ( + // eslint-disable-next-line no-sequences + obj[item.key] = typeof item.value === 'string' || item.value instanceof String ? JSON.parse(item.value) : item.value, obj + ), {}); } return this._config; })(); @@ -49,15 +46,15 @@ class Util { serviceName: process.env.SERVICE_NAME || 'change-me', chunkSize: process.env.CHUNK_SIZE || 90, maxSize: process.env.MAX_SIZE || 5000, + // eslint-disable-next-line eqeqeq generateZips: process.env.GENERATE_ZIPS == undefined ? true : false, generatedFilenameLength: process.env.GENERATED_FILENAME_LENGTH || 12, generatedAlbumLength: process.env.GENERATED_ALBUM_LENGTH || 6, - uploadFolder: process.env.UPLOAD_FOLDER || 'uploads', blockedExtensions: process.env.BLOCKED_EXTENSIONS || ['.jar', '.exe', '.msi', '.com', '.bat', '.cmd', '.scr', '.ps1', '.sh'], + // eslint-disable-next-line eqeqeq publicMode: process.env.PUBLIC_MODE == undefined ? true : false, + // eslint-disable-next-line eqeqeq userAccounts: process.env.USER_ACCOUNTS == undefined ? true : false, - adminAccount: process.env.ADMIN_ACCOUNT || 'admin', - adminPassword: process.env.ADMIN_PASSWORD || 'admin', metaThemeColor: process.env.META_THEME_COLOR || '#20222b', metaDescription: process.env.META_DESCRIPTION || 'Blazing fast file uploader and bunker written in node! 🚀', metaKeywords: process.env.META_KEYWORDS || 'chibisafe,lolisafe,upload,uploader,file,vue,images,ssr,file uploader,free', @@ -65,16 +62,16 @@ class Util { }; } - static async writeConfigToDb(config, overwrite = true) { + static async writeConfigToDb(config) { // TODO: Check that the config passes the joi schema validation + if (!config || !config.key || !config.key) return; try { - if (overwrite) { - await db.table('settings').first().update(config); - } else { - await db.table('settings').insert(config); - } + config.value = JSON.stringify(config.value); + await db.table('settings').insert(config); } catch (error) { console.error(error); + } finally { + this.invalidateConfigCache(); } } @@ -83,7 +80,7 @@ class Util { } static isExtensionBlocked(extension) { - return blockedExtensions.includes(extension); + return this.config.blockedExtensions.includes(extension); } static getMimeFromType(fileTypeMimeObj) { @@ -109,7 +106,7 @@ class Util { static getUniqueFilename(extension) { const retry = (i = 0) => { const filename = randomstring.generate({ - length: parseInt(process.env.GENERATED_FILENAME_LENGTH, 10), + length: this.config.generatedFilenameLength, capitalization: 'lowercase' }) + extension; @@ -126,7 +123,7 @@ class Util { static getUniqueAlbumIdentifier() { const retry = async (i = 0) => { const identifier = randomstring.generate({ - length: parseInt(process.env.GENERATED_ALBUM_LENGTH, 10), + length: this.config.generatedAlbumLength, capitalization: 'lowercase' }); const exists = await db @@ -223,7 +220,7 @@ class Util { const token = req.headers.authorization.split(' ')[1]; if (!token) return false; - return JWT.verify(token, process.env.SECRET, async (error, decoded) => { + return JWT.verify(token, this.config.secret, async (error, decoded) => { if (error) { log.error(error); return false; @@ -249,13 +246,7 @@ class Util { zip.addLocalFile(path.join(Util.uploadPath, file)); } zip.writeZip( - path.join( - __dirname, - '../../../', - process.env.UPLOAD_FOLDER, - 'zips', - `${album.userId}-${album.id}.zip` - ) + path.join(__dirname, '../../../', 'uploads', 'zips', `${album.userId}-${album.id}.zip`) ); } catch (error) { log.error(error); diff --git a/src/api/utils/generateThumbs.js b/src/api/utils/generateThumbs.js index d2cd91b..a22fcb6 100644 --- a/src/api/utils/generateThumbs.js +++ b/src/api/utils/generateThumbs.js @@ -6,7 +6,7 @@ const path = require('path'); const ThumbUtil = require('./ThumbUtil'); const start = async () => { - const files = fs.readdirSync(path.join(__dirname, '../../../', process.env.UPLOAD_FOLDER)); + const files = fs.readdirSync(path.join(__dirname, '../../../uploads')); for (const fileName of files) { console.log(`Generating thumb for '${fileName}`); // eslint-disable-next-line no-await-in-loop -- cgit v1.2.3 From b2253c7f607204daeb20a35cf57ff9605f5ead43 Mon Sep 17 00:00:00 2001 From: Pitu Date: Tue, 15 Jun 2021 00:13:37 +0900 Subject: chore: update db migration, seed and docker env --- .../migrations/20210112011802_addSettingsTable.js | 24 ++----------------- src/api/database/seeds/initial.js | 27 ++++++++++++++++------ 2 files changed, 22 insertions(+), 29 deletions(-) (limited to 'src/api') diff --git a/src/api/database/migrations/20210112011802_addSettingsTable.js b/src/api/database/migrations/20210112011802_addSettingsTable.js index 3acd845..cd4ac55 100644 --- a/src/api/database/migrations/20210112011802_addSettingsTable.js +++ b/src/api/database/migrations/20210112011802_addSettingsTable.js @@ -1,27 +1,7 @@ exports.up = async knex => { await knex.schema.createTable('settings', table => { - table.string('routePrefix'); - table.integer('rateLimitWindow'); - table.integer('rateLimitMax'); - table.string('secret'); - table.string('serviceName'); - table.string('domain'); - table.integer('chunkSize'); - table.integer('maxSize'); - table.boolean('generateZips'); - table.integer('generatedFilenameLength'); - table.integer('generatedAlbumLength'); - table.integer('maxLinksPerAlbum'); - table.string('uploadFolder'); - table.json('blockedExtensions'); - table.boolean('publicMode'); - table.boolean('userAccounts'); - table.string('adminAccount'); - table.string('adminPassword'); - table.string('metaThemeColor'); - table.string('metaDescription'); - table.string('metaKeywords'); - table.string('metaTwitterHandle'); + table.string('key'); + table.string('value'); }); }; diff --git a/src/api/database/seeds/initial.js b/src/api/database/seeds/initial.js index bb60b2c..30d84c8 100644 --- a/src/api/database/seeds/initial.js +++ b/src/api/database/seeds/initial.js @@ -8,21 +8,34 @@ exports.seed = async db => { // Save environment variables to the database try { - const settings = await db.table('settings').first(); - if (!settings) { - await Util.writeConfigToDb(Util.getEnvironmentDefaults(), false); + const defaults = Util.getEnvironmentDefaults(); + const keys = Object.keys(defaults); + for await (const item of keys) { + Util.writeConfigToDb({ + key: item, + value: defaults[item] + }); } } catch (error) { console.error(error); } // Create admin user if it doesnt exist - const user = await db.table('users').where({ username: process.env.ADMIN_ACCOUNT }).first(); - if (user) return; + const user = await db.table('users').where({ username: 'admin' }).first(); + if (user) { + console.log(); + console.log('========================================================='); + console.log('== admin account already exists, skipping. =='); + console.log('========================================================='); + console.log('== Run `pm2 start pm2.json` to start the service =='); + console.log('========================================================='); + console.log(); + return; + } try { - const hash = await bcrypt.hash(process.env.ADMIN_PASSWORD, 10); + const hash = await bcrypt.hash('admin', 10); await db.table('users').insert({ - username: process.env.ADMIN_ACCOUNT, + username: 'admin', password: hash, passwordEditedAt: now, createdAt: now, -- cgit v1.2.3 From 50d13e2ae7bf03420cd3380f2417daf04fe6d725 Mon Sep 17 00:00:00 2001 From: Pitu Date: Thu, 17 Jun 2021 01:10:24 +0900 Subject: feat: fetch settings from api --- src/api/routes/service/configGET.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src/api') diff --git a/src/api/routes/service/configGET.js b/src/api/routes/service/configGET.js index b0a9c16..c8d88d3 100644 --- a/src/api/routes/service/configGET.js +++ b/src/api/routes/service/configGET.js @@ -3,20 +3,21 @@ const Util = require('../../utils/Util'); class configGET extends Route { constructor() { - super('/service/config', 'get', { adminOnly: true }); + super('/service/config', 'get', { bypassAuth: true }); } run(req, res) { return res.json({ message: 'Successfully retrieved config', config: { + version: process.env.npm_package_version, serviceName: Util.config.serviceName, maxUploadSize: Util.config.maxSize, filenameLength: Util.config.generatedFilenameLength, albumLinkLength: Util.config.generatedAlbumLength, - generateZips: Util.config.generateZips, + chunkSize: Util.config.chunkSize, publicMode: Util.config.publicMode, - enableAccounts: Util.config.userAccounts + userAccounts: Util.config.userAccounts } }); } -- cgit v1.2.3 From 30808a3574bec0c3c9c240833d2fa78715862422 Mon Sep 17 00:00:00 2001 From: Pitu Date: Thu, 17 Jun 2021 03:39:58 +0900 Subject: feat: fetch all settings if admin --- src/api/routes/service/configAllGET.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/api/routes/service/configAllGET.js (limited to 'src/api') diff --git a/src/api/routes/service/configAllGET.js b/src/api/routes/service/configAllGET.js new file mode 100644 index 0000000..fe9dae6 --- /dev/null +++ b/src/api/routes/service/configAllGET.js @@ -0,0 +1,17 @@ +const Route = require('../../structures/Route'); +const Util = require('../../utils/Util'); + +class configGET extends Route { + constructor() { + super('/service/config/all', 'get', { adminOnly: true }); + } + + run(req, res) { + return res.json({ + message: 'Successfully retrieved config', + config: Util.config + }); + } +} + +module.exports = configGET; -- cgit v1.2.3 From 6fe5055e9d62a6ae06128bf61a55960537e02091 Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Thu, 17 Jun 2021 01:13:15 +0300 Subject: feat: show setting values on the settings page and implement sending to backend (no saving yet) --- src/api/routes/service/configPOST.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/api/routes/service/configPOST.js (limited to 'src/api') diff --git a/src/api/routes/service/configPOST.js b/src/api/routes/service/configPOST.js new file mode 100644 index 0000000..28d034d --- /dev/null +++ b/src/api/routes/service/configPOST.js @@ -0,0 +1,20 @@ +const Joi = require('joi'); + +const Route = require('../../structures/Route'); +const Util = require('../../utils/Util'); + +const { schema } = require('../../structures/Setting'); + +class configGET extends Route { + constructor() { + super('/service/config', 'post', { adminOnly: true }); + } + + run(req, res) { + const { settings } = req.body; + const validationRes = schema.validate(settings, { abortEarly: false }); + console.log(JSON.stringify(validationRes)); + } +} + +module.exports = configGET; -- 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/routes/service/configPOST.js | 27 ++++++++++++++++++++++----- src/api/structures/Setting.js | 6 +++--- src/api/utils/Util.js | 2 +- 3 files changed, 26 insertions(+), 9 deletions(-) (limited to 'src/api') diff --git a/src/api/routes/service/configPOST.js b/src/api/routes/service/configPOST.js index 28d034d..9129950 100644 --- a/src/api/routes/service/configPOST.js +++ b/src/api/routes/service/configPOST.js @@ -1,19 +1,36 @@ -const Joi = require('joi'); - const Route = require('../../structures/Route'); const Util = require('../../utils/Util'); const { schema } = require('../../structures/Setting'); +const joiOptions = { + abortEarly: false, // include all errors + allowUnknown: true, // ignore unknown props + stripUnknown: true // remove unknown props +}; + class configGET extends Route { constructor() { super('/service/config', 'post', { adminOnly: true }); } - run(req, res) { + async run(req, res) { const { settings } = req.body; - const validationRes = schema.validate(settings, { abortEarly: false }); - console.log(JSON.stringify(validationRes)); + const { error, value } = schema.validate(settings, joiOptions); + if (error) { + return res.status(400).json({ + errors: error.details.reduce((acc, v) => { + for (const p of v.path) { + acc[p] = (acc[p] || []).concat(v.message); + } + return acc; + }, {}) + }); + } + + await Util.writeConfigToDb(value); + + return res.status(200).json({ value }); } } 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 }) diff --git a/src/api/utils/Util.js b/src/api/utils/Util.js index 3780460..628be82 100644 --- a/src/api/utils/Util.js +++ b/src/api/utils/Util.js @@ -64,7 +64,7 @@ class Util { static async writeConfigToDb(config) { // TODO: Check that the config passes the joi schema validation - if (!config || !config.key || !config.key) return; + if (!config || !config.key) return; try { config.value = JSON.stringify(config.value); await db.table('settings').insert(config); -- 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') 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 c131c3a1fc3859908d088dcb04d999d7a3368925 Mon Sep 17 00:00:00 2001 From: Pitu Date: Thu, 17 Jun 2021 23:31:48 +0900 Subject: feat: save correct db info --- src/api/routes/service/configPOST.js | 10 +++++++++- src/api/utils/Util.js | 10 +++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) (limited to 'src/api') diff --git a/src/api/routes/service/configPOST.js b/src/api/routes/service/configPOST.js index 9129950..68af467 100644 --- a/src/api/routes/service/configPOST.js +++ b/src/api/routes/service/configPOST.js @@ -28,7 +28,15 @@ class configGET extends Route { }); } - await Util.writeConfigToDb(value); + await Util.wipeConfigDb(); + + const keys = Object.keys(value); + for await (const item of keys) { + Util.writeConfigToDb({ + key: item, + value: value[item] + }); + } return res.status(200).json({ value }); } diff --git a/src/api/utils/Util.js b/src/api/utils/Util.js index 628be82..bd23fd2 100644 --- a/src/api/utils/Util.js +++ b/src/api/utils/Util.js @@ -62,7 +62,15 @@ class Util { }; } - static async writeConfigToDb(config) { + static async wipeConfigDb() { + try { + await db.table('settings').del(); + } catch (error) { + console.error(error); + } + } + + static async writeConfigToDb(config, wipe = false) { // TODO: Check that the config passes the joi schema validation if (!config || !config.key) return; try { -- cgit v1.2.3 From f42c75c7f4b43161386d40b070e5c7b9b9073213 Mon Sep 17 00:00:00 2001 From: Pitu Date: Fri, 18 Jun 2021 01:45:10 +0900 Subject: feat: add domain to setup process --- src/api/utils/Util.js | 1 + 1 file changed, 1 insertion(+) (limited to 'src/api') diff --git a/src/api/utils/Util.js b/src/api/utils/Util.js index bd23fd2..bea960a 100644 --- a/src/api/utils/Util.js +++ b/src/api/utils/Util.js @@ -39,6 +39,7 @@ class Util { static getEnvironmentDefaults() { return { + domain: process.env.DOMAIN, routePrefix: process.env.ROUTE_PREFIX || '/api', rateLimitWindow: process.env.RATE_LIMIT_WINDOW || 2, rateLimitMax: process.env.RATE_LIMIT_MAX || 5, -- 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') 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 From 9a0e6f96408789d1fa33f5677fd550aadbae0234 Mon Sep 17 00:00:00 2001 From: Pitu Date: Fri, 18 Jun 2021 02:53:45 +0900 Subject: fix: seeding --- src/api/database/seeds/initial.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/api') diff --git a/src/api/database/seeds/initial.js b/src/api/database/seeds/initial.js index 30d84c8..7c04dad 100644 --- a/src/api/database/seeds/initial.js +++ b/src/api/database/seeds/initial.js @@ -10,8 +10,8 @@ exports.seed = async db => { try { const defaults = Util.getEnvironmentDefaults(); const keys = Object.keys(defaults); - for await (const item of keys) { - Util.writeConfigToDb({ + for (const item of keys) { + await Util.writeConfigToDb({ key: item, value: defaults[item] }); -- cgit v1.2.3 From acc7da5310569846d2d2514dc065cc903499b9b3 Mon Sep 17 00:00:00 2001 From: Pitu Date: Fri, 18 Jun 2021 02:53:52 +0900 Subject: test: seeding during migration --- .../migrations/20210112011802_addSettingsTable.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'src/api') diff --git a/src/api/database/migrations/20210112011802_addSettingsTable.js b/src/api/database/migrations/20210112011802_addSettingsTable.js index cd4ac55..8ad2721 100644 --- a/src/api/database/migrations/20210112011802_addSettingsTable.js +++ b/src/api/database/migrations/20210112011802_addSettingsTable.js @@ -1,8 +1,23 @@ +const Util = require('../../utils/Util'); + exports.up = async knex => { await knex.schema.createTable('settings', table => { table.string('key'); table.string('value'); }); + + try { + const defaults = Util.getEnvironmentDefaults(); + const keys = Object.keys(defaults); + for (const item of keys) { + await Util.writeConfigToDb({ + key: item, + value: defaults[item] + }); + } + } catch (error) { + console.error(error); + } }; exports.down = async knex => { -- cgit v1.2.3 From 2b5857182fd161f99f55015346211fec97a0fc32 Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Fri, 18 Jun 2021 15:11:47 +0300 Subject: fix: migrations fix: order of parameters in the startup script --- src/api/database/migrations/20210112011802_addSettingsTable.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/api') diff --git a/src/api/database/migrations/20210112011802_addSettingsTable.js b/src/api/database/migrations/20210112011802_addSettingsTable.js index 8ad2721..54e1cde 100644 --- a/src/api/database/migrations/20210112011802_addSettingsTable.js +++ b/src/api/database/migrations/20210112011802_addSettingsTable.js @@ -10,9 +10,9 @@ exports.up = async knex => { const defaults = Util.getEnvironmentDefaults(); const keys = Object.keys(defaults); for (const item of keys) { - await Util.writeConfigToDb({ + await knex('settings').insert({ key: item, - value: defaults[item] + value: JSON.stringify(defaults[item]) }); } } catch (error) { -- cgit v1.2.3 From 0ec31e23712d5f7672dfafc98af9afcc13703e46 Mon Sep 17 00:00:00 2001 From: Pitu Date: Sat, 19 Jun 2021 02:03:24 +0900 Subject: fix: potentially fix the blocked extensions array splitting --- src/api/utils/Util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/api') diff --git a/src/api/utils/Util.js b/src/api/utils/Util.js index bea960a..73b2b98 100644 --- a/src/api/utils/Util.js +++ b/src/api/utils/Util.js @@ -51,7 +51,7 @@ class Util { generateZips: process.env.GENERATE_ZIPS == undefined ? true : false, generatedFilenameLength: process.env.GENERATED_FILENAME_LENGTH || 12, generatedAlbumLength: process.env.GENERATED_ALBUM_LENGTH || 6, - blockedExtensions: process.env.BLOCKED_EXTENSIONS || ['.jar', '.exe', '.msi', '.com', '.bat', '.cmd', '.scr', '.ps1', '.sh'], + blockedExtensions: process.env.BLOCKED_EXTENSIONS ? process.env.BLOCKED_EXTENSIONS.split(',') : ['.jar', '.exe', '.msi', '.com', '.bat', '.cmd', '.scr', '.ps1', '.sh'], // eslint-disable-next-line eqeqeq publicMode: process.env.PUBLIC_MODE == undefined ? true : false, // eslint-disable-next-line eqeqeq -- cgit v1.2.3