chore: linter the entire project using the new rules
This commit is contained in:
parent
b519b6ccb4
commit
ad852de51a
10
knexfile.js
10
knexfile.js
|
@ -7,17 +7,17 @@ module.exports = {
|
||||||
user: process.env.DB_USER,
|
user: process.env.DB_USER,
|
||||||
password: process.env.DB_PASSWORD,
|
password: process.env.DB_PASSWORD,
|
||||||
database: process.env.DB_DATABASE,
|
database: process.env.DB_DATABASE,
|
||||||
filename: 'database.sqlite'
|
filename: 'database.sqlite',
|
||||||
},
|
},
|
||||||
pool: {
|
pool: {
|
||||||
min: process.env.DATABASE_POOL_MIN || 2,
|
min: process.env.DATABASE_POOL_MIN || 2,
|
||||||
max: process.env.DATABASE_POOL_MAX || 10
|
max: process.env.DATABASE_POOL_MAX || 10,
|
||||||
},
|
},
|
||||||
migrations: {
|
migrations: {
|
||||||
directory: 'src/api/database/migrations'
|
directory: 'src/api/database/migrations',
|
||||||
},
|
},
|
||||||
seeds: {
|
seeds: {
|
||||||
directory: 'src/api/database/seeds'
|
directory: 'src/api/database/seeds',
|
||||||
},
|
},
|
||||||
useNullAsDefault: process.env.DB_CLIENT === 'sqlite3' ? true : false
|
useNullAsDefault: process.env.DB_CLIENT === 'sqlite3',
|
||||||
};
|
};
|
||||||
|
|
|
@ -172,7 +172,8 @@
|
||||||
],
|
],
|
||||||
"import/no-extraneous-dependencies": "off",
|
"import/no-extraneous-dependencies": "off",
|
||||||
"no-restricted-syntax": "off",
|
"no-restricted-syntax": "off",
|
||||||
"no-continue": "off"
|
"no-continue": "off",
|
||||||
|
"no-await-in-loop": "off"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"import/resolver": {
|
"import/resolver": {
|
||||||
|
|
4
pm2.json
4
pm2.json
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"apps" : [
|
"apps": [
|
||||||
{
|
{
|
||||||
"name": "lolisafe",
|
"name": "lolisafe",
|
||||||
"script": "npm",
|
"script": "npm",
|
||||||
|
@ -7,7 +7,7 @@
|
||||||
"env": {
|
"env": {
|
||||||
"NODE_ENV": "production"
|
"NODE_ENV": "production"
|
||||||
},
|
},
|
||||||
"env_production" : {
|
"env_production": {
|
||||||
"NODE_ENV": "production"
|
"NODE_ENV": "production"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
exports.up = async knex => {
|
exports.up = async (knex) => {
|
||||||
await knex.schema.createTable('users', table => {
|
await knex.schema.createTable('users', (table) => {
|
||||||
table.increments();
|
table.increments();
|
||||||
table.string('username');
|
table.string('username');
|
||||||
table.text('password');
|
table.text('password');
|
||||||
|
@ -12,7 +12,7 @@ exports.up = async knex => {
|
||||||
table.timestamp('editedAt');
|
table.timestamp('editedAt');
|
||||||
});
|
});
|
||||||
|
|
||||||
await knex.schema.createTable('albums', table => {
|
await knex.schema.createTable('albums', (table) => {
|
||||||
table.increments();
|
table.increments();
|
||||||
table.integer('userId');
|
table.integer('userId');
|
||||||
table.string('name');
|
table.string('name');
|
||||||
|
@ -22,7 +22,7 @@ exports.up = async knex => {
|
||||||
table.timestamp('editedAt');
|
table.timestamp('editedAt');
|
||||||
});
|
});
|
||||||
|
|
||||||
await knex.schema.createTable('files', table => {
|
await knex.schema.createTable('files', (table) => {
|
||||||
table.increments();
|
table.increments();
|
||||||
table.integer('userId');
|
table.integer('userId');
|
||||||
table.string('name');
|
table.string('name');
|
||||||
|
@ -36,7 +36,7 @@ exports.up = async knex => {
|
||||||
table.timestamp('editedAt');
|
table.timestamp('editedAt');
|
||||||
});
|
});
|
||||||
|
|
||||||
await knex.schema.createTable('links', table => {
|
await knex.schema.createTable('links', (table) => {
|
||||||
table.increments();
|
table.increments();
|
||||||
table.integer('userId');
|
table.integer('userId');
|
||||||
table.integer('albumId');
|
table.integer('albumId');
|
||||||
|
@ -49,19 +49,19 @@ exports.up = async knex => {
|
||||||
table.timestamp('editedAt');
|
table.timestamp('editedAt');
|
||||||
});
|
});
|
||||||
|
|
||||||
await knex.schema.createTable('albumsFiles', table => {
|
await knex.schema.createTable('albumsFiles', (table) => {
|
||||||
table.increments();
|
table.increments();
|
||||||
table.integer('albumId');
|
table.integer('albumId');
|
||||||
table.integer('fileId');
|
table.integer('fileId');
|
||||||
});
|
});
|
||||||
|
|
||||||
await knex.schema.createTable('albumsLinks', table => {
|
await knex.schema.createTable('albumsLinks', (table) => {
|
||||||
table.increments();
|
table.increments();
|
||||||
table.integer('albumId');
|
table.integer('albumId');
|
||||||
table.integer('linkId');
|
table.integer('linkId');
|
||||||
});
|
});
|
||||||
|
|
||||||
await knex.schema.createTable('tags', table => {
|
await knex.schema.createTable('tags', (table) => {
|
||||||
table.increments();
|
table.increments();
|
||||||
table.string('uuid');
|
table.string('uuid');
|
||||||
table.integer('userId');
|
table.integer('userId');
|
||||||
|
@ -70,19 +70,19 @@ exports.up = async knex => {
|
||||||
table.timestamp('editedAt');
|
table.timestamp('editedAt');
|
||||||
});
|
});
|
||||||
|
|
||||||
await knex.schema.createTable('fileTags', table => {
|
await knex.schema.createTable('fileTags', (table) => {
|
||||||
table.increments();
|
table.increments();
|
||||||
table.integer('fileId');
|
table.integer('fileId');
|
||||||
table.integer('tagId');
|
table.integer('tagId');
|
||||||
});
|
});
|
||||||
|
|
||||||
await knex.schema.createTable('bans', table => {
|
await knex.schema.createTable('bans', (table) => {
|
||||||
table.increments();
|
table.increments();
|
||||||
table.string('ip');
|
table.string('ip');
|
||||||
table.timestamp('createdAt');
|
table.timestamp('createdAt');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
exports.down = async knex => {
|
exports.down = async (knex) => {
|
||||||
await knex.schema.dropTableIfExists('users');
|
await knex.schema.dropTableIfExists('users');
|
||||||
await knex.schema.dropTableIfExists('albums');
|
await knex.schema.dropTableIfExists('albums');
|
||||||
await knex.schema.dropTableIfExists('files');
|
await knex.schema.dropTableIfExists('files');
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
|
/* eslint-disable no-console */
|
||||||
const bcrypt = require('bcrypt');
|
const bcrypt = require('bcrypt');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
|
|
||||||
exports.seed = async db => {
|
exports.seed = async (db) => {
|
||||||
const now = moment.utc().toDate();
|
const now = moment.utc().toDate();
|
||||||
const user = await db.table('users').where({ username: process.env.ADMIN_ACCOUNT }).first();
|
const user = await db.table('users').where({ username: process.env.ADMIN_ACCOUNT }).first();
|
||||||
if (user) return;
|
if (user) return;
|
||||||
|
@ -14,7 +15,7 @@ exports.seed = async db => {
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
editedAt: now,
|
editedAt: now,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
isAdmin: true
|
isAdmin: true,
|
||||||
});
|
});
|
||||||
console.log();
|
console.log();
|
||||||
console.log('=========================================================');
|
console.log('=========================================================');
|
||||||
|
|
|
@ -1,28 +1,31 @@
|
||||||
|
/* eslint-disable eqeqeq */
|
||||||
|
/* eslint-disable no-await-in-loop */
|
||||||
|
/* eslint-disable no-console */
|
||||||
const nodePath = require('path');
|
const nodePath = require('path');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
|
|
||||||
const oldDb = require('knex')({
|
const oldDb = require('knex')({
|
||||||
client: 'sqlite3',
|
client: 'sqlite3',
|
||||||
connection: {
|
connection: {
|
||||||
filename: nodePath.join(__dirname, '..', '..', 'db')
|
filename: nodePath.join(__dirname, '..', '..', 'db'),
|
||||||
},
|
},
|
||||||
useNullAsDefault: true
|
useNullAsDefault: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const newDb = require('knex')({
|
const newDb = require('knex')({
|
||||||
client: 'sqlite3',
|
client: 'sqlite3',
|
||||||
connection: {
|
connection: {
|
||||||
filename: nodePath.join(__dirname, '..', '..', 'database.sqlite')
|
filename: nodePath.join(__dirname, '..', '..', 'database.sqlite'),
|
||||||
},
|
},
|
||||||
postProcessResponse: result => {
|
postProcessResponse: (result) => {
|
||||||
const booleanFields = [
|
const booleanFields = [
|
||||||
'enabled',
|
'enabled',
|
||||||
'enableDownload',
|
'enableDownload',
|
||||||
'isAdmin'
|
'isAdmin',
|
||||||
];
|
];
|
||||||
|
|
||||||
const processResponse = row => {
|
const processResponse = (row) => {
|
||||||
Object.keys(row).forEach(key => {
|
Object.keys(row).forEach((key) => {
|
||||||
if (booleanFields.includes(key)) {
|
if (booleanFields.includes(key)) {
|
||||||
if (row[key] === 0) row[key] = false;
|
if (row[key] === 0) row[key] = false;
|
||||||
else if (row[key] === 1) row[key] = true;
|
else if (row[key] === 1) row[key] = true;
|
||||||
|
@ -31,11 +34,11 @@ const newDb = require('knex')({
|
||||||
return row;
|
return row;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (Array.isArray(result)) return result.map(row => processResponse(row));
|
if (Array.isArray(result)) return result.map((row) => processResponse(row));
|
||||||
if (typeof result === 'object') return processResponse(result);
|
if (typeof result === 'object') return processResponse(result);
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
useNullAsDefault: true
|
useNullAsDefault: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const start = async () => {
|
const start = async () => {
|
||||||
|
@ -49,13 +52,13 @@ const start = async () => {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
username: user.username,
|
username: user.username,
|
||||||
password: user.password,
|
password: user.password,
|
||||||
enabled: user.enabled == 1 ? true : false,
|
enabled: user.enabled == 1,
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
apiKey: user.token,
|
apiKey: user.token,
|
||||||
passwordEditedAt: now,
|
passwordEditedAt: now,
|
||||||
apiKeyEditedAt: now,
|
apiKeyEditedAt: now,
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
editedAt: now
|
editedAt: now,
|
||||||
};
|
};
|
||||||
await newDb.table('users').insert(userToInsert);
|
await newDb.table('users').insert(userToInsert);
|
||||||
}
|
}
|
||||||
|
@ -71,7 +74,7 @@ const start = async () => {
|
||||||
name: album.name,
|
name: album.name,
|
||||||
zippedAt: album.zipGeneratedAt ? moment.unix(album.zipGeneratedAt).toDate() : null,
|
zippedAt: album.zipGeneratedAt ? moment.unix(album.zipGeneratedAt).toDate() : null,
|
||||||
createdAt: moment.unix(album.timestamp).toDate(),
|
createdAt: moment.unix(album.timestamp).toDate(),
|
||||||
editedAt: moment.unix(album.editedAt).toDate()
|
editedAt: moment.unix(album.editedAt).toDate(),
|
||||||
};
|
};
|
||||||
const linkToInsert = {
|
const linkToInsert = {
|
||||||
userId: album.userid,
|
userId: album.userid,
|
||||||
|
@ -81,13 +84,13 @@ const start = async () => {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
enableDownload: true,
|
enableDownload: true,
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
editedAt: now
|
editedAt: now,
|
||||||
};
|
};
|
||||||
await newDb.table('albums').insert(albumToInsert);
|
await newDb.table('albums').insert(albumToInsert);
|
||||||
const insertedId = await newDb.table('links').insert(linkToInsert);
|
const insertedId = await newDb.table('links').insert(linkToInsert);
|
||||||
await newDb.table('albumsLinks').insert({
|
await newDb.table('albumsLinks').insert({
|
||||||
albumId: album.id,
|
albumId: album.id,
|
||||||
linkId: insertedId[0]
|
linkId: insertedId[0],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.log('Finished migrating albums...');
|
console.log('Finished migrating albums...');
|
||||||
|
@ -106,12 +109,12 @@ const start = async () => {
|
||||||
hash: file.hash,
|
hash: file.hash,
|
||||||
ip: file.ip,
|
ip: file.ip,
|
||||||
createdAt: moment.unix(file.timestamp).toDate(),
|
createdAt: moment.unix(file.timestamp).toDate(),
|
||||||
editedAt: moment.unix(file.timestamp).toDate()
|
editedAt: moment.unix(file.timestamp).toDate(),
|
||||||
};
|
};
|
||||||
filesToInsert.push(fileToInsert);
|
filesToInsert.push(fileToInsert);
|
||||||
albumsFilesToInsert.push({
|
albumsFilesToInsert.push({
|
||||||
albumId: file.albumid,
|
albumId: file.albumid,
|
||||||
fileId: file.id
|
fileId: file.id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await newDb.batchInsert('files', filesToInsert, 20);
|
await newDb.batchInsert('files', filesToInsert, 20);
|
||||||
|
|
|
@ -17,7 +17,7 @@ class banIP extends Route {
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
message: 'Successfully banned the ip'
|
message: 'Successfully banned the ip',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ class filesGET extends Route {
|
||||||
return res.json({
|
return res.json({
|
||||||
message: 'Successfully retrieved file',
|
message: 'Successfully retrieved file',
|
||||||
file,
|
file,
|
||||||
user
|
user,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ class unBanIP extends Route {
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
message: 'Successfully unbanned the ip'
|
message: 'Successfully unbanned the ip',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ class userDemote extends Route {
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
message: 'Successfully demoted user'
|
message: 'Successfully demoted user',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ class userDisable extends Route {
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
message: 'Successfully disabled user'
|
message: 'Successfully disabled user',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ class userEnable extends Route {
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
message: 'Successfully enabled user'
|
message: 'Successfully enabled user',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ class usersGET extends Route {
|
||||||
return res.json({
|
return res.json({
|
||||||
message: 'Successfully retrieved user',
|
message: 'Successfully retrieved user',
|
||||||
user,
|
user,
|
||||||
files
|
files,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return super.error(res, error);
|
return super.error(res, error);
|
||||||
|
|
|
@ -20,7 +20,7 @@ class userPromote extends Route {
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
message: 'Successfully promoted user'
|
message: 'Successfully promoted user',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ class userDemote extends Route {
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
message: 'Successfully deleted the user\'s files'
|
message: 'Successfully deleted the user\'s files',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ class usersGET extends Route {
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
message: 'Successfully retrieved users',
|
message: 'Successfully retrieved users',
|
||||||
users
|
users,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return super.error(res, error);
|
return super.error(res, error);
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
const Route = require('../../structures/Route');
|
const Route = require('../../structures/Route');
|
||||||
const Util = require('../../utils/Util');
|
|
||||||
|
|
||||||
class albumDELETE extends Route {
|
class albumDELETE extends Route {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const Route = require('../../structures/Route');
|
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
|
const Route = require('../../structures/Route');
|
||||||
|
|
||||||
class albumPOST extends Route {
|
class albumPOST extends Route {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -25,7 +25,7 @@ class albumPOST extends Route {
|
||||||
name,
|
name,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
editedAt: now
|
editedAt: now,
|
||||||
};
|
};
|
||||||
|
|
||||||
const dbRes = await db.table('albums').insert(insertObj);
|
const dbRes = await db.table('albums').insert(insertObj);
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
|
const path = require('path');
|
||||||
|
const jetpack = require('fs-jetpack');
|
||||||
const Route = require('../../structures/Route');
|
const Route = require('../../structures/Route');
|
||||||
const Util = require('../../utils/Util');
|
const Util = require('../../utils/Util');
|
||||||
const log = require('../../utils/Log');
|
const log = require('../../utils/Log');
|
||||||
const path = require('path');
|
|
||||||
const jetpack = require('fs-jetpack');
|
|
||||||
|
|
||||||
class albumGET extends Route {
|
class albumGET extends Route {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -21,7 +21,7 @@ class albumGET extends Route {
|
||||||
.where({
|
.where({
|
||||||
identifier,
|
identifier,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
enableDownload: true
|
enableDownload: true,
|
||||||
})
|
})
|
||||||
.first();
|
.first();
|
||||||
if (!link) return res.status(400).json({ message: 'The supplied identifier could not be found' });
|
if (!link) return res.status(400).json({ message: 'The supplied identifier could not be found' });
|
||||||
|
@ -64,11 +64,11 @@ class albumGET extends Route {
|
||||||
/*
|
/*
|
||||||
Get the actual files
|
Get the actual files
|
||||||
*/
|
*/
|
||||||
const fileIds = fileList.map(el => el.fileId);
|
const fileIds = fileList.map((el) => el.fileId);
|
||||||
const files = await db.table('files')
|
const files = await db.table('files')
|
||||||
.whereIn('id', fileIds)
|
.whereIn('id', fileIds)
|
||||||
.select('name');
|
.select('name');
|
||||||
const filesToZip = files.map(el => el.name);
|
const filesToZip = files.map((el) => el.name);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Util.createZip(filesToZip, album);
|
Util.createZip(filesToZip, album);
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
const Route = require('../../../structures/Route');
|
const Route = require('../../../structures/Route');
|
||||||
const { dump } = require('dumper.js');
|
|
||||||
|
|
||||||
class linkDELETE extends Route {
|
class linkDELETE extends Route {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -28,7 +27,7 @@ class linkDELETE extends Route {
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
message: 'Successfully deleted link'
|
message: 'Successfully deleted link',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
const Route = require('../../../structures/Route');
|
const Route = require('../../../structures/Route');
|
||||||
const log = require('../../../utils/Log');
|
|
||||||
|
|
||||||
class linkEditPOST extends Route {
|
class linkEditPOST extends Route {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -23,7 +22,7 @@ class linkEditPOST extends Route {
|
||||||
try {
|
try {
|
||||||
const updateObj = {
|
const updateObj = {
|
||||||
enableDownload: enableDownload || false,
|
enableDownload: enableDownload || false,
|
||||||
expiresAt // This one should be null if not supplied
|
expiresAt, // This one should be null if not supplied
|
||||||
};
|
};
|
||||||
await db
|
await db
|
||||||
.table('links')
|
.table('links')
|
||||||
|
|
|
@ -28,15 +28,13 @@ class linkPOST extends Route {
|
||||||
.where('albumId', albumId)
|
.where('albumId', albumId)
|
||||||
.count({ count: 'id' })
|
.count({ count: 'id' })
|
||||||
.first();
|
.first();
|
||||||
if (count >= parseInt(process.env.MAX_LINKS_PER_ALBUM, 10))
|
if (count >= parseInt(process.env.MAX_LINKS_PER_ALBUM, 10)) return res.status(400).json({ message: 'Maximum links per album reached' });
|
||||||
return res.status(400).json({ message: 'Maximum links per album reached' });
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Try to allocate a new identifier on the db
|
Try to allocate a new identifier on the db
|
||||||
*/
|
*/
|
||||||
const identifier = await Util.getUniqueAlbumIdentifier();
|
const identifier = await Util.getUniqueAlbumIdentifier();
|
||||||
if (!identifier)
|
if (!identifier) return res.status(500).json({ message: 'There was a problem allocating a link for your album' });
|
||||||
return res.status(500).json({ message: 'There was a problem allocating a link for your album' });
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const insertObj = {
|
const insertObj = {
|
||||||
|
@ -46,13 +44,13 @@ class linkPOST extends Route {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
enableDownload: true,
|
enableDownload: true,
|
||||||
expiresAt: null,
|
expiresAt: null,
|
||||||
views: 0
|
views: 0,
|
||||||
};
|
};
|
||||||
await db.table('links').insert(insertObj);
|
await db.table('links').insert(insertObj);
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
message: 'The link was created successfully',
|
message: 'The link was created successfully',
|
||||||
data: insertObj
|
data: insertObj,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return super.error(res, error);
|
return super.error(res, error);
|
||||||
|
|
|
@ -14,7 +14,7 @@ class linkPOST extends Route {
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
message: 'Successfully retrieved links',
|
message: 'Successfully retrieved links',
|
||||||
links
|
links,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const Route = require('../../structures/Route');
|
|
||||||
const bcrypt = require('bcrypt');
|
const bcrypt = require('bcrypt');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
const JWT = require('jsonwebtoken');
|
const JWT = require('jsonwebtoken');
|
||||||
|
const Route = require('../../structures/Route');
|
||||||
|
|
||||||
class loginPOST extends Route {
|
class loginPOST extends Route {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -36,7 +36,7 @@ class loginPOST extends Route {
|
||||||
const jwt = JWT.sign({
|
const jwt = JWT.sign({
|
||||||
iss: 'lolisafe',
|
iss: 'lolisafe',
|
||||||
sub: user.id,
|
sub: user.id,
|
||||||
iat: moment.utc().valueOf()
|
iat: moment.utc().valueOf(),
|
||||||
}, process.env.SECRET, { expiresIn: '30d' });
|
}, process.env.SECRET, { expiresIn: '30d' });
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
|
@ -45,10 +45,10 @@ class loginPOST extends Route {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
username: user.username,
|
username: user.username,
|
||||||
apiKey: user.apiKey,
|
apiKey: user.apiKey,
|
||||||
isAdmin: user.isAdmin
|
isAdmin: user.isAdmin,
|
||||||
},
|
},
|
||||||
token: jwt,
|
token: jwt,
|
||||||
apiKey: user.apiKey
|
apiKey: user.apiKey,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const Route = require('../../structures/Route');
|
|
||||||
const log = require('../../utils/Log');
|
|
||||||
const bcrypt = require('bcrypt');
|
const bcrypt = require('bcrypt');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
|
const Route = require('../../structures/Route');
|
||||||
|
const log = require('../../utils/Log');
|
||||||
|
|
||||||
class registerPOST extends Route {
|
class registerPOST extends Route {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -9,7 +9,7 @@ class registerPOST extends Route {
|
||||||
}
|
}
|
||||||
|
|
||||||
async run(req, res, db) {
|
async run(req, res, db) {
|
||||||
if (process.env.USER_ACCOUNTS == 'false') return res.status(401).json({ message: 'Creation of new accounts is currently disabled' });
|
if (process.env.USER_ACCOUNTS === 'false') 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' });
|
if (!req.body) return res.status(400).json({ message: 'No body provided' });
|
||||||
const { username, password } = req.body;
|
const { username, password } = req.body;
|
||||||
if (!username || !password) return res.status(401).json({ message: 'Invalid body provided' });
|
if (!username || !password) return res.status(401).json({ message: 'Invalid body provided' });
|
||||||
|
@ -50,7 +50,7 @@ class registerPOST extends Route {
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
editedAt: now,
|
editedAt: now,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
isAdmin: false
|
isAdmin: false,
|
||||||
});
|
});
|
||||||
return res.json({ message: 'The account was created successfully' });
|
return res.json({ message: 'The account was created successfully' });
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ class filesGET extends Route {
|
||||||
.select('albumId');
|
.select('albumId');
|
||||||
|
|
||||||
if (albumFiles.length) {
|
if (albumFiles.length) {
|
||||||
albumFiles = albumFiles.map(a => a.albumId);
|
albumFiles = albumFiles.map((a) => a.albumId);
|
||||||
albums = await db.table('albums')
|
albums = await db.table('albums')
|
||||||
.whereIn('id', albumFiles)
|
.whereIn('id', albumFiles)
|
||||||
.select('id', 'name');
|
.select('id', 'name');
|
||||||
|
@ -26,7 +26,7 @@ class filesGET extends Route {
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
message: 'Successfully retrieved file albums',
|
message: 'Successfully retrieved file albums',
|
||||||
albums
|
albums,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ class filesGET extends Route {
|
||||||
return res.json({
|
return res.json({
|
||||||
message: 'Successfully retrieved files',
|
message: 'Successfully retrieved files',
|
||||||
files,
|
files,
|
||||||
count
|
count,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,8 @@ class tagAddPOST extends Route {
|
||||||
const file = await db.table('files').where({ id: fileId, userId: user.id }).first();
|
const file = await db.table('files').where({ id: fileId, userId: user.id }).first();
|
||||||
if (!file) return res.status(400).json({ message: 'File doesn\'t exist.' });
|
if (!file) return res.status(400).json({ message: 'File doesn\'t exist.' });
|
||||||
|
|
||||||
tagNames.forEach(async tag => {
|
// eslint-disable-next-line consistent-return
|
||||||
|
tagNames.forEach(async (tag) => {
|
||||||
try {
|
try {
|
||||||
await db.table('fileTags').insert({ fileId, tag });
|
await db.table('fileTags').insert({ fileId, tag });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -23,7 +24,7 @@ class tagAddPOST extends Route {
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
message: 'Successfully added file to album'
|
message: 'Successfully added file to album',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,11 +15,11 @@ class configGET extends Route {
|
||||||
maxUploadSize: parseInt(process.env.MAX_SIZE, 10),
|
maxUploadSize: parseInt(process.env.MAX_SIZE, 10),
|
||||||
filenameLength: parseInt(process.env.GENERATED_FILENAME_LENGTH, 10),
|
filenameLength: parseInt(process.env.GENERATED_FILENAME_LENGTH, 10),
|
||||||
albumLinkLength: parseInt(process.env.GENERATED_ALBUM_LENGTH, 10),
|
albumLinkLength: parseInt(process.env.GENERATED_ALBUM_LENGTH, 10),
|
||||||
generateThumbnails: process.env.GENERATE_THUMBNAILS == 'true' ? true : false,
|
generateThumbnails: process.env.GENERATE_THUMBNAILS === 'true',
|
||||||
generateZips: process.env.GENERATE_ZIPS == 'true' ? true : false,
|
generateZips: process.env.GENERATE_ZIPS === 'true',
|
||||||
publicMode: process.env.PUBLIC_MODE == 'true' ? true : false,
|
publicMode: process.env.PUBLIC_MODE === 'true',
|
||||||
enableAccounts: process.env.USER_ACCOUNTS == 'true' ? true : false
|
enableAccounts: process.env.USER_ACCOUNTS === 'true',
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const Route = require('../../structures/Route');
|
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
|
const Route = require('../../structures/Route');
|
||||||
|
|
||||||
class tagPOST extends Route {
|
class tagPOST extends Route {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -22,7 +22,7 @@ class tagPOST extends Route {
|
||||||
name,
|
name,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
editedAt: now
|
editedAt: now,
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.json({ message: 'The tag was created successfully' });
|
return res.json({ message: 'The tag was created successfully' });
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
const Route = require('../../structures/Route');
|
const Route = require('../../structures/Route');
|
||||||
const Util = require('../../utils/Util');
|
|
||||||
|
|
||||||
class tagsGET extends Route {
|
class tagsGET extends Route {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -20,7 +19,7 @@ class tagsGET extends Route {
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
message: 'Successfully retrieved tags',
|
message: 'Successfully retrieved tags',
|
||||||
tags
|
tags,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return super.error(res, error);
|
return super.error(res, error);
|
||||||
|
|
|
@ -1,27 +1,27 @@
|
||||||
const Route = require('../../structures/Route');
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const Util = require('../../utils/Util');
|
|
||||||
const jetpack = require('fs-jetpack');
|
const jetpack = require('fs-jetpack');
|
||||||
const randomstring = require('randomstring');
|
const randomstring = require('randomstring');
|
||||||
|
const Util = require('../../utils/Util');
|
||||||
|
const Route = require('../../structures/Route');
|
||||||
|
|
||||||
class uploadPOST extends Route {
|
class uploadPOST extends Route {
|
||||||
constructor() {
|
constructor() {
|
||||||
super('/upload/chunks', 'post', {
|
super('/upload/chunks', 'post', {
|
||||||
bypassAuth: true,
|
bypassAuth: true,
|
||||||
canApiKey: true
|
canApiKey: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async run(req, res, db) {
|
async run(req, res) {
|
||||||
const filename = Util.getUniqueFilename(randomstring.generate(32));
|
const filename = Util.getUniqueFilename(randomstring.generate(32));
|
||||||
// console.log('Files', req.body.files);
|
// console.log('Files', req.body.files);
|
||||||
const info = {
|
const info = {
|
||||||
size: req.body.files[0].size,
|
size: req.body.files[0].size,
|
||||||
url: `${process.env.DOMAIN}/`
|
url: `${process.env.DOMAIN}/`,
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const chunk of req.body.files) {
|
for (const chunk of req.body.files) {
|
||||||
const { uuid, count } = chunk;
|
const { uuid } = chunk;
|
||||||
// console.log('Chunk', chunk);
|
// console.log('Chunk', chunk);
|
||||||
|
|
||||||
const chunkOutput = path.join(__dirname,
|
const chunkOutput = path.join(__dirname,
|
||||||
|
@ -65,7 +65,7 @@ class uploadPOST extends Route {
|
||||||
|
|
||||||
return res.status(201).send({
|
return res.status(201).send({
|
||||||
message: 'Sucessfully merged the chunk(s).',
|
message: 'Sucessfully merged the chunk(s).',
|
||||||
...info
|
...info,
|
||||||
/*
|
/*
|
||||||
name: `${filename}${ext || ''}`,
|
name: `${filename}${ext || ''}`,
|
||||||
size: exists.size,
|
size: exists.size,
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
const Route = require('../../structures/Route');
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const Util = require('../../utils/Util');
|
|
||||||
const jetpack = require('fs-jetpack');
|
const jetpack = require('fs-jetpack');
|
||||||
const multer = require('multer');
|
const multer = require('multer');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
|
const Util = require('../../utils/Util');
|
||||||
|
const Route = require('../../structures/Route');
|
||||||
|
|
||||||
const upload = multer({
|
const upload = multer({
|
||||||
storage: multer.memoryStorage(),
|
storage: multer.memoryStorage(),
|
||||||
limits: {
|
limits: {
|
||||||
fileSize: parseInt(process.env.MAX_SIZE, 10) * (1000 * 1000),
|
fileSize: parseInt(process.env.MAX_SIZE, 10) * (1000 * 1000),
|
||||||
files: 1
|
files: 1,
|
||||||
},
|
},
|
||||||
fileFilter: (req, file, cb) => {
|
fileFilter: (req, file, cb) =>
|
||||||
// TODO: Enable blacklisting of files/extensions
|
// TODO: Enable blacklisting of files/extensions
|
||||||
/*
|
/*
|
||||||
if (options.blacklist.mimes.includes(file.mimetype)) {
|
if (options.blacklist.mimes.includes(file.mimetype)) {
|
||||||
|
@ -19,21 +20,20 @@ const upload = multer({
|
||||||
return cb(new Error(`${path.extname(file.originalname).toLowerCase()} is a blacklisted extension.`));
|
return cb(new Error(`${path.extname(file.originalname).toLowerCase()} is a blacklisted extension.`));
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
return cb(null, true);
|
cb(null, true)
|
||||||
}
|
,
|
||||||
}).array('files[]');
|
}).array('files[]');
|
||||||
|
|
||||||
/*
|
/*
|
||||||
TODO: If source has transparency generate a png thumbnail, otherwise a jpg.
|
TODO: If source has transparency generate a png thumbnail, otherwise a jpg.
|
||||||
TODO: If source is a gif, generate a thumb of the first frame and play the gif on hover on the frontend.
|
TODO: If source is a gif, generate a thumb of the first frame and play the gif on hover on the frontend.
|
||||||
TODO: If source is a video, generate a thumb of the first frame and save the video length to the file?
|
|
||||||
Another possible solution would be to play a gif on hover that grabs a few chunks like youtube.
|
|
||||||
|
|
||||||
TODO: Think if its worth making a folder with the user uuid in uploads/ and upload the pictures there so
|
TODO: Think if its worth making a folder with the user uuid in uploads/ and upload the pictures there so
|
||||||
that this way at least not every single file will be in 1 directory
|
that this way at least not every single file will be in 1 directory
|
||||||
|
|
||||||
- Addendum to this: Now that the default behaviour is to serve files with node, we can actually pull this off. Before this, having files in
|
XXX: Now that the default behaviour is to serve files with node, we can actually pull this off.
|
||||||
subfolders meant messing with nginx and the paths, but now it should be fairly easy to re-arrange the folder structure with express.static
|
Before this, having files in subfolders meant messing with nginx and the paths,
|
||||||
|
but now it should be fairly easy to re-arrange the folder structure with express.static
|
||||||
I see great value in this, open to suggestions.
|
I see great value in this, open to suggestions.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -41,13 +41,13 @@ class uploadPOST extends Route {
|
||||||
constructor() {
|
constructor() {
|
||||||
super('/upload', 'post', {
|
super('/upload', 'post', {
|
||||||
bypassAuth: true,
|
bypassAuth: true,
|
||||||
canApiKey: true
|
canApiKey: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async run(req, res, db) {
|
async run(req, res, db) {
|
||||||
const user = await Util.isAuthorized(req);
|
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 && process.env.PUBLIC_MODE === 'false') return res.status(401).json({ message: 'Not authorized to use this resource' });
|
||||||
|
|
||||||
const albumId = req.body.albumid || req.headers.albumid;
|
const albumId = req.body.albumid || req.headers.albumid;
|
||||||
if (albumId && !user) return res.status(401).json({ message: 'Only registered users can upload files to an album' });
|
if (albumId && !user) return res.status(401).json({ message: 'Only registered users can upload files to an album' });
|
||||||
|
@ -56,12 +56,13 @@ class uploadPOST extends Route {
|
||||||
if (!album) return res.status(401).json({ message: 'Album doesn\'t exist or it doesn\'t belong to the user' });
|
if (!album) return res.status(401).json({ message: 'Album doesn\'t exist or it doesn\'t belong to the user' });
|
||||||
}
|
}
|
||||||
|
|
||||||
return upload(req, res, async err => {
|
return upload(req, res, async (err) => {
|
||||||
if (err) console.error(err.message);
|
if (err) console.error(err.message);
|
||||||
|
|
||||||
let uploadedFile = {};
|
let uploadedFile = {};
|
||||||
let insertedId;
|
let insertedId;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-underscore-dangle
|
||||||
const remappedKeys = this._remapKeys(req.body);
|
const remappedKeys = this._remapKeys(req.body);
|
||||||
const file = req.files[0];
|
const file = req.files[0];
|
||||||
|
|
||||||
|
@ -105,7 +106,7 @@ class uploadPOST extends Route {
|
||||||
name: filename,
|
name: filename,
|
||||||
hash,
|
hash,
|
||||||
size: file.buffer.length,
|
size: file.buffer.length,
|
||||||
url: filename
|
url: filename,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,7 +125,7 @@ class uploadPOST extends Route {
|
||||||
|
|
||||||
return res.status(201).send({
|
return res.status(201).send({
|
||||||
message: 'Sucessfully uploaded the file.',
|
message: 'Sucessfully uploaded the file.',
|
||||||
...uploadedFile
|
...uploadedFile,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -137,7 +138,7 @@ class uploadPOST extends Route {
|
||||||
size: exists.size,
|
size: exists.size,
|
||||||
url: `${process.env.DOMAIN}/${exists.name}`,
|
url: `${process.env.DOMAIN}/${exists.name}`,
|
||||||
deleteUrl: `${process.env.DOMAIN}/api/file/${exists.id}`,
|
deleteUrl: `${process.env.DOMAIN}/api/file/${exists.id}`,
|
||||||
repeated: true
|
repeated: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
return Util.deleteFile(filename);
|
return Util.deleteFile(filename);
|
||||||
|
@ -145,7 +146,7 @@ class uploadPOST extends Route {
|
||||||
|
|
||||||
async checkIfFileExists(db, user, hash) {
|
async checkIfFileExists(db, user, hash) {
|
||||||
const exists = await db.table('files')
|
const exists = await db.table('files')
|
||||||
.where(function() { // eslint-disable-line func-names
|
.where(function () { // eslint-disable-line func-names
|
||||||
if (user) this.where('userId', user.id);
|
if (user) this.where('userId', user.id);
|
||||||
else this.whereNull('userId');
|
else this.whereNull('userId');
|
||||||
})
|
})
|
||||||
|
@ -186,7 +187,7 @@ class uploadPOST extends Route {
|
||||||
hash: file.hash,
|
hash: file.hash,
|
||||||
ip: req.ip,
|
ip: req.ip,
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
editedAt: now
|
editedAt: now,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
insertedId = await db.table('files').insert({
|
insertedId = await db.table('files').insert({
|
||||||
|
@ -198,7 +199,7 @@ class uploadPOST extends Route {
|
||||||
hash: file.hash,
|
hash: file.hash,
|
||||||
ip: req.ip,
|
ip: req.ip,
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
editedAt: now
|
editedAt: now,
|
||||||
}, 'id');
|
}, 'id');
|
||||||
}
|
}
|
||||||
return insertedId;
|
return insertedId;
|
||||||
|
@ -220,6 +221,7 @@ class uploadPOST extends Route {
|
||||||
}
|
}
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
return keys;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const Route = require('../../structures/Route');
|
|
||||||
const randomstring = require('randomstring');
|
const randomstring = require('randomstring');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
const { dump } = require('dumper.js');
|
const { dump } = require('dumper.js');
|
||||||
|
const Route = require('../../structures/Route');
|
||||||
|
|
||||||
class apiKeyPOST extends Route {
|
class apiKeyPOST extends Route {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -17,7 +17,7 @@ class apiKeyPOST extends Route {
|
||||||
.where({ id: user.id })
|
.where({ id: user.id })
|
||||||
.update({
|
.update({
|
||||||
apiKey,
|
apiKey,
|
||||||
apiKeyEditedAt: now
|
apiKeyEditedAt: now,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
dump(error);
|
dump(error);
|
||||||
|
@ -26,7 +26,7 @@ class apiKeyPOST extends Route {
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
message: 'Successfully created new api key',
|
message: 'Successfully created new api key',
|
||||||
apiKey
|
apiKey,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const Route = require('../../structures/Route');
|
|
||||||
const log = require('../../utils/Log');
|
|
||||||
const bcrypt = require('bcrypt');
|
const bcrypt = require('bcrypt');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
|
const Route = require('../../structures/Route');
|
||||||
|
const log = require('../../utils/Log');
|
||||||
|
|
||||||
class changePasswordPOST extends Route {
|
class changePasswordPOST extends Route {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -36,7 +36,7 @@ class changePasswordPOST extends Route {
|
||||||
const now = moment.utc().toDate();
|
const now = moment.utc().toDate();
|
||||||
await db.table('users').where('id', user.id).update({
|
await db.table('users').where('id', user.id).update({
|
||||||
password: hash,
|
password: hash,
|
||||||
passwordEditedAt: now
|
passwordEditedAt: now,
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.json({ message: 'The password was changed successfully' });
|
return res.json({ message: 'The password was changed successfully' });
|
||||||
|
|
|
@ -12,8 +12,8 @@ class usersGET extends Route {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
username: user.username,
|
username: user.username,
|
||||||
isAdmin: user.isAdmin,
|
isAdmin: user.isAdmin,
|
||||||
apiKey: user.apiKey
|
apiKey: user.apiKey,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,8 @@ class verifyGET extends Route {
|
||||||
user: {
|
user: {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
username: user.username,
|
username: user.username,
|
||||||
isAdmin: user.isAdmin
|
isAdmin: user.isAdmin,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,8 @@ class Route {
|
||||||
if (banned) return res.status(401).json({ message: 'This IP has been banned from using the service.' });
|
if (banned) return res.status(401).json({ message: 'This IP has been banned from using the service.' });
|
||||||
|
|
||||||
if (this.options.bypassAuth) return this.run(req, res, db);
|
if (this.options.bypassAuth) return this.run(req, res, db);
|
||||||
// The only reason I call it token here and not Api Key is to be backwards compatible with the uploader and sharex
|
// The only reason I call it token here and not Api Key is to be backwards compatible
|
||||||
|
// with the uploader and sharex
|
||||||
// Small price to pay.
|
// Small price to pay.
|
||||||
if (req.headers.token) return this.authorizeApiKey(req, res, req.headers.token);
|
if (req.headers.token) return this.authorizeApiKey(req, res, req.headers.token);
|
||||||
if (!req.headers.authorization) return res.status(401).json({ message: 'No authorization header provided' });
|
if (!req.headers.authorization) return res.status(401).json({ message: 'No authorization header provided' });
|
||||||
|
@ -96,10 +97,7 @@ class Route {
|
||||||
return this.run(req, res, db, user);
|
return this.run(req, res, db, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
run(req, res, db) {
|
run() {}
|
||||||
// eslint-disable-line no-unused-vars
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
error(res, error) {
|
error(res, error) {
|
||||||
log.error(error);
|
log.error(error);
|
||||||
|
|
|
@ -11,6 +11,7 @@ const morgan = require('morgan');
|
||||||
const log = require('../utils/Log');
|
const log = require('../utils/Log');
|
||||||
const ThumbUtil = require('../utils/ThumbUtil');
|
const ThumbUtil = require('../utils/ThumbUtil');
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
const rateLimiter = new RateLimit({
|
const rateLimiter = new RateLimit({
|
||||||
windowMs: parseInt(process.env.RATE_LIMIT_WINDOW, 10),
|
windowMs: parseInt(process.env.RATE_LIMIT_WINDOW, 10),
|
||||||
max: parseInt(process.env.RATE_LIMIT_MAX, 10),
|
max: parseInt(process.env.RATE_LIMIT_MAX, 10),
|
||||||
|
|
|
@ -27,12 +27,6 @@ class Log {
|
||||||
else console.log(chalk.gray(args)); // eslint-disable-line no-console
|
else console.log(chalk.gray(args)); // eslint-disable-line no-console
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
static dump(args) {
|
|
||||||
dump(args);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
static checkIfArrayOrObject(thing) {
|
static checkIfArrayOrObject(thing) {
|
||||||
if (typeof thing === typeof [] || typeof thing === typeof {}) return true;
|
if (typeof thing === typeof [] || typeof thing === typeof {}) return true;
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -22,7 +22,9 @@ class ThumbUtil {
|
||||||
const output = `${filename.slice(0, -ext.length)}.png`;
|
const output = `${filename.slice(0, -ext.length)}.png`;
|
||||||
const previewOutput = `${filename.slice(0, -ext.length)}.webm`;
|
const previewOutput = `${filename.slice(0, -ext.length)}.webm`;
|
||||||
|
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
if (ThumbUtil.imageExtensions.includes(ext)) return ThumbUtil.generateThumbnailForImage(filename, output);
|
if (ThumbUtil.imageExtensions.includes(ext)) return ThumbUtil.generateThumbnailForImage(filename, output);
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
if (ThumbUtil.videoExtensions.includes(ext)) return ThumbUtil.generateThumbnailForVideo(filename, previewOutput);
|
if (ThumbUtil.videoExtensions.includes(ext)) return ThumbUtil.generateThumbnailForVideo(filename, previewOutput);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -75,17 +77,21 @@ class ThumbUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
static getFileThumbnail(filename) {
|
static getFileThumbnail(filename) {
|
||||||
// TODO: refactor so we don't do the same compare multiple times (poor cpu cycles)
|
|
||||||
if (!filename) return null;
|
if (!filename) return null;
|
||||||
const ext = path.extname(filename).toLowerCase();
|
const ext = path.extname(filename).toLowerCase();
|
||||||
if (!ThumbUtil.imageExtensions.includes(ext) && !ThumbUtil.videoExtensions.includes(ext)) return null;
|
|
||||||
if (ThumbUtil.imageExtensions.includes(ext)) return { thumb: `${filename.slice(0, -ext.length)}.png` };
|
const isImage = ThumbUtil.imageExtensions.includes(ext);
|
||||||
if (ThumbUtil.videoExtensions.includes(ext)) {
|
const isVideo = ThumbUtil.videoExtensions.includes(ext);
|
||||||
|
|
||||||
|
if (isImage) return { thumb: `${filename.slice(0, -ext.length)}.png` };
|
||||||
|
if (isVideo) {
|
||||||
return {
|
return {
|
||||||
thumb: `${filename.slice(0, -ext.length)}.png`,
|
thumb: `${filename.slice(0, -ext.length)}.png`,
|
||||||
preview: `${filename.slice(0, -ext.length)}.webm`,
|
preview: `${filename.slice(0, -ext.length)}.webm`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async removeThumbs({ thumb, preview }) {
|
static async removeThumbs({ thumb, preview }) {
|
||||||
|
|
|
@ -81,7 +81,7 @@ class Util {
|
||||||
/*
|
/*
|
||||||
It's funny but if you do i++ the asignment never gets done resulting in an infinite loop
|
It's funny but if you do i++ the asignment never gets done resulting in an infinite loop
|
||||||
*/
|
*/
|
||||||
if (i < 5) return retry(++i);
|
if (i < 5) return retry(i + 1);
|
||||||
log.error('Couldnt allocate identifier for album');
|
log.error('Couldnt allocate identifier for album');
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable no-bitwise */
|
||||||
const ffmpeg = require('fluent-ffmpeg');
|
const ffmpeg = require('fluent-ffmpeg');
|
||||||
const probe = require('ffmpeg-probe');
|
const probe = require('ffmpeg-probe');
|
||||||
|
|
||||||
|
@ -24,7 +25,7 @@ const getStartTime = (vDuration, fDuration, ignoreBeforePercent, ignoreAfterPerc
|
||||||
return getRandomInt(ignoreBeforePercent * safeVDuration, ignoreAfterPercent * safeVDuration);
|
return getRandomInt(ignoreBeforePercent * safeVDuration, ignoreAfterPercent * safeVDuration);
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = async opts => {
|
module.exports = async (opts) => {
|
||||||
const {
|
const {
|
||||||
log = noop,
|
log = noop,
|
||||||
|
|
||||||
|
@ -37,7 +38,7 @@ module.exports = async opts => {
|
||||||
|
|
||||||
fragmentDurationSecond = 3,
|
fragmentDurationSecond = 3,
|
||||||
ignoreBeforePercent = 0.25,
|
ignoreBeforePercent = 0.25,
|
||||||
ignoreAfterPercent = 0.75
|
ignoreAfterPercent = 0.75,
|
||||||
} = opts;
|
} = opts;
|
||||||
|
|
||||||
const info = await probe(input);
|
const info = await probe(input);
|
||||||
|
@ -77,7 +78,7 @@ module.exports = async opts => {
|
||||||
.outputOptions([`-t ${fragmentDurationSecond}`])
|
.outputOptions([`-t ${fragmentDurationSecond}`])
|
||||||
.noAudio()
|
.noAudio()
|
||||||
.output(output)
|
.output(output)
|
||||||
.on('start', cmd => log && log({ cmd }))
|
.on('start', (cmd) => log && log({ cmd }))
|
||||||
.on('end', resolve)
|
.on('end', resolve)
|
||||||
.on('error', reject)
|
.on('error', reject)
|
||||||
.run();
|
.run();
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
|
/* eslint-disable no-bitwise */
|
||||||
const ffmpeg = require('fluent-ffmpeg');
|
const ffmpeg = require('fluent-ffmpeg');
|
||||||
const probe = require('ffmpeg-probe');
|
const probe = require('ffmpeg-probe');
|
||||||
|
|
||||||
const noop = () => {};
|
const noop = () => {};
|
||||||
|
|
||||||
module.exports = async opts => {
|
module.exports = async (opts) => {
|
||||||
const {
|
const {
|
||||||
log = noop,
|
log = noop,
|
||||||
|
|
||||||
|
@ -15,13 +16,13 @@ module.exports = async opts => {
|
||||||
output,
|
output,
|
||||||
|
|
||||||
numFrames,
|
numFrames,
|
||||||
numFramesPercent = 0.05
|
numFramesPercent = 0.05,
|
||||||
} = opts;
|
} = opts;
|
||||||
|
|
||||||
const info = await probe(input);
|
const info = await probe(input);
|
||||||
// const numFramesTotal = parseInt(info.streams[0].nb_frames, 10);
|
// const numFramesTotal = parseInt(info.streams[0].nb_frames, 10);
|
||||||
const { avg_frame_rate: avgFrameRate, duration } = info.streams[0];
|
const { avg_frame_rate: avgFrameRate, duration } = info.streams[0];
|
||||||
const [frames, time] = avgFrameRate.split('/').map(e => parseInt(e, 10));
|
const [frames, time] = avgFrameRate.split('/').map((e) => parseInt(e, 10));
|
||||||
|
|
||||||
const numFramesTotal = (frames / time) * duration;
|
const numFramesTotal = (frames / time) * duration;
|
||||||
|
|
||||||
|
@ -31,7 +32,7 @@ module.exports = async opts => {
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
output,
|
output,
|
||||||
numFrames: numFramesToCapture
|
numFrames: numFramesToCapture,
|
||||||
};
|
};
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
|
@ -62,9 +63,9 @@ module.exports = async opts => {
|
||||||
.noAudio()
|
.noAudio()
|
||||||
.outputFormat('webm')
|
.outputFormat('webm')
|
||||||
.output(output)
|
.output(output)
|
||||||
.on('start', cmd => log && log({ cmd }))
|
.on('start', (cmd) => log && log({ cmd }))
|
||||||
.on('end', () => resolve())
|
.on('end', () => resolve())
|
||||||
.on('error', err => reject(err))
|
.on('error', (err) => reject(err))
|
||||||
.run();
|
.run();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -6,34 +6,43 @@
|
||||||
:data="details.links || []"
|
:data="details.links || []"
|
||||||
:mobile-cards="true">
|
:mobile-cards="true">
|
||||||
<template slot-scope="props">
|
<template slot-scope="props">
|
||||||
<b-table-column field="identifier"
|
<b-table-column
|
||||||
|
field="identifier"
|
||||||
label="Link"
|
label="Link"
|
||||||
centered>
|
centered>
|
||||||
<a :href="`${config.URL}/a/${props.row.identifier}`"
|
<a
|
||||||
|
:href="`${config.URL}/a/${props.row.identifier}`"
|
||||||
target="_blank">
|
target="_blank">
|
||||||
{{ props.row.identifier }}
|
{{ props.row.identifier }}
|
||||||
</a>
|
</a>
|
||||||
</b-table-column>
|
</b-table-column>
|
||||||
|
|
||||||
<b-table-column field="views"
|
<b-table-column
|
||||||
|
field="views"
|
||||||
label="Views"
|
label="Views"
|
||||||
centered>
|
centered>
|
||||||
{{ props.row.views }}
|
{{ props.row.views }}
|
||||||
</b-table-column>
|
</b-table-column>
|
||||||
|
|
||||||
<b-table-column field="enableDownload"
|
<b-table-column
|
||||||
|
field="enableDownload"
|
||||||
label="Allow download"
|
label="Allow download"
|
||||||
centered>
|
centered>
|
||||||
<b-switch v-model="props.row.enableDownload"
|
<b-switch
|
||||||
|
v-model="props.row.enableDownload"
|
||||||
@input="updateLinkOptions(albumId, props.row)" />
|
@input="updateLinkOptions(albumId, props.row)" />
|
||||||
</b-table-column>
|
</b-table-column>
|
||||||
|
|
||||||
<b-table-column field="enabled"
|
<b-table-column
|
||||||
|
field="enabled"
|
||||||
numeric>
|
numeric>
|
||||||
<button :class="{ 'is-loading': isDeleting(props.row.identifier) }"
|
<button
|
||||||
|
:class="{ 'is-loading': isDeleting(props.row.identifier) }"
|
||||||
class="button is-danger"
|
class="button is-danger"
|
||||||
:disabled="isDeleting(props.row.identifier)"
|
:disabled="isDeleting(props.row.identifier)"
|
||||||
@click="promptDeleteAlbumLink(albumId, props.row.identifier)">Delete link</button>
|
@click="promptDeleteAlbumLink(albumId, props.row.identifier)">
|
||||||
|
Delete link
|
||||||
|
</button>
|
||||||
</b-table-column>
|
</b-table-column>
|
||||||
</template>
|
</template>
|
||||||
<template slot="empty">
|
<template slot="empty">
|
||||||
|
@ -49,10 +58,13 @@
|
||||||
<div class="level is-paddingless">
|
<div class="level is-paddingless">
|
||||||
<div class="level-left">
|
<div class="level-left">
|
||||||
<div class="level-item">
|
<div class="level-item">
|
||||||
<button :class="{ 'is-loading': isCreatingLink }"
|
<button
|
||||||
|
:class="{ 'is-loading': isCreatingLink }"
|
||||||
class="button is-primary"
|
class="button is-primary"
|
||||||
style="float: left"
|
style="float: left"
|
||||||
@click="createLink(albumId)">Create new link</button>
|
@click="createLink(albumId)">
|
||||||
|
Create new link
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="level-item">
|
<div class="level-item">
|
||||||
<span class="has-text-default">{{ details.links.length }} / {{ config.maxLinksPerAlbum }} links created</span>
|
<span class="has-text-default">{{ details.links.length }} / {{ config.maxLinksPerAlbum }} links created</span>
|
||||||
|
@ -61,9 +73,12 @@
|
||||||
|
|
||||||
<div class="level-right">
|
<div class="level-right">
|
||||||
<div class="level-item">
|
<div class="level-item">
|
||||||
<button class="button is-danger"
|
<button
|
||||||
|
class="button is-danger"
|
||||||
style="float: right"
|
style="float: right"
|
||||||
@click="promptDeleteAlbum(albumId)">Delete album</button>
|
@click="promptDeleteAlbum(albumId)">
|
||||||
|
Delete album
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -79,18 +94,18 @@ export default {
|
||||||
props: {
|
props: {
|
||||||
albumId: {
|
albumId: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0
|
default: 0,
|
||||||
},
|
},
|
||||||
details: {
|
details: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => ({})
|
default: () => ({}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isCreatingLink: false,
|
isCreatingLink: false,
|
||||||
isDeletingLinks: [],
|
isDeletingLinks: [],
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
computed: mapState(['config']),
|
computed: mapState(['config']),
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -99,20 +114,20 @@ export default {
|
||||||
deleteAlbumLinkAction: 'albums/deleteLink',
|
deleteAlbumLinkAction: 'albums/deleteLink',
|
||||||
updateLinkOptionsAction: 'albums/updateLinkOptions',
|
updateLinkOptionsAction: 'albums/updateLinkOptions',
|
||||||
createLinkAction: 'albums/createLink',
|
createLinkAction: 'albums/createLink',
|
||||||
alert: 'alert/set'
|
alert: 'alert/set',
|
||||||
}),
|
}),
|
||||||
promptDeleteAlbum(id) {
|
promptDeleteAlbum(id) {
|
||||||
this.$buefy.dialog.confirm({
|
this.$buefy.dialog.confirm({
|
||||||
type: 'is-danger',
|
type: 'is-danger',
|
||||||
message: 'Are you sure you want to delete this album?',
|
message: 'Are you sure you want to delete this album?',
|
||||||
onConfirm: () => this.deleteAlbum(id)
|
onConfirm: () => this.deleteAlbum(id),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
promptDeleteAlbumLink(albumId, identifier) {
|
promptDeleteAlbumLink(albumId, identifier) {
|
||||||
this.$buefy.dialog.confirm({
|
this.$buefy.dialog.confirm({
|
||||||
type: 'is-danger',
|
type: 'is-danger',
|
||||||
message: 'Are you sure you want to delete this album link?',
|
message: 'Are you sure you want to delete this album link?',
|
||||||
onConfirm: () => this.deleteAlbumLink(albumId, identifier)
|
onConfirm: () => this.deleteAlbumLink(albumId, identifier),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async deleteAlbum(id) {
|
async deleteAlbum(id) {
|
||||||
|
@ -133,7 +148,7 @@ export default {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.alert({ text: e.message, error: true });
|
this.alert({ text: e.message, error: true });
|
||||||
} finally {
|
} finally {
|
||||||
this.isDeletingLinks = this.isDeletingLinks.filter(e => e !== identifier);
|
this.isDeletingLinks = this.isDeletingLinks.filter((e) => e !== identifier);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async createLink(albumId) {
|
async createLink(albumId) {
|
||||||
|
@ -159,8 +174,8 @@ export default {
|
||||||
},
|
},
|
||||||
isDeleting(identifier) {
|
isDeleting(identifier) {
|
||||||
return this.isDeletingLinks.indexOf(identifier) > -1;
|
return this.isDeletingLinks.indexOf(identifier) > -1;
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -184,7 +199,6 @@ export default {
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '~/assets/styles/_colors.scss';
|
@import '~/assets/styles/_colors.scss';
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="album">
|
<div class="album">
|
||||||
<div class="arrow-container"
|
<div
|
||||||
|
class="arrow-container"
|
||||||
@click="toggleDetails(album)">
|
@click="toggleDetails(album)">
|
||||||
<i :class="{ active: isExpanded }"
|
<i
|
||||||
|
:class="{ active: isExpanded }"
|
||||||
class="icon-arrow" />
|
class="icon-arrow" />
|
||||||
</div>
|
</div>
|
||||||
<div class="thumb">
|
<div class="thumb">
|
||||||
|
@ -12,7 +14,9 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<h4>
|
<h4>
|
||||||
<router-link :to="`/dashboard/albums/${album.id}`">{{ album.name }}</router-link>
|
<router-link :to="`/dashboard/albums/${album.id}`">
|
||||||
|
{{ album.name }}
|
||||||
|
</router-link>
|
||||||
</h4>
|
</h4>
|
||||||
<span>
|
<span>
|
||||||
Created <span class="is-inline has-text-weight-semibold"><timeago :since="album.createdAt" /></span>
|
Created <span class="is-inline has-text-weight-semibold"><timeago :since="album.createdAt" /></span>
|
||||||
|
@ -21,19 +25,24 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="latest is-hidden-mobile">
|
<div class="latest is-hidden-mobile">
|
||||||
<template v-if="album.fileCount > 0">
|
<template v-if="album.fileCount > 0">
|
||||||
<div v-for="file of album.files"
|
<div
|
||||||
|
v-for="file of album.files"
|
||||||
:key="file.id"
|
:key="file.id"
|
||||||
class="thumb">
|
class="thumb">
|
||||||
<figure class="image is-64x64">
|
<figure class="image is-64x64">
|
||||||
<a :href="file.url"
|
<a
|
||||||
|
:href="file.url"
|
||||||
target="_blank">
|
target="_blank">
|
||||||
<img :src="file.thumbSquare">
|
<img :src="file.thumbSquare">
|
||||||
</a>
|
</a>
|
||||||
</figure>
|
</figure>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="album.fileCount > 5"
|
<div
|
||||||
|
v-if="album.fileCount > 5"
|
||||||
class="thumb more no-background">
|
class="thumb more no-background">
|
||||||
<router-link :to="`/dashboard/albums/${album.id}`">{{ album.fileCount - 5 }}+ more</router-link>
|
<router-link :to="`/dashboard/albums/${album.id}`">
|
||||||
|
{{ album.fileCount - 5 }}+ more
|
||||||
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
|
@ -41,7 +50,8 @@
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AlbumDetails v-if="isExpanded"
|
<AlbumDetails
|
||||||
|
v-if="isExpanded"
|
||||||
:details="getDetails(album.id)"
|
:details="getDetails(album.id)"
|
||||||
:albumId="album.id" />
|
:albumId="album.id" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -53,22 +63,22 @@ import AlbumDetails from '~/components/album/AlbumDetails.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
AlbumDetails
|
AlbumDetails,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
album: {
|
album: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => ({})
|
default: () => ({}),
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
isExpandedGetter: 'albums/isExpanded',
|
isExpandedGetter: 'albums/isExpanded',
|
||||||
getDetails: 'albums/getDetails'
|
getDetails: 'albums/getDetails',
|
||||||
}),
|
}),
|
||||||
isExpanded() {
|
isExpanded() {
|
||||||
return this.isExpandedGetter(this.album.id);
|
return this.isExpandedGetter(this.album.id);
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async toggleDetails(album) {
|
async toggleDetails(album) {
|
||||||
|
@ -76,8 +86,8 @@ export default {
|
||||||
await this.$store.dispatch('albums/fetchDetails', album.id);
|
await this.$store.dispatch('albums/fetchDetails', album.id);
|
||||||
}
|
}
|
||||||
this.$store.commit('albums/toggleExpandedState', album.id);
|
this.$store.commit('albums/toggleExpandedState', album.id);
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -175,4 +185,3 @@ export default {
|
||||||
|
|
||||||
div.no-background { background: none !important; }
|
div.no-background { background: none !important; }
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
|
@ -163,7 +163,7 @@
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<div class="albums-container">
|
<div class="albums-container">
|
||||||
<div v-for="(album, index) in albums" :key="album.id" class="album">
|
<div v-for="album in albums" :key="album.id" class="album">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<b-checkbox
|
<b-checkbox
|
||||||
:value="isAlbumSelected(album.id)"
|
:value="isAlbumSelected(album.id)"
|
||||||
|
@ -252,7 +252,7 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
async search() {
|
async search() {
|
||||||
const data = await this.$search.do(this.searchTerm, ['name', 'original', 'type', 'albums:name']);
|
const data = await this.$search.do(this.searchTerm, ['name', 'original', 'type', 'albums:name']);
|
||||||
console.log('> Search result data', data);
|
console.log('> Search result data', data); // eslint-disable-line no-console
|
||||||
},
|
},
|
||||||
deleteFile(file) {
|
deleteFile(file) {
|
||||||
// this.$emit('delete', file);
|
// this.$emit('delete', file);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div :style="styles"
|
<div
|
||||||
|
:style="styles"
|
||||||
class="spinner spinner--cube-shadow" />
|
class="spinner spinner--cube-shadow" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -8,16 +9,16 @@ export default {
|
||||||
props: {
|
props: {
|
||||||
size: {
|
size: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '60px'
|
default: '60px',
|
||||||
},
|
},
|
||||||
background: {
|
background: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '#9C27B0'
|
default: '#9C27B0',
|
||||||
},
|
},
|
||||||
duration: {
|
duration: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '1.8s'
|
default: '1.8s',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
styles() {
|
styles() {
|
||||||
|
@ -25,10 +26,10 @@ export default {
|
||||||
width: this.size,
|
width: this.size,
|
||||||
height: this.size,
|
height: this.size,
|
||||||
backgroundColor: this.background,
|
backgroundColor: this.background,
|
||||||
animationDuration: this.duration
|
animationDuration: this.duration,
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<div :style="styles"
|
<div
|
||||||
|
:style="styles"
|
||||||
class="spinner spinner-origami">
|
class="spinner spinner-origami">
|
||||||
<div :style="innerStyles"
|
<div
|
||||||
|
:style="innerStyles"
|
||||||
class="spinner-inner loading">
|
class="spinner-inner loading">
|
||||||
<span class="slice" />
|
<span class="slice" />
|
||||||
<span class="slice" />
|
<span class="slice" />
|
||||||
|
@ -18,21 +20,21 @@ export default {
|
||||||
props: {
|
props: {
|
||||||
size: {
|
size: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '40px'
|
default: '40px',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
innerStyles() {
|
innerStyles() {
|
||||||
let size = parseInt(this.size);
|
const size = parseInt(this.size, 10);
|
||||||
return { transform: `scale(${(size / 60)})` };
|
return { transform: `scale(${(size / 60)})` };
|
||||||
},
|
},
|
||||||
styles() {
|
styles() {
|
||||||
return {
|
return {
|
||||||
width: this.size,
|
width: this.size,
|
||||||
height: this.size
|
height: this.size,
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
<template>
|
<template>
|
||||||
<div :style="styles"
|
<div
|
||||||
|
:style="styles"
|
||||||
class="spinner spinner--ping-pong">
|
class="spinner spinner--ping-pong">
|
||||||
<div :style="innerStyles"
|
<div
|
||||||
|
:style="innerStyles"
|
||||||
class="spinner-inner">
|
class="spinner-inner">
|
||||||
<div class="board">
|
<div class="board">
|
||||||
<div class="left"/>
|
<div class="left" />
|
||||||
<div class="right"/>
|
<div class="right" />
|
||||||
<div class="ball"/>
|
<div class="ball" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -17,22 +19,22 @@ export default {
|
||||||
props: {
|
props: {
|
||||||
size: {
|
size: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '60px'
|
default: '60px',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
innerStyles() {
|
innerStyles() {
|
||||||
let size = parseInt(this.size);
|
const size = parseInt(this.size, 10);
|
||||||
return { transform: `scale(${size / 250})` };
|
return { transform: `scale(${size / 250})` };
|
||||||
},
|
},
|
||||||
styles() {
|
styles() {
|
||||||
return {
|
return {
|
||||||
width: this.size,
|
width: this.size,
|
||||||
height: this.size
|
height: this.size,
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div :style="styles"
|
<div
|
||||||
|
:style="styles"
|
||||||
class="spinner spinner--rotate-square-2" />
|
class="spinner spinner--rotate-square-2" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -8,18 +9,18 @@ export default {
|
||||||
props: {
|
props: {
|
||||||
size: {
|
size: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '40px'
|
default: '40px',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
styles() {
|
styles() {
|
||||||
return {
|
return {
|
||||||
width: this.size,
|
width: this.size,
|
||||||
height: this.size,
|
height: this.size,
|
||||||
display: 'inline-block'
|
display: 'inline-block',
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -172,6 +172,7 @@ export default {
|
||||||
text: 'There was an error uploading this file. Check the console.',
|
text: 'There was an error uploading this file. Check the console.',
|
||||||
error: true,
|
error: true,
|
||||||
});
|
});
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.error(file, message, xhr);
|
console.error(file, message, xhr);
|
||||||
},
|
},
|
||||||
async dropzoneChunksUploaded(file, done) {
|
async dropzoneChunksUploaded(file, done) {
|
||||||
|
|
|
@ -1,27 +1,29 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-bar
|
<div
|
||||||
|
v-bar
|
||||||
class="scroll-area">
|
class="scroll-area">
|
||||||
<div class="default-body">
|
<div class="default-body">
|
||||||
<Navbar :isWhite="true" />
|
<Navbar :isWhite="true" />
|
||||||
<nuxt-child id="app"
|
<nuxt-child
|
||||||
|
id="app"
|
||||||
class="nuxt-app is-height-max-content" />
|
class="nuxt-app is-height-max-content" />
|
||||||
<Footer />
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import Navbar from '~/components/navbar/Navbar.vue';
|
|
||||||
import Footer from '~/components/footer/Footer';
|
|
||||||
import { mapState } from 'vuex';
|
import { mapState } from 'vuex';
|
||||||
|
import Navbar from '~/components/navbar/Navbar.vue';
|
||||||
|
import Footer from '~/components/footer/Footer.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Navbar,
|
Navbar,
|
||||||
Footer
|
Footer,
|
||||||
},
|
},
|
||||||
computed: mapState(['config']),
|
computed: mapState(['config']),
|
||||||
created() {
|
created() {
|
||||||
this.$store.watch(state => state.alert.text, () => {
|
this.$store.watch((state) => state.alert.text, () => {
|
||||||
const { text, error } = this.$store.state.alert;
|
const { text, error } = this.$store.state.alert;
|
||||||
|
|
||||||
if (!text) return;
|
if (!text) return;
|
||||||
|
@ -30,20 +32,21 @@ export default {
|
||||||
duration: 3500,
|
duration: 3500,
|
||||||
message: text,
|
message: text,
|
||||||
position: 'is-bottom',
|
position: 'is-bottom',
|
||||||
type: error ? 'is-danger' : 'is-success'
|
type: error ? 'is-danger' : 'is-success',
|
||||||
});
|
});
|
||||||
|
|
||||||
this.$store.dispatch('alert/clear', null);
|
this.$store.dispatch('alert/clear', null);
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log(
|
console.log(
|
||||||
`%c lolisafe %c v${this.config.version} %c`,
|
`%c lolisafe %c v${this.config.version} %c`,
|
||||||
'background:#35495e ; padding: 1px; border-radius: 3px 0 0 3px; color: #fff',
|
'background:#35495e ; padding: 1px; border-radius: 3px 0 0 3px; color: #fff',
|
||||||
'background:#ff015b; padding: 1px; border-radius: 0 3px 3px 0; color: #fff',
|
'background:#ff015b; padding: 1px; border-radius: 0 3px 3px 0; color: #fff',
|
||||||
'background:transparent'
|
'background:transparent',
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,6 @@
|
||||||
import Navbar from '~/components/navbar/Navbar.vue';
|
import Navbar from '~/components/navbar/Navbar.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { Navbar }
|
components: { Navbar },
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
export default function({ store, redirect }) {
|
export default function ({ store, redirect }) {
|
||||||
// If the user is not authenticated
|
// If the user is not authenticated
|
||||||
if (!store.state.auth.user) return redirect('/login');
|
if (!store.state.auth.user) return redirect('/login');
|
||||||
if (!store.state.auth.user.isAdmin) return redirect('/dashboard');
|
if (!store.state.auth.user.isAdmin) return redirect('/dashboard');
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
export default function({ store, redirect }) {
|
export default function ({ store, redirect }) {
|
||||||
// If the user is not authenticated
|
// If the user is not authenticated
|
||||||
if (!store.state.auth.loggedIn) {
|
if (!store.state.auth.loggedIn) {
|
||||||
return redirect('/login');
|
return redirect('/login');
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,74 +9,90 @@
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<h2 class="subtitle">File details</h2>
|
<h2 class="subtitle">
|
||||||
|
File details
|
||||||
|
</h2>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column is-6">
|
<div class="column is-6">
|
||||||
<b-field label="ID"
|
<b-field
|
||||||
|
label="ID"
|
||||||
horizontal>
|
horizontal>
|
||||||
<span>{{ file.id }}</span>
|
<span>{{ file.id }}</span>
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field label="Name"
|
<b-field
|
||||||
|
label="Name"
|
||||||
horizontal>
|
horizontal>
|
||||||
<span>{{ file.name }}</span>
|
<span>{{ file.name }}</span>
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field label="Original Name"
|
<b-field
|
||||||
|
label="Original Name"
|
||||||
horizontal>
|
horizontal>
|
||||||
<span>{{ file.original }}</span>
|
<span>{{ file.original }}</span>
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field label="IP"
|
<b-field
|
||||||
|
label="IP"
|
||||||
horizontal>
|
horizontal>
|
||||||
<span class="underline">{{ file.ip }}</span>
|
<span class="underline">{{ file.ip }}</span>
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field label="Link"
|
<b-field
|
||||||
|
label="Link"
|
||||||
horizontal>
|
horizontal>
|
||||||
<a :href="file.url"
|
<a
|
||||||
|
:href="file.url"
|
||||||
target="_blank">{{ file.url }}</a>
|
target="_blank">{{ file.url }}</a>
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field label="Size"
|
<b-field
|
||||||
|
label="Size"
|
||||||
horizontal>
|
horizontal>
|
||||||
<span>{{ formatBytes(file.size) }}</span>
|
<span>{{ formatBytes(file.size) }}</span>
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field label="Hash"
|
<b-field
|
||||||
|
label="Hash"
|
||||||
horizontal>
|
horizontal>
|
||||||
<span>{{ file.hash }}</span>
|
<span>{{ file.hash }}</span>
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field label="Uploaded"
|
<b-field
|
||||||
|
label="Uploaded"
|
||||||
horizontal>
|
horizontal>
|
||||||
<span><timeago :since="file.createdAt" /></span>
|
<span><timeago :since="file.createdAt" /></span>
|
||||||
</b-field>
|
</b-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-6">
|
<div class="column is-6">
|
||||||
<b-field label="User Id"
|
<b-field
|
||||||
|
label="User Id"
|
||||||
horizontal>
|
horizontal>
|
||||||
<span>{{ user.id }}</span>
|
<span>{{ user.id }}</span>
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field label="Username"
|
<b-field
|
||||||
|
label="Username"
|
||||||
horizontal>
|
horizontal>
|
||||||
<span>{{ user.username }}</span>
|
<span>{{ user.username }}</span>
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field label="Enabled"
|
<b-field
|
||||||
|
label="Enabled"
|
||||||
horizontal>
|
horizontal>
|
||||||
<span>{{ user.enabled }}</span>
|
<span>{{ user.enabled }}</span>
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field label="Registered"
|
<b-field
|
||||||
|
label="Registered"
|
||||||
horizontal>
|
horizontal>
|
||||||
<span><timeago :since="user.createdAt" /></span>
|
<span><timeago :since="user.createdAt" /></span>
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field label="Files"
|
<b-field
|
||||||
|
label="Files"
|
||||||
horizontal>
|
horizontal>
|
||||||
<span>
|
<span>
|
||||||
<nuxt-link :to="`/dashboard/admin/user/${user.id}`">{{ user.fileCount }}</nuxt-link>
|
<nuxt-link :to="`/dashboard/admin/user/${user.id}`">{{ user.fileCount }}</nuxt-link>
|
||||||
|
@ -86,10 +102,16 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb2 mt2 text-center">
|
<div class="mb2 mt2 text-center">
|
||||||
<button class="button is-danger"
|
<button
|
||||||
@click="promptBanIP">Ban IP</button>
|
class="button is-danger"
|
||||||
<button class="button is-danger"
|
@click="promptBanIP">
|
||||||
@click="promptDisableUser">Disable user</button>
|
Ban IP
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="button is-danger"
|
||||||
|
@click="promptDisableUser">
|
||||||
|
Disable user
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -102,14 +124,14 @@ import Sidebar from '~/components/sidebar/Sidebar.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Sidebar
|
Sidebar,
|
||||||
},
|
},
|
||||||
middleware: ['auth', 'admin'],
|
middleware: ['auth', 'admin'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
options: {},
|
options: {},
|
||||||
file: null,
|
file: null,
|
||||||
user: null
|
user: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async asyncData({ $axios, route }) {
|
async asyncData({ $axios, route }) {
|
||||||
|
@ -117,13 +139,13 @@ export default {
|
||||||
const response = await $axios.$get(`file/${route.params.id}`);
|
const response = await $axios.$get(`file/${route.params.id}`);
|
||||||
return {
|
return {
|
||||||
file: response.file ? response.file : null,
|
file: response.file ? response.file : null,
|
||||||
user: response.user ? response.user : null
|
user: response.user ? response.user : null,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
return {
|
return {
|
||||||
file: null,
|
file: null,
|
||||||
user: null
|
user: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -132,12 +154,12 @@ export default {
|
||||||
this.$buefy.dialog.confirm({
|
this.$buefy.dialog.confirm({
|
||||||
type: 'is-danger',
|
type: 'is-danger',
|
||||||
message: 'Are you sure you want to disable the account of the user that uploaded this file?',
|
message: 'Are you sure you want to disable the account of the user that uploaded this file?',
|
||||||
onConfirm: () => this.disableUser()
|
onConfirm: () => this.disableUser(),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async disableUser() {
|
async disableUser() {
|
||||||
const response = await this.$axios.$post('admin/users/disable', {
|
const response = await this.$axios.$post('admin/users/disable', {
|
||||||
id: this.user.id
|
id: this.user.id,
|
||||||
});
|
});
|
||||||
this.$buefy.toast.open(response.message);
|
this.$buefy.toast.open(response.message);
|
||||||
},
|
},
|
||||||
|
@ -145,12 +167,12 @@ export default {
|
||||||
this.$buefy.dialog.confirm({
|
this.$buefy.dialog.confirm({
|
||||||
type: 'is-danger',
|
type: 'is-danger',
|
||||||
message: 'Are you sure you want to ban the IP this file was uploaded from?',
|
message: 'Are you sure you want to ban the IP this file was uploaded from?',
|
||||||
onConfirm: () => this.banIP()
|
onConfirm: () => this.banIP(),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async banIP() {
|
async banIP() {
|
||||||
const response = await this.$axios.$post('admin/ban/ip', {
|
const response = await this.$axios.$post('admin/ban/ip', {
|
||||||
ip: this.file.ip
|
ip: this.file.ip,
|
||||||
});
|
});
|
||||||
this.$buefy.toast.open(response.message);
|
this.$buefy.toast.open(response.message);
|
||||||
},
|
},
|
||||||
|
@ -163,8 +185,8 @@ export default {
|
||||||
|
|
||||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
|
||||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
return `${parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}`;
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -6,87 +6,112 @@
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<h2 class="subtitle">Service settings</h2>
|
<h2 class="subtitle">
|
||||||
|
Service settings
|
||||||
|
</h2>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<b-field label="Service name"
|
<b-field
|
||||||
|
label="Service name"
|
||||||
message="Please enter the name which this service is gonna be identified as"
|
message="Please enter the name which this service is gonna be identified as"
|
||||||
horizontal>
|
horizontal>
|
||||||
<b-input v-model="options.serviceName"
|
<b-input
|
||||||
|
v-model="options.serviceName"
|
||||||
expanded />
|
expanded />
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field label="Upload folder"
|
<b-field
|
||||||
|
label="Upload folder"
|
||||||
message="Where to store the files relative to the working directory"
|
message="Where to store the files relative to the working directory"
|
||||||
horizontal>
|
horizontal>
|
||||||
<b-input v-model="options.uploadFolder"
|
<b-input
|
||||||
|
v-model="options.uploadFolder"
|
||||||
expanded />
|
expanded />
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field label="Links per album"
|
<b-field
|
||||||
|
label="Links per album"
|
||||||
message="Maximum links allowed per album"
|
message="Maximum links allowed per album"
|
||||||
horizontal>
|
horizontal>
|
||||||
<b-input v-model="options.linksPerAlbum"
|
<b-input
|
||||||
|
v-model="options.linksPerAlbum"
|
||||||
type="number"
|
type="number"
|
||||||
expanded />
|
expanded />
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field label="Max upload size"
|
<b-field
|
||||||
|
label="Max upload size"
|
||||||
message="Maximum allowed file size in MB"
|
message="Maximum allowed file size in MB"
|
||||||
horizontal>
|
horizontal>
|
||||||
<b-input v-model="options.maxUploadSize"
|
<b-input
|
||||||
|
v-model="options.maxUploadSize"
|
||||||
expanded />
|
expanded />
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field label="Filename length"
|
<b-field
|
||||||
|
label="Filename length"
|
||||||
message="How many characters long should the generated filenames be"
|
message="How many characters long should the generated filenames be"
|
||||||
horizontal>
|
horizontal>
|
||||||
<b-input v-model="options.filenameLength"
|
<b-input
|
||||||
|
v-model="options.filenameLength"
|
||||||
expanded />
|
expanded />
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field label="Album link length"
|
<b-field
|
||||||
|
label="Album link length"
|
||||||
message="How many characters a link for an album should have"
|
message="How many characters a link for an album should have"
|
||||||
horizontal>
|
horizontal>
|
||||||
<b-input v-model="options.albumLinkLength"
|
<b-input
|
||||||
|
v-model="options.albumLinkLength"
|
||||||
expanded />
|
expanded />
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field label="Generate thumbnails"
|
<b-field
|
||||||
|
label="Generate thumbnails"
|
||||||
message="Generate thumbnails when uploading a file if possible"
|
message="Generate thumbnails when uploading a file if possible"
|
||||||
horizontal>
|
horizontal>
|
||||||
<b-switch v-model="options.generateThumbnails"
|
<b-switch
|
||||||
|
v-model="options.generateThumbnails"
|
||||||
:true-value="true"
|
:true-value="true"
|
||||||
:false-value="false" />
|
:false-value="false" />
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field label="Generate zips"
|
<b-field
|
||||||
|
label="Generate zips"
|
||||||
message="Allow generating zips to download entire albums"
|
message="Allow generating zips to download entire albums"
|
||||||
horizontal>
|
horizontal>
|
||||||
<b-switch v-model="options.generateZips"
|
<b-switch
|
||||||
|
v-model="options.generateZips"
|
||||||
:true-value="true"
|
:true-value="true"
|
||||||
:false-value="false" />
|
:false-value="false" />
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field label="Public mode"
|
<b-field
|
||||||
|
label="Public mode"
|
||||||
message="Enable anonymous uploades"
|
message="Enable anonymous uploades"
|
||||||
horizontal>
|
horizontal>
|
||||||
<b-switch v-model="options.publicMode"
|
<b-switch
|
||||||
|
v-model="options.publicMode"
|
||||||
:true-value="true"
|
:true-value="true"
|
||||||
:false-value="false" />
|
:false-value="false" />
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field label="Enable creating account"
|
<b-field
|
||||||
|
label="Enable creating account"
|
||||||
message="Enable creating new accounts in the platform"
|
message="Enable creating new accounts in the platform"
|
||||||
horizontal>
|
horizontal>
|
||||||
<b-switch v-model="options.enableAccounts"
|
<b-switch
|
||||||
|
v-model="options.enableAccounts"
|
||||||
:true-value="true"
|
:true-value="true"
|
||||||
:false-value="false" />
|
:false-value="false" />
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<div class="mb2 mt2 text-center">
|
<div class="mb2 mt2 text-center">
|
||||||
<button class="button is-primary"
|
<button
|
||||||
@click="promptRestartService">Save and restart service</button>
|
class="button is-primary"
|
||||||
|
@click="promptRestartService">
|
||||||
|
Save and restart service
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -99,12 +124,12 @@ import Sidebar from '~/components/sidebar/Sidebar.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Sidebar
|
Sidebar,
|
||||||
},
|
},
|
||||||
middleware: ['auth', 'admin'],
|
middleware: ['auth', 'admin'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
options: {}
|
options: {},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
metaInfo() {
|
metaInfo() {
|
||||||
|
@ -115,19 +140,19 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async getSettings() {
|
async getSettings() {
|
||||||
const response = await this.$axios.$get(`service/config`);
|
const response = await this.$axios.$get('service/config');
|
||||||
this.options = response.config;
|
this.options = response.config;
|
||||||
},
|
},
|
||||||
promptRestartService() {
|
promptRestartService() {
|
||||||
this.$buefy.dialog.confirm({
|
this.$buefy.dialog.confirm({
|
||||||
message: 'Keep in mind that restarting only works if you have PM2 or something similar set up. Continue?',
|
message: 'Keep in mind that restarting only works if you have PM2 or something similar set up. Continue?',
|
||||||
onConfirm: () => this.restartService()
|
onConfirm: () => this.restartService(),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async restartService() {
|
async restartService() {
|
||||||
const response = await this.$axios.$post(`service/restart`);
|
const response = await this.$axios.$post('service/restart');
|
||||||
this.$buefy.toast.open(response.message);
|
this.$buefy.toast.open(response.message);
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -123,7 +123,6 @@
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="section is-fullheight dashboard">
|
<section class="section is-fullheight dashboard">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@ -132,27 +131,35 @@
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<h2 class="subtitle">Manage your tags</h2>
|
<h2 class="subtitle">
|
||||||
|
Manage your tags
|
||||||
|
</h2>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<div class="search-container">
|
<div class="search-container">
|
||||||
<b-field>
|
<b-field>
|
||||||
<b-input v-model="newTagName"
|
<b-input
|
||||||
|
v-model="newTagName"
|
||||||
placeholder="Tag name..."
|
placeholder="Tag name..."
|
||||||
type="text"
|
type="text"
|
||||||
@keyup.enter.native="createTag" />
|
@keyup.enter.native="createTag" />
|
||||||
<p class="control">
|
<p class="control">
|
||||||
<button class="button is-primary"
|
<button
|
||||||
@click="createTag">Create tags</button>
|
class="button is-primary"
|
||||||
|
@click="createTag">
|
||||||
|
Create tags
|
||||||
|
</button>
|
||||||
</p>
|
</p>
|
||||||
</b-field>
|
</b-field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="view-container">
|
<div class="view-container">
|
||||||
<div v-for="tag in tags"
|
<div
|
||||||
|
v-for="tag in tags"
|
||||||
:key="tag.id"
|
:key="tag.id"
|
||||||
class="album">
|
class="album">
|
||||||
<div class="arrow-container"
|
<div
|
||||||
|
class="arrow-container"
|
||||||
@click="promptDeleteTag">
|
@click="promptDeleteTag">
|
||||||
<i class="icon-arrow" />
|
<i class="icon-arrow" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -165,7 +172,9 @@
|
||||||
-->
|
-->
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<h4>
|
<h4>
|
||||||
<router-link :to="`/dashboard/tags/${tag.id}`">{{ tag.name }}</router-link>
|
<router-link :to="`/dashboard/tags/${tag.id}`">
|
||||||
|
{{ tag.name }}
|
||||||
|
</router-link>
|
||||||
</h4>
|
</h4>
|
||||||
<span>{{ tag.count || 0 }} files</span>
|
<span>{{ tag.count || 0 }} files</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -205,19 +214,19 @@ import Sidebar from '~/components/sidebar/Sidebar.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Sidebar
|
Sidebar,
|
||||||
},
|
},
|
||||||
middleware: 'auth',
|
middleware: 'auth',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
tags: [],
|
tags: [],
|
||||||
newTagName: null
|
newTagName: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
config() {
|
config() {
|
||||||
return this.$store.state.config;
|
return this.$store.state.config;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
metaInfo() {
|
metaInfo() {
|
||||||
return { title: 'Tags' };
|
return { title: 'Tags' };
|
||||||
|
@ -230,7 +239,7 @@ export default {
|
||||||
this.$buefy.dialog.confirm({
|
this.$buefy.dialog.confirm({
|
||||||
type: 'is-danger',
|
type: 'is-danger',
|
||||||
message: 'Are you sure you want to delete this tag?',
|
message: 'Are you sure you want to delete this tag?',
|
||||||
onConfirm: () => this.promptPurgeTag(id)
|
onConfirm: () => this.promptPurgeTag(id),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
promptPurgeTag(id) {
|
promptPurgeTag(id) {
|
||||||
|
@ -240,7 +249,7 @@ export default {
|
||||||
cancelText: 'No',
|
cancelText: 'No',
|
||||||
confirmText: 'Yes',
|
confirmText: 'Yes',
|
||||||
onConfirm: () => this.deleteTag(id, true),
|
onConfirm: () => this.deleteTag(id, true),
|
||||||
onCancel: () => this.deleteTag(id, false)
|
onCancel: () => this.deleteTag(id, false),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async deleteTag(id, purge) {
|
async deleteTag(id, purge) {
|
||||||
|
@ -250,19 +259,19 @@ export default {
|
||||||
},
|
},
|
||||||
async createTag() {
|
async createTag() {
|
||||||
if (!this.newTagName || this.newTagName === '') return;
|
if (!this.newTagName || this.newTagName === '') return;
|
||||||
const response = await this.$axios.$post(`tag/new`,
|
const response = await this.$axios.$post('tag/new',
|
||||||
{ name: this.newTagName });
|
{ name: this.newTagName });
|
||||||
this.newTagName = null;
|
this.newTagName = null;
|
||||||
this.$buefy.toast.open(response.message);
|
this.$buefy.toast.open(response.message);
|
||||||
this.getTags();
|
this.getTags();
|
||||||
},
|
},
|
||||||
async getTags() {
|
async getTags() {
|
||||||
const response = await this.$axios.$get(`tags`);
|
const response = await this.$axios.$get('tags');
|
||||||
for (const tag of response.tags) {
|
for (const tag of response.tags) {
|
||||||
tag.isDetailsOpen = false;
|
tag.isDetailsOpen = false;
|
||||||
}
|
}
|
||||||
this.tags = response.tags;
|
this.tags = response.tags;
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,34 +1,45 @@
|
||||||
<template>
|
<template>
|
||||||
|
<!-- eslint-disable max-len -->
|
||||||
<div class="container has-text-left">
|
<div class="container has-text-left">
|
||||||
<h2 class="subtitle">What is lolisafe?</h2>
|
<h2 class="subtitle">
|
||||||
|
What is lolisafe?
|
||||||
|
</h2>
|
||||||
<article class="message">
|
<article class="message">
|
||||||
<div class="message-body">
|
<div class="message-body">
|
||||||
lolisafe is an easy to use, open source and completely free file upload service. We accept your files, photos, documents, anything, and give you back a shareable link for you to send to others.
|
lolisafe is an easy to use, open source and completely free file upload service. We accept your files, photos, documents, anything, and give you back a shareable link for you to send to others.
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<h2 class="subtitle">Can I run my own lolisafe?</h2>
|
<h2 class="subtitle">
|
||||||
|
Can I run my own lolisafe?
|
||||||
|
</h2>
|
||||||
<article class="message">
|
<article class="message">
|
||||||
<div class="message-body">
|
<div class="message-body">
|
||||||
Definitely. Head to <a target="_blank" href="https://github.com/WeebDev/lolisafe">our GitHub repo</a> and follow the instructions to clone, build and deploy it by yourself. It's super easy too!
|
Definitely. Head to <a target="_blank" href="https://github.com/WeebDev/lolisafe">our GitHub repo</a> and follow the instructions to clone, build and deploy it by yourself. It's super easy too!
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<h2 class="subtitle">How can I keep track of my uploads?</h2>
|
<h2 class="subtitle">
|
||||||
|
How can I keep track of my uploads?
|
||||||
|
</h2>
|
||||||
<article class="message">
|
<article class="message">
|
||||||
<div class="message-body">
|
<div class="message-body">
|
||||||
Simply create a user on the site and every upload will be associated with your account, granting you access to your uploaded files through our dashboard.
|
Simply create a user on the site and every upload will be associated with your account, granting you access to your uploaded files through our dashboard.
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<h2 class="subtitle">What are albums?</h2>
|
<h2 class="subtitle">
|
||||||
|
What are albums?
|
||||||
|
</h2>
|
||||||
<article class="message">
|
<article class="message">
|
||||||
<div class="message-body">
|
<div class="message-body">
|
||||||
Albums are a simple way of sorting uploads together. Right now you can create albums through the dashboard and use them only with <a target="_blank" href="https://chrome.google.com/webstore/detail/loli-safe-uploader/enkkmplljfjppcdaancckgilmgoiofnj">our chrome extension</a> which will enable you to <strong>right click -> send to lolisafe</strong> or to a desired album if you have any.
|
Albums are a simple way of sorting uploads together. Right now you can create albums through the dashboard and use them only with <a target="_blank" href="https://chrome.google.com/webstore/detail/loli-safe-uploader/enkkmplljfjppcdaancckgilmgoiofnj">our chrome extension</a> which will enable you to <strong>right click -> send to lolisafe</strong> or to a desired album if you have any.
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<h2 class="subtitle">Why should I use this?</h2>
|
<h2 class="subtitle">
|
||||||
|
Why should I use this?
|
||||||
|
</h2>
|
||||||
<article class="message">
|
<article class="message">
|
||||||
<div class="message-body">
|
<div class="message-body">
|
||||||
There are too many file upload services out there, and a lot of them rely on the foundations of pomf which is ancient. In a desperate and unsuccessful attempt of finding a good file uploader that's easily extendable, lolisafe was born. We give you control over your files, we give you a way to sort your uploads into albums for ease of access and we give you an api to use with ShareX or any other thing that let's you make POST requests.
|
There are too many file upload services out there, and a lot of them rely on the foundations of pomf which is ancient. In a desperate and unsuccessful attempt of finding a good file uploader that's easily extendable, lolisafe was born. We give you control over your files, we give you a way to sort your uploads into albums for ease of access and we give you an api to use with ShareX or any other thing that let's you make POST requests.
|
||||||
|
@ -45,7 +56,7 @@ export default {
|
||||||
},
|
},
|
||||||
metaInfo() {
|
metaInfo() {
|
||||||
return { title: 'Faq' };
|
return { title: 'Faq' };
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
<div class="content-wrapper">
|
<div class="content-wrapper">
|
||||||
<h4>Blazing fast file uploader. <br>For real.</h4>
|
<h4>Blazing fast file uploader. <br>For real.</h4>
|
||||||
<p>
|
<p>
|
||||||
|
<!-- eslint-disable-next-line max-len -->
|
||||||
A <strong>modern</strong> and <strong>self-hosted</strong> file upload service that can handle anything you throw at it. Fast uploads, file manager and sharing capabilities all crafted with a beautiful user experience in mind.
|
A <strong>modern</strong> and <strong>self-hosted</strong> file upload service that can handle anything you throw at it. Fast uploads, file manager and sharing capabilities all crafted with a beautiful user experience in mind.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,18 +10,21 @@
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column is-4 is-offset-4">
|
<div class="column is-4 is-offset-4">
|
||||||
<b-field>
|
<b-field>
|
||||||
<b-input v-model="username"
|
<b-input
|
||||||
|
v-model="username"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Username" />
|
placeholder="Username" />
|
||||||
</b-field>
|
</b-field>
|
||||||
<b-field>
|
<b-field>
|
||||||
<b-input v-model="password"
|
<b-input
|
||||||
|
v-model="password"
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="Password"
|
placeholder="Password"
|
||||||
password-reveal />
|
password-reveal />
|
||||||
</b-field>
|
</b-field>
|
||||||
<b-field>
|
<b-field>
|
||||||
<b-input v-model="rePassword"
|
<b-input
|
||||||
|
v-model="rePassword"
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="Re-type Password"
|
placeholder="Re-type Password"
|
||||||
password-reveal
|
password-reveal
|
||||||
|
@ -29,11 +32,17 @@
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<p class="control has-addons is-pulled-right">
|
<p class="control has-addons is-pulled-right">
|
||||||
<router-link to="/login"
|
<router-link
|
||||||
class="is-text">Already have an account?</router-link>
|
to="/login"
|
||||||
<button class="button is-primary big ml1"
|
class="is-text">
|
||||||
|
Already have an account?
|
||||||
|
</router-link>
|
||||||
|
<button
|
||||||
|
class="button is-primary big ml1"
|
||||||
:disabled="isLoading"
|
:disabled="isLoading"
|
||||||
@click="register">Register</button>
|
@click="register">
|
||||||
|
Register
|
||||||
|
</button>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -51,7 +60,7 @@ export default {
|
||||||
username: null,
|
username: null,
|
||||||
password: null,
|
password: null,
|
||||||
rePassword: null,
|
rePassword: null,
|
||||||
isLoading: false
|
isLoading: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: mapState(['config', 'auth']),
|
computed: mapState(['config', 'auth']),
|
||||||
|
@ -64,23 +73,23 @@ export default {
|
||||||
if (!this.username || !this.password || !this.rePassword) {
|
if (!this.username || !this.password || !this.rePassword) {
|
||||||
this.$store.dispatch('alert', {
|
this.$store.dispatch('alert', {
|
||||||
text: 'Please fill all fields before attempting to register.',
|
text: 'Please fill all fields before attempting to register.',
|
||||||
error: true
|
error: true,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.password !== this.rePassword) {
|
if (this.password !== this.rePassword) {
|
||||||
this.$store.dispatch('alert', {
|
this.$store.dispatch('alert', {
|
||||||
text: "Passwords don't match",
|
text: "Passwords don't match",
|
||||||
error: true
|
error: true,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.$axios.$post(`auth/register`, {
|
const response = await this.$axios.$post('auth/register', {
|
||||||
username: this.username,
|
username: this.username,
|
||||||
password: this.password
|
password: this.password,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.$store.dispatch('alert', { text: response.message });
|
this.$store.dispatch('alert', { text: response.message });
|
||||||
|
@ -90,7 +99,7 @@ export default {
|
||||||
} finally {
|
} finally {
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,25 +1,25 @@
|
||||||
export default function({ $axios, store }) {
|
export default function ({ $axios, store }) {
|
||||||
$axios.setHeader('accept', 'application/vnd.lolisafe.json');
|
$axios.setHeader('accept', 'application/vnd.lolisafe.json');
|
||||||
|
|
||||||
$axios.onRequest(config => {
|
$axios.onRequest((config) => {
|
||||||
if (store.state.auth.token) {
|
if (store.state.auth.token) {
|
||||||
config.headers.common['Authorization'] = `bearer ${store.state.auth.token}`;
|
config.headers.common.Authorization = `bearer ${store.state.auth.token}`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$axios.onError(error => {
|
$axios.onError((error) => {
|
||||||
if (process.env.NODE_ENV !== 'production') console.error('[AXIOS Error]', error);
|
if (process.env.NODE_ENV !== 'production') console.error('[AXIOS Error]', error);
|
||||||
if (process.browser) {
|
if (process.browser) {
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
if (error.response?.data?.message) {
|
if (error.response?.data?.message) {
|
||||||
store.dispatch('alert/set', {
|
store.dispatch('alert/set', {
|
||||||
text: error.response.data.message,
|
text: error.response.data.message,
|
||||||
error: true
|
error: true,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
store.dispatch('alert/set', {
|
store.dispatch('alert/set', {
|
||||||
text: `[AXIOS]: ${error.message}`,
|
text: `[AXIOS]: ${error.message}`,
|
||||||
error: true
|
error: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import FlexSearch from 'flexsearch';
|
import FlexSearch from 'flexsearch';
|
||||||
|
|
||||||
const search = new FlexSearch('speed');
|
const search = new FlexSearch('speed');
|
||||||
|
|
||||||
// https://github.com/nextapps-de/flexsearch
|
// https://github.com/nextapps-de/flexsearch
|
||||||
|
|
||||||
Vue.prototype.$search = {
|
Vue.prototype.$search = {
|
||||||
items: async items => {
|
items: async (items) => {
|
||||||
await search.clear();
|
await search.clear();
|
||||||
await search.add(items);
|
await search.add(items);
|
||||||
},
|
},
|
||||||
do: async (term, field) => {
|
do: async (term, field) => {
|
||||||
const results = await search.search(term, { field });
|
const results = await search.search(term, { field });
|
||||||
return results;
|
return results;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
export default async ctx => {
|
export default async (ctx) => {
|
||||||
await ctx.store.dispatch('nuxtClientInit', ctx);
|
await ctx.store.dispatch('nuxtClientInit', ctx);
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,5 +3,5 @@ import VueIsYourPasswordSafe from 'vue-isyourpasswordsafe';
|
||||||
|
|
||||||
Vue.use(VueIsYourPasswordSafe, {
|
Vue.use(VueIsYourPasswordSafe, {
|
||||||
minLength: 6,
|
minLength: 6,
|
||||||
maxLength: 64
|
maxLength: 64,
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,5 +4,5 @@ import VueTimeago from 'vue-timeago';
|
||||||
Vue.use(VueTimeago, {
|
Vue.use(VueTimeago, {
|
||||||
name: 'timeago',
|
name: 'timeago',
|
||||||
locale: 'en-US',
|
locale: 'en-US',
|
||||||
locales: { 'en-US': require('vue-timeago/locales/en-US.json') }
|
locales: { 'en-US': require('vue-timeago/locales/en-US.json') },
|
||||||
});
|
});
|
||||||
|
|
|
@ -49,6 +49,8 @@ export const actions = {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dispatch('alert/set', { text: e.message, error: true }, { root: true });
|
dispatch('alert/set', { text: e.message, error: true }, { root: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
},
|
},
|
||||||
async requestAPIKey({ commit, dispatch }) {
|
async requestAPIKey({ commit, dispatch }) {
|
||||||
try {
|
try {
|
||||||
|
@ -59,6 +61,8 @@ export const actions = {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dispatch('alert/set', { text: e.message, error: true }, { root: true });
|
dispatch('alert/set', { text: e.message, error: true }, { root: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
},
|
},
|
||||||
logout({ commit }) {
|
logout({ commit }) {
|
||||||
commit('logout');
|
commit('logout');
|
||||||
|
|
59
yarn.lock
59
yarn.lock
|
@ -875,6 +875,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.5.tgz#b32366c89b43c6f8cefbdefac778b9c828e3ba8c"
|
resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.5.tgz#b32366c89b43c6f8cefbdefac778b9c828e3ba8c"
|
||||||
integrity sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==
|
integrity sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==
|
||||||
|
|
||||||
|
"@mdi/font@^5.3.45":
|
||||||
|
version "5.3.45"
|
||||||
|
resolved "https://registry.yarnpkg.com/@mdi/font/-/font-5.3.45.tgz#086d3ef77dee260c04dd5a593af602c250e5b315"
|
||||||
|
integrity sha512-SD5d2vHEKRvDCInZQFXOwiFpBlzpuZOiqwxKf6E+zCt7UDc52TUSrL0+TXqY57VQh/SnTpZVXM+Uvs21OdPFWg==
|
||||||
|
|
||||||
"@nodelib/fs.scandir@2.1.3":
|
"@nodelib/fs.scandir@2.1.3":
|
||||||
version "2.1.3"
|
version "2.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b"
|
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b"
|
||||||
|
@ -1967,6 +1972,13 @@ base@^0.11.1:
|
||||||
mixin-deep "^1.2.0"
|
mixin-deep "^1.2.0"
|
||||||
pascalcase "^0.1.1"
|
pascalcase "^0.1.1"
|
||||||
|
|
||||||
|
basic-auth@~2.0.1:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a"
|
||||||
|
integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==
|
||||||
|
dependencies:
|
||||||
|
safe-buffer "5.1.2"
|
||||||
|
|
||||||
bcrypt-pbkdf@^1.0.0:
|
bcrypt-pbkdf@^1.0.0:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
|
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
|
||||||
|
@ -3460,7 +3472,7 @@ delegates@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
|
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
|
||||||
integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
|
integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
|
||||||
|
|
||||||
depd@2.0.0:
|
depd@2.0.0, depd@~2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
|
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
|
||||||
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
|
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
|
||||||
|
@ -3478,6 +3490,11 @@ des.js@^1.0.0:
|
||||||
inherits "^2.0.1"
|
inherits "^2.0.1"
|
||||||
minimalistic-assert "^1.0.0"
|
minimalistic-assert "^1.0.0"
|
||||||
|
|
||||||
|
desandro-matches-selector@^2.0.0:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/desandro-matches-selector/-/desandro-matches-selector-2.0.2.tgz#717beed4dc13e7d8f3762f707a6d58a6774218e1"
|
||||||
|
integrity sha1-cXvu1NwT59jzdi9wem1YpndCGOE=
|
||||||
|
|
||||||
destroy@^1.0.4, destroy@~1.0.4:
|
destroy@^1.0.4, destroy@~1.0.4:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
|
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
|
||||||
|
@ -4608,6 +4625,13 @@ fined@^1.0.1:
|
||||||
object.pick "^1.2.0"
|
object.pick "^1.2.0"
|
||||||
parse-filepath "^1.0.1"
|
parse-filepath "^1.0.1"
|
||||||
|
|
||||||
|
fizzy-ui-utils@^2.0.0:
|
||||||
|
version "2.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/fizzy-ui-utils/-/fizzy-ui-utils-2.0.7.tgz#7df45dcc4eb374a08b65d39bb9a4beedf7330505"
|
||||||
|
integrity sha512-CZXDVXQ1If3/r8s0T+v+qVeMshhfcuq0rqIFgJnrtd+Bu8GmDmqMjntjUePypVtjHXKJ6V4sw9zeyox34n9aCg==
|
||||||
|
dependencies:
|
||||||
|
desandro-matches-selector "^2.0.0"
|
||||||
|
|
||||||
flagged-respawn@^1.0.0:
|
flagged-respawn@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-1.0.1.tgz#e7de6f1279ddd9ca9aac8a5971d618606b3aab41"
|
resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-1.0.1.tgz#e7de6f1279ddd9ca9aac8a5971d618606b3aab41"
|
||||||
|
@ -4843,6 +4867,11 @@ get-caller-file@^1.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
|
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
|
||||||
integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==
|
integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==
|
||||||
|
|
||||||
|
get-size@^2.0.2:
|
||||||
|
version "2.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/get-size/-/get-size-2.0.3.tgz#54a1d0256b20ea7ac646516756202769941ad2ef"
|
||||||
|
integrity sha512-lXNzT/h/dTjTxRbm9BXb+SGxxzkm97h/PCIKtlN/CBCxxmkkIVV21udumMS93MuVTDX583gqc94v3RjuHmI+2Q==
|
||||||
|
|
||||||
get-stdin@^4.0.1:
|
get-stdin@^4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
|
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
|
||||||
|
@ -6581,6 +6610,14 @@ markdown-escapes@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535"
|
resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535"
|
||||||
integrity sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==
|
integrity sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==
|
||||||
|
|
||||||
|
masonry-layout@^4.2.2:
|
||||||
|
version "4.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/masonry-layout/-/masonry-layout-4.2.2.tgz#d57b44af13e601bfcdc423f1dd8348b5524de348"
|
||||||
|
integrity sha512-iGtAlrpHNyxaR19CvKC3npnEcAwszXoyJiI8ARV2ePi7fmYhIud25MHK8Zx4P0LCC4d3TNO9+rFa1KoK1OEOaA==
|
||||||
|
dependencies:
|
||||||
|
get-size "^2.0.2"
|
||||||
|
outlayer "^2.1.0"
|
||||||
|
|
||||||
md5.js@^1.3.4:
|
md5.js@^1.3.4:
|
||||||
version "1.3.5"
|
version "1.3.5"
|
||||||
resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
|
resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
|
||||||
|
@ -6868,6 +6905,17 @@ moment@^2.24.0:
|
||||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
|
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
|
||||||
integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
|
integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
|
||||||
|
|
||||||
|
morgan@^1.10.0:
|
||||||
|
version "1.10.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.10.0.tgz#091778abc1fc47cd3509824653dae1faab6b17d7"
|
||||||
|
integrity sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==
|
||||||
|
dependencies:
|
||||||
|
basic-auth "~2.0.1"
|
||||||
|
debug "2.6.9"
|
||||||
|
depd "~2.0.0"
|
||||||
|
on-finished "~2.3.0"
|
||||||
|
on-headers "~1.0.2"
|
||||||
|
|
||||||
move-concurrently@^1.0.1:
|
move-concurrently@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
|
resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
|
||||||
|
@ -7515,6 +7563,15 @@ outdent@0.7.1:
|
||||||
resolved "https://registry.yarnpkg.com/outdent/-/outdent-0.7.1.tgz#e9b400443622a97760b0bc74fa3223252ccd02a2"
|
resolved "https://registry.yarnpkg.com/outdent/-/outdent-0.7.1.tgz#e9b400443622a97760b0bc74fa3223252ccd02a2"
|
||||||
integrity sha512-VjIzdUHunL74DdhcwMDt5FhNDQ8NYmTkuW0B+usIV2afS9aWT/1c9z1TsnFW349TP3nxmYeUl7Z++XpJRByvgg==
|
integrity sha512-VjIzdUHunL74DdhcwMDt5FhNDQ8NYmTkuW0B+usIV2afS9aWT/1c9z1TsnFW349TP3nxmYeUl7Z++XpJRByvgg==
|
||||||
|
|
||||||
|
outlayer@^2.1.0:
|
||||||
|
version "2.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/outlayer/-/outlayer-2.1.1.tgz#29863b6de10ea5dadfffcadfa0d728907387e9a2"
|
||||||
|
integrity sha1-KYY7beEOpdrf/8rfoNcokHOH6aI=
|
||||||
|
dependencies:
|
||||||
|
ev-emitter "^1.0.0"
|
||||||
|
fizzy-ui-utils "^2.0.0"
|
||||||
|
get-size "^2.0.2"
|
||||||
|
|
||||||
p-defer@^1.0.0:
|
p-defer@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c"
|
resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c"
|
||||||
|
|
Loading…
Reference in New Issue