untested WIP
This commit is contained in:
@@ -0,0 +1,200 @@
|
||||
import { Collection } from "@discordjs/collection";
|
||||
import { z } from "zod";
|
||||
import { Role } from "../../generated/prisma/client";
|
||||
import { User } from "../../generated/prisma/browser";
|
||||
import { SessionTokensObject } from "./SessionController";
|
||||
import { sessions } from "../managers/sessions";
|
||||
import BodyError from "../Errors/BodyError";
|
||||
import { prisma } from "../constants";
|
||||
import { events } from "../modules/globalEvents";
|
||||
import { RoleController } from "./RoleController";
|
||||
import { roles } from "../managers/roles";
|
||||
|
||||
|
||||
|
||||
export default class UserController {
|
||||
public id: string;
|
||||
public name: string | null;
|
||||
public login: string;
|
||||
public email: string;
|
||||
public image: string | null;
|
||||
|
||||
private _roles: Collection<string, Role>;
|
||||
|
||||
public createdAt: Date;
|
||||
public updatedAt: Date;
|
||||
constructor(userdata: User & { roles: Role[] }) {
|
||||
this.id = userdata.id;
|
||||
this.name = userdata.name;
|
||||
this.login = userdata.login;
|
||||
this.email = userdata.email;
|
||||
this.image = userdata.image;
|
||||
this.updatedAt = userdata.updatedAt;
|
||||
this.createdAt = userdata.createdAt;
|
||||
|
||||
this._roles = (() => {
|
||||
let collection = new Collection<string, Role>();
|
||||
userdata.roles.map((v: any) => collection.set(v.id, v));
|
||||
|
||||
return collection;
|
||||
})();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the internal values
|
||||
*
|
||||
* This is an internal method used to update all the internal values when we query the database. This way
|
||||
* everything stays upto date even when we pass around the user controller.
|
||||
*
|
||||
* @param userdata - User object from Prisma
|
||||
*/
|
||||
private _updateInternalValues(userdata: User) {
|
||||
this.id = userdata.id;
|
||||
this.name = userdata.name;
|
||||
this.login = userdata.login;
|
||||
this.email = userdata.email;
|
||||
this.image = userdata.image;
|
||||
this.updatedAt = userdata.updatedAt;
|
||||
this.createdAt = userdata.createdAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Session
|
||||
*
|
||||
* This will create a session in the database that is linked to the user and will then create a pair of access and refresh
|
||||
* tokens to provide to the user such that they can authorized their api requests.
|
||||
*
|
||||
* @returns {Promise<SessionTokensObject>} - Object with an access token and a refresh token.
|
||||
*/
|
||||
public async createSession(): Promise<SessionTokensObject> {
|
||||
return sessions.create({ user: this });
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user
|
||||
*
|
||||
* Take in a partial of the user data and validate it then updated it if it passes validation and return
|
||||
* the updated `UserController` object.
|
||||
*
|
||||
* @param data - A partial of the user data
|
||||
* @returns {Promise<UserController>} - The updated user controller
|
||||
*/
|
||||
public async update(data: Partial<User>) {
|
||||
// Parsed Data With Schema
|
||||
const pData = z
|
||||
.object({
|
||||
name: z.string().optional(),
|
||||
image: z.string().optional(),
|
||||
})
|
||||
.strict()
|
||||
.parse(data);
|
||||
|
||||
if (Object.keys(data).length == 0)
|
||||
throw new BodyError("Body cannot be empty.");
|
||||
|
||||
const updatedUser = await prisma.user.update({
|
||||
where: { id: this.id },
|
||||
data: pData,
|
||||
});
|
||||
|
||||
this._updateInternalValues(updatedUser);
|
||||
events.emit("user:updated", { user: this, updatedValues: data });
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch Roles
|
||||
*
|
||||
* This method will fetch all of the roles that a user belongs to and will return each of their controllers in a collection
|
||||
* of role id's and RoleControllers.
|
||||
*
|
||||
* @returns {Promise<Collection<string, RoleController>>} A collection of all the roles a user has
|
||||
*/
|
||||
public async fetchRoles(): Promise<Collection<string, RoleController>> {
|
||||
const collection = new Collection<string, RoleController>();
|
||||
|
||||
await Promise.all(
|
||||
this._roles.map(async (v) =>
|
||||
collection.set(v.id, await roles.fetch(v.id))
|
||||
)
|
||||
);
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check Permission
|
||||
*
|
||||
* Check if this user has this specific permission. This method will not only check explicit permissions defined in
|
||||
* the database under users and roles, but will also generate implicit permissions for resources that the user has
|
||||
* access to but doesn't specifically have defined under any given permissions object.
|
||||
*
|
||||
* @param permission - The permission to check for
|
||||
* @returns {boolean} Does this user have the specified permission
|
||||
*/
|
||||
public async hasPermission(permission: string) {
|
||||
let resources = await prisma.user.findFirst({
|
||||
where: { id: this.id },
|
||||
select: {
|
||||
sessions: {
|
||||
select: { id: true },
|
||||
},
|
||||
apiKeys: {
|
||||
select: { id: true },
|
||||
},
|
||||
projects: {
|
||||
select: { id: true },
|
||||
},
|
||||
services: {
|
||||
select: { id: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const implicitPermissions = Object.keys(resources ?? {})
|
||||
.filter((v) => resources![v].length > 0)
|
||||
.map(
|
||||
(v) =>
|
||||
`resource.${v}.[${(resources![v] as { id: string }[])
|
||||
.map((o) => o.id)
|
||||
.join(",")}].user.${this.id}.implicit`
|
||||
);
|
||||
|
||||
console.log(implicitPermissions);
|
||||
|
||||
let checks = [
|
||||
(await this.fetchRoles()).map((v) => v.checkPermission(permission)),
|
||||
].flatMap((v) => v);
|
||||
|
||||
return checks.includes(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* To JSON
|
||||
*
|
||||
* Create an object that can be safely returned to the user of an api request such that when you
|
||||
* need to return data to the end user, you don't accidently return data that could be harmful
|
||||
* if leaked.
|
||||
*
|
||||
* Options:
|
||||
* - Safe return is to return only data that is considered "safe", and not detrimental to pass around
|
||||
*
|
||||
* @param opts - Options to change the output
|
||||
* @returns - An object that is JSON friendly
|
||||
*/
|
||||
public toJson(opts?: { safeReturn: boolean }) {
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
roles: opts?.safeReturn
|
||||
? undefined
|
||||
: this._roles.size > 0
|
||||
? this._roles.map((v) => v.moniker)
|
||||
: undefined,
|
||||
login: opts?.safeReturn ? undefined : this.login,
|
||||
email: opts?.safeReturn ? undefined : this.email,
|
||||
image: this.image,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user