Compare commits
2 Commits
bc82da67a2
...
f908620c46
Author | SHA1 | Date | |
---|---|---|---|
f908620c46 | |||
8a9916893a |
@ -181,4 +181,13 @@ export type RegistrationInvite = {
|
||||
code: string,
|
||||
validFor: number,
|
||||
created: number
|
||||
}
|
||||
|
||||
// Types for these don't matter for the server as they're only used by the client
|
||||
export type GlobalClientSettings = object
|
||||
export type UserClientSettings = object
|
||||
|
||||
export type ClientSettings = {
|
||||
global: GlobalClientSettings,
|
||||
user?: UserClientSettings
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { Collection } from '@discordjs/collection';
|
||||
import { Collection as ExtendedMap } from '@discordjs/collection';
|
||||
|
||||
import { LoggerClient } from '@navy.gif/logger';
|
||||
import { MongoDB, ObjectId, Document } from '@navy.gif/wrappers';
|
||||
@ -6,7 +6,7 @@ import { MongoDB, ObjectId, Document } from '@navy.gif/wrappers';
|
||||
import Server from '../Server.js';
|
||||
import { Entity, UserDatabaseInterface } from '../interfaces/index.js';
|
||||
import { Role, User, UserApplication } from '../structures/index.js';
|
||||
import { ApplicationData, ExternalProfile, RoleData, UserData } from '../../../@types/Server.js';
|
||||
import { ApplicationData, ExternalProfile, RoleData, UserClientSettings, UserData } from '../../../@types/Server.js';
|
||||
import { Util } from '../../util/index.js';
|
||||
import { SignupCode } from '../../../@types/Other.js';
|
||||
import { CountResult, RoleQuery, UserQuery } from '../interfaces/UserDatabaseInterface.js';
|
||||
@ -15,6 +15,7 @@ type UserDBOptions = {
|
||||
userCollection?: string,
|
||||
appCollection?: string,
|
||||
roleCollection?: string,
|
||||
settingsCollection?: string,
|
||||
disableCache?: boolean
|
||||
}
|
||||
|
||||
@ -32,12 +33,13 @@ class UserDatabase implements UserDatabaseInterface
|
||||
#logger: LoggerClient;
|
||||
#disableCache: boolean;
|
||||
|
||||
#cache: Collection<string, Entity>;
|
||||
#cache: ExtendedMap<string, Entity>;
|
||||
// #userCollection: MongoCollection<Entity> | null;
|
||||
|
||||
#_userCollection: string;
|
||||
#_appCollection: string;
|
||||
#_roleCollection: string;
|
||||
#userCollection: string;
|
||||
#appCollection: string;
|
||||
#roleCollection: string;
|
||||
#settingsCollection: string;
|
||||
|
||||
#amount: CountResult;
|
||||
|
||||
@ -45,6 +47,7 @@ class UserDatabase implements UserDatabaseInterface
|
||||
userCollection = 'users',
|
||||
appCollection = 'applications',
|
||||
roleCollection = 'roles',
|
||||
settingsCollection = 'userSettings',
|
||||
disableCache = false
|
||||
}: UserDBOptions = {})
|
||||
{
|
||||
@ -53,12 +56,13 @@ class UserDatabase implements UserDatabaseInterface
|
||||
this.#db = db;
|
||||
this.#server = server;
|
||||
this.#logger = server.createLogger(this);
|
||||
this.#cache = new Collection();
|
||||
this.#cache = new ExtendedMap();
|
||||
// this.#userCollection = null;
|
||||
|
||||
this.#_userCollection = userCollection;
|
||||
this.#_appCollection = appCollection;
|
||||
this.#_roleCollection = roleCollection;
|
||||
this.#userCollection = userCollection;
|
||||
this.#appCollection = appCollection;
|
||||
this.#roleCollection = roleCollection;
|
||||
this.#settingsCollection = settingsCollection;
|
||||
|
||||
this.#disableCache = process.env.NODE_ENV === 'development' || disableCache;
|
||||
this.#amount = {
|
||||
@ -71,8 +75,9 @@ class UserDatabase implements UserDatabaseInterface
|
||||
|
||||
async init ()
|
||||
{
|
||||
for (const coll of [ this.#_userCollection, this.#_appCollection, this.#_roleCollection ])
|
||||
for (const coll of [ this.#userCollection, this.#appCollection, this.#roleCollection ])
|
||||
await this.#db.ensureIndex(coll, [ 'name' ], { unique: true });
|
||||
await this.#db.ensureIndex(this.#settingsCollection, [ 'user' ], { unique: true });
|
||||
await this.count(true);
|
||||
}
|
||||
|
||||
@ -81,9 +86,9 @@ class UserDatabase implements UserDatabaseInterface
|
||||
if (force)
|
||||
{
|
||||
const collections: string[] = [
|
||||
this.#_userCollection,
|
||||
this.#_appCollection,
|
||||
this.#_roleCollection
|
||||
this.#userCollection,
|
||||
this.#appCollection,
|
||||
this.#roleCollection
|
||||
];
|
||||
this.#amount = {} as CountResult;
|
||||
|
||||
@ -94,6 +99,20 @@ class UserDatabase implements UserDatabaseInterface
|
||||
return this.#amount;
|
||||
}
|
||||
|
||||
async fetchSettings (id: string): Promise<UserClientSettings>
|
||||
{
|
||||
const settings = await this.#db.collection<UserClientSettings>(this.#settingsCollection).findOne({ user: id }) as {_id?: ObjectId};
|
||||
if (!settings)
|
||||
return {};
|
||||
delete settings._id;
|
||||
return settings;
|
||||
}
|
||||
|
||||
async updateSettings (id: string, settings: object): Promise<void>
|
||||
{
|
||||
await this.#db.updateOne(this.#settingsCollection, { user: id }, settings, true);
|
||||
}
|
||||
|
||||
async fetchUsers ({ ids, page, pageSize }: UserQuery = {}): Promise<User[]>
|
||||
{
|
||||
|
||||
@ -114,7 +133,7 @@ class UserDatabase implements UserDatabaseInterface
|
||||
findOptions.skip = page * pageSize;
|
||||
}
|
||||
|
||||
const data = await this.#db.find<UserData>(this.#_userCollection, query, findOptions);
|
||||
const data = await this.#db.find<UserData>(this.#userCollection, query, findOptions);
|
||||
const users = [];
|
||||
for (const user of data)
|
||||
{
|
||||
@ -159,13 +178,12 @@ class UserDatabase implements UserDatabaseInterface
|
||||
data.roles = await this.fetchRoles(data.roles);
|
||||
|
||||
return data as T;
|
||||
|
||||
}
|
||||
|
||||
async fetchUser (id: string, force = false)
|
||||
{
|
||||
|
||||
const data = await this._fetch<UserData>(id, this.#_userCollection, force);
|
||||
const data = await this._fetch<UserData>(id, this.#userCollection, force);
|
||||
if (!data)
|
||||
return null;
|
||||
if (data instanceof User)
|
||||
@ -182,7 +200,7 @@ class UserDatabase implements UserDatabaseInterface
|
||||
async fetchApplication (id: string, force = false)
|
||||
{
|
||||
|
||||
const data = await this._fetch<ApplicationData>(id, this.#_appCollection, force);
|
||||
const data = await this._fetch<ApplicationData>(id, this.#appCollection, force);
|
||||
if (!data)
|
||||
return null;
|
||||
if (data instanceof UserApplication)
|
||||
@ -198,7 +216,7 @@ class UserDatabase implements UserDatabaseInterface
|
||||
|
||||
async fetchRole (id: string, force = false)
|
||||
{
|
||||
const data = await this._fetch<RoleData>(id, this.#_roleCollection, force);
|
||||
const data = await this._fetch<RoleData>(id, this.#roleCollection, force);
|
||||
if (!data)
|
||||
return null;
|
||||
if (data instanceof Role)
|
||||
@ -227,7 +245,7 @@ class UserDatabase implements UserDatabaseInterface
|
||||
findOptions.skip = page * pageSize;
|
||||
}
|
||||
|
||||
const data = await this.#db.find<RoleData>(this.#_roleCollection, query, findOptions);
|
||||
const data = await this.#db.find<RoleData>(this.#roleCollection, query, findOptions);
|
||||
const roles = [];
|
||||
for (const role of data)
|
||||
{
|
||||
@ -246,7 +264,7 @@ class UserDatabase implements UserDatabaseInterface
|
||||
// Retrieves the role with the lowest position, where 0 is highest
|
||||
async lowestRole ()
|
||||
{
|
||||
const data = await this.#db.findOne<RoleData>(this.#_roleCollection, {}, { sort: { position: 'desc' } });
|
||||
const data = await this.#db.findOne<RoleData>(this.#roleCollection, {}, { sort: { position: 'desc' } });
|
||||
if (!data)
|
||||
return null;
|
||||
const role = this._createRole(data);
|
||||
@ -260,7 +278,7 @@ class UserDatabase implements UserDatabaseInterface
|
||||
throw new Error('Missing user id');
|
||||
|
||||
id = id.toString();
|
||||
const data = await this.#db.find<ApplicationData>(this.#_appCollection, { ownerId: new ObjectId(id) });
|
||||
const data = await this.#db.find<ApplicationData>(this.#appCollection, { ownerId: new ObjectId(id) });
|
||||
if (!data || !data.length)
|
||||
return [];
|
||||
|
||||
@ -286,7 +304,7 @@ class UserDatabase implements UserDatabaseInterface
|
||||
if (user)
|
||||
return Promise.resolve(user);
|
||||
|
||||
const data = await this.#db.findOne<UserData>(this.#_userCollection, { name }, { collation: { locale: 'en', strength: 2 } });
|
||||
const data = await this.#db.findOne<UserData>(this.#userCollection, { name }, { collation: { locale: 'en', strength: 2 } });
|
||||
if (!data)
|
||||
return null;
|
||||
|
||||
@ -319,7 +337,7 @@ class UserDatabase implements UserDatabaseInterface
|
||||
if (app)
|
||||
return Promise.resolve(app);
|
||||
|
||||
const data = await this.#db.findOne<ApplicationData>(this.#_appCollection, { token });
|
||||
const data = await this.#db.findOne<ApplicationData>(this.#appCollection, { token });
|
||||
if (!data)
|
||||
return null;
|
||||
|
||||
@ -350,7 +368,7 @@ class UserDatabase implements UserDatabaseInterface
|
||||
if (user)
|
||||
return Promise.resolve(user);
|
||||
|
||||
const data = await this.#db.findOne<UserData>(this.#_userCollection, { 'externalProfiles.discord.id': profile.id });
|
||||
const data = await this.#db.findOne<UserData>(this.#userCollection, { 'externalProfiles.discord.id': profile.id });
|
||||
if (data)
|
||||
{
|
||||
user = await this._createUser(data);
|
||||
@ -362,7 +380,7 @@ class UserDatabase implements UserDatabaseInterface
|
||||
return null;
|
||||
|
||||
// If a user with the same username already exists, force the holder of the discord account to create a new account or log in to the other account and link them
|
||||
const existing = await this.#db.findOne(this.#_userCollection, { name: profile.username }, { collation: { locale: 'en', strength: 2 } });
|
||||
const existing = await this.#db.findOne(this.#userCollection, { name: profile.username }, { collation: { locale: 'en', strength: 2 } });
|
||||
if (existing)
|
||||
{
|
||||
const temp = await this._createUser({ temporary: true, displayName: profile.username });
|
||||
@ -402,7 +420,7 @@ class UserDatabase implements UserDatabaseInterface
|
||||
const json = user.jsonPrivate as {_id?: ObjectId, id?: string};
|
||||
json._id = new ObjectId(json.id);
|
||||
delete json.id;
|
||||
await this.#db.insertOne(this.#_userCollection, json);
|
||||
await this.#db.insertOne(this.#userCollection, json);
|
||||
// await this.updateUser(user);
|
||||
if (!this.#disableCache)
|
||||
this.#cache.set(user.id, user);
|
||||
@ -417,7 +435,7 @@ class UserDatabase implements UserDatabaseInterface
|
||||
const json = app.jsonPrivate as {_id?: ObjectId, id?: string};
|
||||
json._id = new ObjectId(json.id);
|
||||
delete json.id;
|
||||
await this.#db.insertOne(this.#_appCollection, json);
|
||||
await this.#db.insertOne(this.#appCollection, json);
|
||||
if (!this.#disableCache)
|
||||
this.#cache.set(app.id, app);
|
||||
this.#amount.applications++;
|
||||
@ -436,7 +454,7 @@ class UserDatabase implements UserDatabaseInterface
|
||||
}
|
||||
|
||||
const role = this._createRole(data);
|
||||
await this.#db.insertOne(this.#_roleCollection, role.jsonPrivate);
|
||||
await this.#db.insertOne(this.#roleCollection, role.jsonPrivate);
|
||||
if (!this.#disableCache)
|
||||
this.#cache.set(role.id, role);
|
||||
this.#amount.roles++;
|
||||
@ -445,12 +463,12 @@ class UserDatabase implements UserDatabaseInterface
|
||||
|
||||
async deleteApplication (id: string)
|
||||
{
|
||||
return void await this.#db.deleteOne(this.#_appCollection, { _id: new ObjectId(id) });
|
||||
return void await this.#db.deleteOne(this.#appCollection, { _id: new ObjectId(id) });
|
||||
}
|
||||
|
||||
deleteRole (id: string)
|
||||
{
|
||||
return this.#db.deleteOne(this.#_roleCollection, { _id: new ObjectId(id) });
|
||||
return this.#db.deleteOne(this.#roleCollection, { _id: new ObjectId(id) });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -493,17 +511,17 @@ class UserDatabase implements UserDatabaseInterface
|
||||
*/
|
||||
updateUser (user: User)
|
||||
{
|
||||
return this._updateEntity<User>(user, this.#_userCollection);
|
||||
return this._updateEntity<User>(user, this.#userCollection);
|
||||
}
|
||||
|
||||
updateApplication (app: UserApplication)
|
||||
{
|
||||
return this._updateEntity<UserApplication>(app, this.#_appCollection);
|
||||
return this._updateEntity<UserApplication>(app, this.#appCollection);
|
||||
}
|
||||
|
||||
updateRole (role: Role)
|
||||
{
|
||||
return this._updateEntity<Role>(role, this.#_roleCollection);
|
||||
return this._updateEntity<Role>(role, this.#roleCollection);
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,47 +0,0 @@
|
||||
import { Request, Response } from '../../../../@types/Server.js';
|
||||
import { PermissionManager } from '../../../util/index.js';
|
||||
import Server from '../../Server.js';
|
||||
import VersionedEndpoint from '../../interfaces/VersionedEndpoint.js';
|
||||
|
||||
class TestEndpoint extends VersionedEndpoint
|
||||
{
|
||||
|
||||
constructor (server: Server)
|
||||
{
|
||||
super(server, {
|
||||
name: 'test',
|
||||
path: '/test',
|
||||
version: 2,
|
||||
auth: true
|
||||
});
|
||||
|
||||
this.methods = [
|
||||
[ 'get', this.get.bind(this) ]
|
||||
];
|
||||
|
||||
this.subpaths = [
|
||||
[ 'get', '/dingus/:thing', this.dingus.bind(this) ]
|
||||
];
|
||||
|
||||
this.middleware = [ server.auth.createAuthoriser('testing:bruh') ];
|
||||
|
||||
}
|
||||
|
||||
get (_req: Request, res: Response)
|
||||
{
|
||||
res.send('duh!');
|
||||
}
|
||||
|
||||
dingus (_req: Request, res: Response)
|
||||
{
|
||||
res.status(200).json({
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
routes: this.server.app._router.stack.filter((elem) => elem.route).map(elem => elem.route.path).sort(),
|
||||
permissions: PermissionManager.DefaultPermissions
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default TestEndpoint;
|
@ -133,7 +133,7 @@ class Flags extends ApiEndpoint
|
||||
flag.env = body.env;
|
||||
if (body.consumer)
|
||||
flag.consumer = body.consumer;
|
||||
if (body.hierarchy)
|
||||
if ('hierarchy' in body)
|
||||
flag.hierarchy = body.hierarchy;
|
||||
if (flag.value)
|
||||
try
|
||||
|
@ -26,8 +26,8 @@ class Roles extends ApiEndpoint
|
||||
this.subpaths = [
|
||||
[ 'delete', '/:roleid', this.deleteRole.bind(this), server.auth.createAuthoriser('administrator:roles:delete', 5) ],
|
||||
[ 'patch', '/:roleid', this.modifyRole.bind(this), server.auth.createAuthoriser('administrator:roles:edit', 5) ],
|
||||
[ 'patch', '/:roleid/permissions', this.modifyPerms.bind(this), server.auth.createAuthoriser('administrator:roles:edit', 5) ],
|
||||
[ 'patch', '/:roleid/ratelimits', this.modifyLimits.bind(this), server.auth.createAuthoriser('administrator:roles:edit', 5) ]
|
||||
[ 'patch', '/:roleid/permissions', this.modifyPerms.bind(this), server.auth.createAuthoriser('administrator:roles:permissions', 5) ],
|
||||
[ 'patch', '/:roleid/ratelimits', this.modifyLimits.bind(this), server.auth.createAuthoriser('administrator:roles:ratelimits', 5) ]
|
||||
];
|
||||
|
||||
this.#users = server.users;
|
||||
|
@ -1,7 +1,8 @@
|
||||
// const { ApiEndpoint } = require("../../../interfaces");
|
||||
import { Request, Response } from '../../../../../@types/Server.js';
|
||||
import { ClientSettings, Request, Response } from '../../../../../@types/Server.js';
|
||||
import Server from '../../../Server.js';
|
||||
import { ApiEndpoint } from '../../../interfaces/index.js';
|
||||
import User from '../../../structures/User.js';
|
||||
|
||||
class SettingsEndpoint extends ApiEndpoint
|
||||
{
|
||||
@ -14,16 +15,31 @@ class SettingsEndpoint extends ApiEndpoint
|
||||
});
|
||||
|
||||
this.methods = [
|
||||
[ 'get', this.getSettings.bind(this) ]
|
||||
[ 'get', this.getSettings.bind(this) ],
|
||||
[ 'post', this.updateSettings.bind(this), [ server.auth.authenticate ]]
|
||||
];
|
||||
|
||||
// this.middleware = [ server.auth.authenticate ];
|
||||
|
||||
}
|
||||
|
||||
getSettings (_req: Request, res: Response)
|
||||
async getSettings (req: Request, res: Response)
|
||||
{
|
||||
res.json(this.server.clientSettings);
|
||||
const { user } = req;
|
||||
const settings: ClientSettings = { global: this.server.clientSettings };
|
||||
if (user && user instanceof User)
|
||||
settings.user = await user.clientSettings();
|
||||
|
||||
res.json(settings);
|
||||
}
|
||||
|
||||
async updateSettings (req: Request, res: Response): Promise<void>
|
||||
{
|
||||
const { user, body } = req;
|
||||
if (!(user instanceof User))
|
||||
return void res.status(403).send('Only user accounts can edit settings');
|
||||
await user.updateClientSettings(body);
|
||||
res.end();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -17,12 +17,12 @@ class UsersEndpoint extends ApiEndpoint
|
||||
path: '/users',
|
||||
});
|
||||
|
||||
this.methods.push([ 'get', this.getUsers.bind(this), [ server.auth.createAuthoriser('administrator', 5) ]]);
|
||||
this.methods.push([ 'get', this.getUsers.bind(this), [ server.auth.createAuthoriser('administrator:users', 5) ]]);
|
||||
this.subpaths = [
|
||||
[ 'get', '/:userid', this.user.bind(this), [ server.auth.createAuthoriser('administrator', 5) ]],
|
||||
[ 'get', '/:userid', this.user.bind(this), [ server.auth.createAuthoriser('administrator:users', 5) ]],
|
||||
[ 'get', '/:userid/avatar', this.avatar.bind(this) ],
|
||||
[ 'get', '/:userid/applications', this.userApplications.bind(this), [ server.auth.createAuthoriser('administrator', 5) ]],
|
||||
[ 'post', '/:userid/permissions', this.updatePermissions.bind(this), [ server.auth.createAuthoriser('administrator', 5) ]]
|
||||
[ 'get', '/:userid/applications', this.userApplications.bind(this), [ server.auth.createAuthoriser('administrator:users:applications', 5) ]],
|
||||
[ 'post', '/:userid/permissions', this.updatePermissions.bind(this), [ server.auth.createAuthoriser('administrator:users:permissions', 5) ]]
|
||||
];
|
||||
// this.middleware = [ server.auth.createAuthoriser('administrator', 10) ];
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { DeleteResult } from '@navy.gif/wrappers';
|
||||
import { SignupCode } from '../../../@types/Other.js';
|
||||
import { ApplicationData, ExternalProfile, RoleData, UserData } from '../../../@types/Server.js';
|
||||
import { ApplicationData, ExternalProfile, RoleData, UserClientSettings, UserData } from '../../../@types/Server.js';
|
||||
import { Role, User, UserApplication } from '../structures/index.js';
|
||||
|
||||
export type Query<T, TData> = {
|
||||
@ -43,6 +43,10 @@ interface UserDatabaseInterface {
|
||||
|
||||
updateUser(user: User): Promise<User>
|
||||
|
||||
fetchSettings(userId: string): Promise<UserClientSettings>
|
||||
|
||||
updateSettings(userId: string, settings: UserClientSettings): Promise<void>
|
||||
|
||||
fetchUserApplications(id: string): Promise<UserApplication[]>
|
||||
|
||||
fetchApplication(id: string, force?: boolean): Promise<UserApplication | null>
|
||||
|
@ -4,7 +4,7 @@
|
||||
import Argon2 from 'argon2';
|
||||
import { AbstractUser } from '../interfaces/index.js';
|
||||
import Server from '../Server.js';
|
||||
import { ExternalProfile, UserData } from '../../../@types/Server.js';
|
||||
import { ExternalProfile, UserClientSettings, UserData } from '../../../@types/Server.js';
|
||||
|
||||
// Fields omitted in the json getter
|
||||
// Should be keys used in jsonPrivate, not the ones defined in the constructor
|
||||
@ -39,6 +39,16 @@ class User extends AbstractUser
|
||||
|
||||
}
|
||||
|
||||
clientSettings (): Promise<UserClientSettings>
|
||||
{
|
||||
return this.db.fetchSettings(this.id);
|
||||
}
|
||||
|
||||
async updateClientSettings (settings: UserClientSettings): Promise<void>
|
||||
{
|
||||
await this.db.updateSettings(this.id, settings);
|
||||
}
|
||||
|
||||
override save ()
|
||||
{
|
||||
return this.db.updateUser(this);
|
||||
|
Loading…
Reference in New Issue
Block a user