User client settings storage

Edited some permissions
This commit is contained in:
Erik 2023-07-17 00:56:36 +03:00
parent bc82da67a2
commit 8a9916893a
Signed by: Navy.gif
GPG Key ID: 2532FBBB61C65A68
8 changed files with 95 additions and 94 deletions

View File

@ -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);
}

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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) ];

View File

@ -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>

View File

@ -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);