Compare commits

...

5 Commits

Author SHA1 Message Date
d6779a7323
absolute banger 2022-03-22 20:08:44 +02:00
be2511c01b
env stuff 2022-03-22 20:08:35 +02:00
5c9171363c
user datastore 2022-03-22 20:08:29 +02:00
d33ef3a3f5
perm check middleware 2022-03-22 20:08:18 +02:00
6e430db6e1
users endpoint 2022-03-22 20:08:06 +02:00
10 changed files with 372 additions and 18 deletions

View File

@ -25,10 +25,11 @@ const User = ({user}) => {
const Restricted = ({user}) => { const Restricted = ({user}) => {
if (!user) return ''; if (!user) return '';
const { upload, admin } = user.permissions;
return ( return (
<div className='flex-container'> <div className='flex-container'>
{user.admin ? <NavLink className='navlink' to='/panel' >Panel</NavLink> : ''} {admin ? <NavLink className='navlink' to='/panel' >Panel</NavLink> : ''}
<NavLink className='navlink' to='/upload' >Upload</NavLink> {upload || admin ? <NavLink className='navlink' to='/upload' >Upload</NavLink>: '' }
<User user={user} /> <User user={user} />
</div> </div>
); );

View File

@ -1,4 +1,5 @@
.panel { .panel {
height: inherit; height: inherit;
width: inherit; width: inherit;
flex-direction: column;
} }

View File

@ -1,15 +1,34 @@
import React, { useEffect } from "react"; import React, { useEffect, useState } from "react";
import '../css/Panel.css'; import '../css/Panel.css';
const User = ({user}) => {
return (
<div className='user flex-container'>
{user.tag}
</div>
);
};
const Panel = () => { const Panel = () => {
const [users, setUsers] = useState([]);
useEffect(() => { useEffect(() => {
(async () => {
const response = await fetch('/api/users');
if (response.status !== 200) return;
const users = await response.json();
setUsers(users);
})();
}, []); }, []);
return ( return (
<div className='panel'> <div className='panel flex-container'>
asd {users.length ? users.map(user => <User key={user.id} user={user}/>):'No users'}
</div> </div>
); );

View File

@ -0,0 +1,212 @@
const { Logger } = require('../util/');
const { MongoClient } = require('mongodb');
/**
* A dedicated class to locally wrap the mongodb wrapper
*
* @class MongoDB
*/
class MongoDB {
constructor(client, config) {
if (!client) throw new Error('Missing reference to client!');
if (!config) throw new Error('No config file provided!');
if (config && (!config.database || !config.url)) throw new Error('Invalid config file provided!');
this.config = config;
this.client = null; // Mongo Client
this.parent = client; // Parent client
this.db = null;
this.logger = new Logger(this);
this.logger._debug = this.parent._debug;
}
async init() {
this.logger.info('Initializing database connection.');
try {
const client = new MongoClient(this.config.url + this.config.database, { useUnifiedTopology: true });
this.client = await client.connect();
this.db = await this.client.db(this.config.database);
this.logger.info('Database connected.');
} catch (err) {
this.logger.error('Database connection failed!\n' + err);
}
return this;
}
/**
* Find and return the first match
*
* @param {String} db The collection in which the data is to be updated
* @param {Object} query The filter that is used to find the data
* @returns {Array} An array containing the corresponding objects for the query
* @memberof Database
*/
async find(db, query) {
this.logger.debug(`Incoming find query for ${db} with parameters ${JSON.stringify(query)}`);
const cursor = await this.db.collection(db).find(query);
return cursor.toArray();
}
/**
* Find and return the first match
*
* @param {String} db The collection in which the data is to be updated
* @param {Object} query The filter that is used to find the data
* @returns {Object} An object containing the queried data
* @memberof Database
*/
findOne(db, query) {
this.logger.debug(`Incoming findOne query for ${db} with parameters ${JSON.stringify(query)}`);
return new Promise((resolve, reject) => {
this.db.collection(db).findOne(query, async (error, item) => {
if (error) return reject(error);
return resolve(item);
});
});
}
/**
* Update any and all filter matches.
* DEPRECATED!
*
* @param {String} db The collection in which the data is to be updated
* @param {Object} filter The filter that is used to find the data
* @param {Object} data The updated data
* @returns {WriteResult} Object containing the followint counts: Matched, Upserted, Modified
* @memberof Database
*/
update(db, filter, data) {
this.logger.debug(`Incoming update query for '${db}' with parameters\n${JSON.stringify(filter)}\nand data\n${JSON.stringify(data)}`);
this.logger.warn('Database.update() is deprecated!');
return new Promise((resolve, reject) => {
this.db.collection(db).update(filter, data, async (error, result) => {
if (error) return reject(error);
return resolve(result);
});
});
}
/**
* Update the first filter match.
*
* @param {String} db The collection in which the data is to be updated
* @param {Object} filter The filter that is used to find the data
* @param {Object} data The updated data
* @returns {WriteResult} Object containing the followint counts: Matched, Upserted, Modified
* @memberof Database
*/
updateOne(db, filter, data, upsert = false) {
this.logger.debug(`Incoming updateOne query for ${db} with parameters ${JSON.stringify(filter)}`);
return new Promise((resolve, reject) => {
this.db.collection(db).updateOne(filter, { $set: data }, { upsert }, async (error, result) => {
if (error) return reject(error);
//return resolve(result)
const { matchedCount, upsertedCount, modifiedCount } = result;
return resolve({ matched: matchedCount, upserted: upsertedCount, modified: modifiedCount });
});
});
}
/**
* Push data to an array
*
* @param {string} db The collection to query
* @param {object} filter The filter to find the document to update
* @param {object} data The data to be pushed
* @param {boolean} [upsert=false]
* @returns
* @memberof Database
*/
push(db, filter, data, upsert = false) {
this.logger.debug(`Incoming push query for ${db}, with upsert ${upsert} and with parameters ${JSON.stringify(filter)} and data ${JSON.stringify(data)}`);
return new Promise((resolve, reject) => {
this.db.collection(db).updateOne(filter, { $push: data }, { upsert }, async (error, result) => {
if (error) return reject(error);
return resolve(result);
});
});
}
/**
* Find a random element from a database
*
* @param {string} db The collection to query
* @param {object} [filter={}] The filtering object to narrow down the sample pool
* @param {number} [amount=1] Amount of items to return
* @returns {object}
* @memberof Database
*/
random(db, filter = {}, amount = 1) {
this.logger.debug(`Incoming random query for ${db} with parameters ${JSON.stringify(filter)} and amount ${amount}`);
if (amount > 100) amount = 100;
return new Promise((resolve, reject) => {
this.db.collection(db).aggregate([{ $match: filter }, { $sample: { size: amount } }], (err, item) => {
if (err) return reject(err);
resolve(item);
});
});
}
stats(options = {}) {
return new Promise((resolve, reject) => {
if (!this.db) return reject(new Error('Database not initialised'));
this.db.stats(options, (error, result) => {
if (error) reject(error);
resolve(result);
});
});
}
}
module.exports = MongoDB;

View File

@ -17,6 +17,8 @@ const { Logger } = require('../util');
const Intercom = require('./Intercom'); const Intercom = require('./Intercom');
const Registry = require('./Registry.js'); const Registry = require('./Registry.js');
const ClipIndex = require('./ClipIndex'); const ClipIndex = require('./ClipIndex');
const Database = require('./Database.js');
const Users = require('./Users');
class Client extends EventEmitter { class Client extends EventEmitter {
@ -42,9 +44,12 @@ class Client extends EventEmitter {
this.mediaDirectory = path.join(this.baseDirectory, opts.media.videos); this.mediaDirectory = path.join(this.baseDirectory, opts.media.videos);
this.registry = new Registry(this); this.registry = new Registry(this);
// this.mongoDB = new MongoDB(this, this._mongoOpts); this.database = new Database(this, this._mongoOpts);
this.intercom = new Intercom(this); this.intercom = new Intercom(this);
// this.permissionsUtil = PermissionsUtil; this.users = new Users(this, {
database: this.database,
collection: env.API_USER_COLLECTION
});
this.logger = new Logger(this); this.logger = new Logger(this);
this.clipIndex = new ClipIndex(this, opts); this.clipIndex = new ClipIndex(this, opts);
this.server = null; this.server = null;
@ -61,6 +66,11 @@ class Client extends EventEmitter {
this.passport = passport; this.passport = passport;
this.app = express(); this.app = express();
this.app.use((req, res, next) => {
if (!this.ready) return res.status(503).send('Server not ready. Try again in a moment.');
next();
});
// Shouldn't be necessary, everything should come from the same domain: galactic.corgi.wtf // Shouldn't be necessary, everything should come from the same domain: galactic.corgi.wtf
this.app.use(cors({ this.app.use(cors({
origin: (origin, cb) => { origin: (origin, cb) => {
@ -121,8 +131,8 @@ class Client extends EventEmitter {
} }
)); ));
passport.serializeUser((user, callback) => { passport.serializeUser(async (user, callback) => {
user.admin = user.id === '132777808362471424'; user = await this.users.getOrCreate(user);
callback(null, user); callback(null, user);
}); });
@ -130,11 +140,6 @@ class Client extends EventEmitter {
callback(null, user); callback(null, user);
}); });
this.app.use((req, res, next) => {
if (!this.ready) return res.status(503).send('Server not ready. Try again in a moment.');
next();
});
this.app.use((req, res, next) => { this.app.use((req, res, next) => {
this.logger.debug(`New request to path ${req.path} || Route: ${req.route}`); this.logger.debug(`New request to path ${req.path} || Route: ${req.route}`);
res.once('finish', () => { res.once('finish', () => {
@ -148,12 +153,22 @@ class Client extends EventEmitter {
next(); next();
}); });
this.users.on('userCreate', (user) => {
this.logger.info(`New user created: ${user.tag}`);
});
this.users.on('debug', (msg) => {
this.logger.debug(msg);
});
} }
async init() { async init() {
this.logger.info('Loading endpoints'); this.logger.info('Loading endpoints');
await this.registry.init(); await this.registry.init();
await this.database.init();
this.logger.debug(this.registry.print); this.logger.debug(this.registry.print);
const httpOpts = { const httpOpts = {
port: this.port, port: this.port,
@ -169,8 +184,6 @@ class Client extends EventEmitter {
this.server = http.createServer(httpOpts, this.app).listen(this.port); this.server = http.createServer(httpOpts, this.app).listen(this.port);
} }
// await this.mongoDB.init().catch((err) => this.logger.error(err.stack));
this.ready = true; this.ready = true;
this.logger.info(`API client built in ${process.env.NODE_ENV} environment`); this.logger.info(`API client built in ${process.env.NODE_ENV} environment`);
process.send({ _ready: true }); process.send({ _ready: true });

View File

@ -0,0 +1,49 @@
const { EventEmitter } = require('events');
// eslint-disable-next-line no-unused-vars
const MongoDB = require('./Database');
class Users extends EventEmitter {
/**
* Creates an instance of Users.
* @param {*} client
* @param {MongoDB} database
* @memberof Users
*/
constructor(client, { database, collection }) {
super();
this.client = client;
this.database = database;
this.collection = collection;
}
async getOrCreate(user) {
const id = user.id || user;
this.emit('debug', `User perms query for ${id}`);
const userPartial = await this.database.findOne(this.collection, { id });
user = { ...user, ...userPartial };
user.tag = `${user.username}#${user.discriminator}`;
this.emit('debug', `Result for ${id}: ${JSON.stringify(userPartial)}`);
if (userPartial) return user;
user.permissions = {};
await this.database.updateOne(this.collection, { id }, { id, tag: user.tag, permissions: {} }, true);
this.emit('userCreate', user);
return user;
}
getAll() {
return this.database.find(this.collection, {});
}
}
module.exports = Users;

View File

@ -0,0 +1,46 @@
const { APIEndpoint } = require('../../interfaces');
const { CheckAuth, Permissions } = require('../../middleware');
class Users extends APIEndpoint {
constructor(client, opts) {
super(client, {
name: 'users',
path: '/users',
...opts
});
this.methods = [
['get', this.get.bind(this)]
];
this.subpaths = [
['/:id', 'get', this.user.bind(this)]
];
this.middleware = [CheckAuth, Permissions('admin')];
this.init();
}
async get(req, res) {
try {
const users = await this.client.users.getAll();
res.json(users);
} catch (err) {
this.logger.error(err.stack || err);
res.status(500).end();
}
}
async user(req, res) {
}
}
module.exports = Users;

View File

@ -0,0 +1,12 @@
const Permissions = (perm) => {
return (req, res, next) => {
const { user: { permissions } } = req;
if (permissions[perm]) return next();
res.status(401).end();
};
};
module.exports = Permissions;

View File

@ -1,3 +1,4 @@
module.exports = { module.exports = {
CheckAuth: require('./Auth.js') CheckAuth: require('./Auth.js'),
Permissions: require('./Permissions.js'),
}; };

View File

@ -138,9 +138,9 @@ class Manager extends EventEmitter {
API_DB_URL: env.API_DB_URL, API_DB_URL: env.API_DB_URL,
API_SESSION_STORE: env.API_SESSION_STORE, API_SESSION_STORE: env.API_SESSION_STORE,
API_SESSION_COLLECTION: env.API_SESSION_COLLECTION, API_SESSION_COLLECTION: env.API_SESSION_COLLECTION,
API_USER_COLLECTION: env.API_USER_COLLECTION,
HTTP_PORT: opts.http.port, HTTP_PORT: opts.http.port,
API_SECRET: env.API_SECRET, API_SECRET: env.API_SECRET,
// DASHBOARD: opts.dasboardUrl,
AUTH_CALLBACK: opts.discord.callback, AUTH_CALLBACK: opts.discord.callback,
DISCORD_SECRET: env.DISCORD_SECRET, DISCORD_SECRET: env.DISCORD_SECRET,
DISCORD_ID: env.DISCORD_ID, DISCORD_ID: env.DISCORD_ID,