Database migration and seeding

This commit is contained in:
Pitu 2019-02-21 23:49:29 +09:00
parent 25c5a06ec3
commit 44e6fd31d2
7 changed files with 166 additions and 125 deletions

21
knexfile.js Normal file
View File

@ -0,0 +1,21 @@
require('dotenv').config();
module.exports = {
client: process.env.DB_CLIENT,
connection: {
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE
},
pool: {
min: process.env.DATABASE_POOL_MIN || 2,
max: process.env.DATABASE_POOL_MAX || 10
},
migrations: {
directory: 'src/api/database/migrations'
},
seeds: {
directory: 'src/api/database/seeds'
}
};

View File

@ -10,11 +10,14 @@
}, },
"main": "src/_scripts/start.js", "main": "src/_scripts/start.js",
"scripts": { "scripts": {
"setup": "node src/wizard.js", "setup": "yarn build && node src/wizard.js",
"api": "node src/api/structures/Server", "dev": "nuxt",
"site": "nuxt",
"build": "nuxt build", "build": "nuxt build",
"start": "cross-env NODE_ENV=production node src/start && nuxt start" "migrate": "yarn knex migrate:latest",
"seed": "yarn knex seed:run",
"api": "node src/api/structures/Server",
"site": "cross-env NODE_ENV=production nuxt start",
"update": "git pull && yarn migrate && yarn build"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -0,0 +1,69 @@
exports.up = async knex => {
await knex.schema.createTable('users', table => {
table.increments();
table.string('username');
table.string('password');
table.boolean('enabled').defaultTo(true);
table.boolean('isAdmin').defaultTo(false);
table.string('apiKey');
table.timestamp('passwordEditedAt');
table.timestamp('apiKeyEditedAt');
table.timestamp('createdAt');
table.timestamp('editedAt');
});
await knex.schema.createTable('albums', table => {
table.increments();
table.integer('userId');
table.string('name');
table.timestamp('zippedAt');
table.timestamp('createdAt');
table.timestamp('editedAt');
});
await knex.schema.createTable('files', table => {
table.increments();
table.integer('userId');
table.string('name');
table.string('original');
table.string('type');
table.integer('size');
table.string('hash');
table.string('ip');
table.timestamp('createdAt');
table.timestamp('editedAt');
});
await knex.schema.createTable('links', table => {
table.increments();
table.integer('userId');
table.integer('albumId');
table.string('identifier');
table.integer('views').defaultTo(0);
table.boolean('enabled').defaultTo(true);
table.boolean('enableDownload').defaultTo(true);
table.timestamp('expiresAt');
table.timestamp('createdAt');
table.timestamp('editedAt');
});
await knex.schema.createTable('albumsFiles', table => {
table.increments();
table.integer('albumId');
table.integer('fileId');
});
await knex.schema.createTable('albumsLinks', table => {
table.increments();
table.integer('albumId');
table.integer('linkId');
});
};
exports.down = async knex => {
await knex.schema.dropTableIfExists('users');
await knex.schema.dropTableIfExists('albums');
await knex.schema.dropTableIfExists('files');
await knex.schema.dropTableIfExists('links');
await knex.schema.dropTableIfExists('albumsFiles');
await knex.schema.dropTableIfExists('albumsLinks');
};

View File

@ -0,0 +1,32 @@
const bcrypt = require('bcrypt');
const moment = require('moment');
const randomstring = require('randomstring');
exports.seed = async db => {
const now = moment.utc().toDate();
const user = await db.table('users').where({ username: 'root' }).first();
if (user) return;
try {
const hash = await bcrypt.hash(process.env.ADMIN_PASSWORD, 10);
await db.table('users').insert({
username: process.env.ADMIN_ACCOUNT,
password: hash,
apiKey: randomstring.generate(64),
passwordEditedAt: now,
apiKeyEditedAt: now,
createdAt: now,
editedAt: now,
isAdmin: true
});
console.log();
console.log('====================================================');
console.log('== Successfully created the admin account. ==');
console.log('====================================================');
console.log('== Run `yarn api` and `yarn site` next ==');
console.log('== preferably with pm2 or tmux to keep them alive ==');
console.log('====================================================');
console.log();
} catch (error) {
console.error(error);
}
}

View File

@ -1,112 +0,0 @@
const log = require('../utils/Log');
const { server } = require('../../../config');
const db = require('knex')(server.database);
const bcrypt = require('bcrypt');
const moment = require('moment');
const randomstring = require('randomstring');
class Database {
constructor() {
this.createTables();
}
async createTables() {
if (!await db.schema.hasTable('users')) {
await db.schema.createTable('users', table => {
table.increments();
table.string('username');
table.string('password');
table.boolean('enabled').defaultTo(true);
table.boolean('isAdmin').defaultTo(false);
table.string('apiKey');
table.timestamp('passwordEditedAt');
table.timestamp('apiKeyEditedAt');
table.timestamp('createdAt');
table.timestamp('editedAt');
});
}
if (!await db.schema.hasTable('albums')) {
await db.schema.createTable('albums', table => {
table.increments();
table.integer('userId');
table.string('name');
// table.string('identifier');
// table.boolean('enabled');
// table.boolean('enableDownload').defaultTo(true);
table.timestamp('zippedAt');
table.timestamp('createdAt');
table.timestamp('editedAt');
});
}
if (!await db.schema.hasTable('files')) {
await db.schema.createTable('files', table => {
table.increments();
table.integer('userId');
table.string('name');
table.string('original');
table.string('type');
table.integer('size');
table.string('hash');
table.string('ip');
table.timestamp('createdAt');
table.timestamp('editedAt');
});
}
if (!await db.schema.hasTable('links')) {
await db.schema.createTable('links', table => {
table.increments();
table.integer('userId');
table.integer('albumId');
table.string('identifier');
table.integer('views').defaultTo(0);
table.boolean('enabled').defaultTo(true);
table.boolean('enableDownload').defaultTo(true);
table.timestamp('expiresAt');
table.timestamp('createdAt');
table.timestamp('editedAt');
});
}
if (!await db.schema.hasTable('albumsFiles')) {
await db.schema.createTable('albumsFiles', table => {
table.increments();
table.integer('albumId');
table.integer('fileId');
});
}
if (!await db.schema.hasTable('albumsLinks')) {
await db.schema.createTable('albumsLinks', table => {
table.increments();
table.integer('albumId');
table.integer('linkId');
});
}
const now = moment.utc().toDate();
const user = await db.table('users').where({ username: 'root' }).first();
if (user) return;
try {
const hash = await bcrypt.hash('root', 10);
await db.table('users').insert({
username: 'root',
password: hash,
apiKey: randomstring.generate(64),
passwordEditedAt: now,
apiKeyEditedAt: now,
createdAt: now,
editedAt: now,
isAdmin: true
});
log.success('Successfully created the root user with password "root". Make sure to log in and change it!');
} catch (error) {
log.error(error);
if (error) log.error('Error generating password hash for root');
}
}
}
module.exports = Database;

View File

@ -8,7 +8,6 @@ const RateLimit = require('express-rate-limit');
const bodyParser = require('body-parser'); const bodyParser = require('body-parser');
const jetpack = require('fs-jetpack'); const jetpack = require('fs-jetpack');
const path = require('path'); const path = require('path');
const Database = require('./Database');
const rateLimiter = new RateLimit({ const rateLimiter = new RateLimit({
windowMs: process.env.RATE_LIMIT_WINDOW, windowMs: process.env.RATE_LIMIT_WINDOW,
@ -35,7 +34,6 @@ class Server {
this.server.use(bodyParser.json()); this.server.use(bodyParser.json());
// this.server.use(rateLimiter); // this.server.use(rateLimiter);
this.routesFolder = path.join(__dirname, '..', 'routes'); this.routesFolder = path.join(__dirname, '..', 'routes');
this.database = new Database();
} }
registerAllTheRoutes() { registerAllTheRoutes() {

View File

@ -1,3 +1,4 @@
const randomstring = require('randomstring');
const jetpack = require('fs-jetpack'); const jetpack = require('fs-jetpack');
const qoa = require('qoa'); const qoa = require('qoa');
qoa.config({ qoa.config({
@ -15,6 +16,10 @@ async function start() {
}); });
if (!confirm.run) process.exit(0); if (!confirm.run) process.exit(0);
console.log();
console.log('You can manually edit .env file after the wizard to edit values');
console.log();
const wizard = [ const wizard = [
{ {
type: 'input', type: 'input',
@ -93,10 +98,35 @@ async function start() {
accept: 'y', accept: 'y',
deny: 'n' deny: 'n'
}, },
{
type: 'input',
query: 'Name of the admin account?',
handle: 'ADMIN_ACCOUNT'
},
{ {
type: 'secure', type: 'secure',
query: 'Type a secure password for the root user:', query: 'Type a secure password for the root user:',
handle: 'ROOT_PASSWORD' handle: 'ADMIN_PASSWORD'
},
{
type: 'input',
query: 'Database host',
handle: 'DB_HOST'
},
{
type: 'input',
query: 'Database user',
handle: 'DB_USER'
},
{
type: 'input',
query: 'Database password',
handle: 'DB_PASSWORD'
},
{
type: 'input',
query: 'Database name',
handle: 'DB_DATABASE'
} }
]; ];
@ -109,13 +139,9 @@ async function start() {
RATE_LIMIT_WINDOW: 2, RATE_LIMIT_WINDOW: 2,
RATE_LIMIT_MAX: 5, RATE_LIMIT_MAX: 5,
DB_CLIENT: 'pg', DB_CLIENT: 'pg',
DB_HOST: 'localhost',
DB_USER: '',
DB_PASSWORD: '',
DB_DATABASE: '',
BLOCKED_EXTENSIONS: ['.jar', '.exe', '.msi', '.com', '.bat', '.cmd', '.scr', '.ps1', '.sh'], BLOCKED_EXTENSIONS: ['.jar', '.exe', '.msi', '.com', '.bat', '.cmd', '.scr', '.ps1', '.sh'],
UPLOAD_FOLDER: 'uploads', UPLOAD_FOLDER: 'uploads',
SECRET: 'SuperSecretPassphraseHere', SECRET: randomstring.generate(64),
MAX_LINKS_PER_ALBUM: 5 MAX_LINKS_PER_ALBUM: 5
}; };
@ -128,7 +154,11 @@ async function start() {
jetpack.write('.env', envfile); jetpack.write('.env', envfile);
console.log(); console.log();
console.log('== .env file generated successfully. You can now run lolisafe =='); console.log('=============================================');
console.log('== .env file generated successfully. ==');
console.log('=============================================');
console.log('== Run `yarn migrate` and `yarn seed` next ==');
console.log('=============================================');
console.log(); console.log();
} }