Fix UserController permission serialization and include current updates
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
import { CatalogItem } from "../../generated/prisma/client";
|
||||
import { prisma } from "../constants";
|
||||
import { catalogCw } from "../modules/cw-utils/procurement/catalog";
|
||||
import { CatalogItem as CWCatalogItem } from "../modules/cw-utils/procurement/catalog.types";
|
||||
import GenericError from "../Errors/GenericError";
|
||||
|
||||
/**
|
||||
* Catalog Item Controller
|
||||
*
|
||||
* This class encapsulates a catalog item entity and provides domain methods
|
||||
* for accessing, refreshing, and serializing catalog item data. It bridges
|
||||
* the internal database representation with ConnectWise catalog data.
|
||||
*/
|
||||
export class CatalogItemController {
|
||||
public readonly id: string;
|
||||
public name: string;
|
||||
public description: string | null;
|
||||
public customerDescription: string | null;
|
||||
public internalNotes: string | null;
|
||||
|
||||
public readonly cwCatalogId: number;
|
||||
public readonly identifier: string | null;
|
||||
|
||||
public manufacturer: string | null;
|
||||
public manufactureCwId: number | null;
|
||||
public partNumber: string | null;
|
||||
|
||||
public vendorName: string | null;
|
||||
public vendorSku: string | null;
|
||||
public vendorCwId: number | null;
|
||||
|
||||
public price: number;
|
||||
public cost: number;
|
||||
|
||||
public inactive: boolean;
|
||||
public salesTaxable: boolean;
|
||||
|
||||
public onHand: number;
|
||||
public cwLastUpdated: Date | null;
|
||||
|
||||
private _linkedItems: CatalogItemController[];
|
||||
|
||||
public readonly createdAt: Date;
|
||||
public updatedAt: Date;
|
||||
|
||||
constructor(
|
||||
itemData: CatalogItem & {
|
||||
linkedItems?: CatalogItem[];
|
||||
},
|
||||
) {
|
||||
this.id = itemData.id;
|
||||
this.name = itemData.name;
|
||||
this.description = itemData.description;
|
||||
this.customerDescription = itemData.customerDescription;
|
||||
this.internalNotes = itemData.internalNotes;
|
||||
this.cwCatalogId = itemData.cwCatalogId;
|
||||
this.identifier = itemData.identifier;
|
||||
this.manufacturer = itemData.manufacturer;
|
||||
this.manufactureCwId = itemData.manufactureCwId;
|
||||
this.partNumber = itemData.partNumber;
|
||||
this.vendorName = itemData.vendorName;
|
||||
this.vendorSku = itemData.vendorSku;
|
||||
this.vendorCwId = itemData.vendorCwId;
|
||||
this.price = itemData.price;
|
||||
this.cost = itemData.cost;
|
||||
this.inactive = itemData.inactive;
|
||||
this.salesTaxable = itemData.salesTaxable;
|
||||
this.onHand = itemData.onHand;
|
||||
this.cwLastUpdated = itemData.cwLastUpdated;
|
||||
this.createdAt = itemData.createdAt;
|
||||
this.updatedAt = itemData.updatedAt;
|
||||
|
||||
this._linkedItems = (itemData.linkedItems ?? []).map(
|
||||
(linked) => new CatalogItemController(linked),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh Inventory
|
||||
*
|
||||
* Fetches the latest on-hand inventory count from ConnectWise
|
||||
* and updates both the controller state and the database.
|
||||
*
|
||||
* @returns {Promise<CatalogItemController>} - The updated controller
|
||||
*/
|
||||
public async refreshInventory(): Promise<CatalogItemController> {
|
||||
const onHand = await catalogCw.fetchInventoryOnHand(this.cwCatalogId);
|
||||
|
||||
if (onHand !== this.onHand) {
|
||||
await prisma.catalogItem.update({
|
||||
where: { id: this.id },
|
||||
data: { onHand },
|
||||
});
|
||||
this.onHand = onHand;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch Linked Items
|
||||
*
|
||||
* Returns the linked catalog items as an array of controllers.
|
||||
*
|
||||
* @returns {CatalogItemController[]} - Array of linked item controllers
|
||||
*/
|
||||
public getLinkedItems(): CatalogItemController[] {
|
||||
return this._linkedItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Link Item
|
||||
*
|
||||
* Links another catalog item to this item. The relationship is bidirectional
|
||||
* via the Prisma implicit many-to-many.
|
||||
*
|
||||
* @param targetId - The internal ID of the catalog item to link
|
||||
* @returns {Promise<CatalogItemController>} - The updated controller
|
||||
*/
|
||||
public async linkItem(targetId: string): Promise<CatalogItemController> {
|
||||
if (targetId === this.id) {
|
||||
throw new GenericError({
|
||||
message: "Cannot link a catalog item to itself",
|
||||
name: "InvalidLinkTarget",
|
||||
cause: `Item '${this.id}' cannot be linked to itself`,
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const target = await prisma.catalogItem.findFirst({
|
||||
where: { id: targetId },
|
||||
});
|
||||
|
||||
if (!target) {
|
||||
throw new GenericError({
|
||||
message: "Target catalog item not found",
|
||||
name: "CatalogItemNotFound",
|
||||
cause: `No catalog item exists with ID '${targetId}'`,
|
||||
status: 404,
|
||||
});
|
||||
}
|
||||
|
||||
const updated = await prisma.catalogItem.update({
|
||||
where: { id: this.id },
|
||||
data: {
|
||||
linkedItems: { connect: { id: targetId } },
|
||||
},
|
||||
include: { linkedItems: true },
|
||||
});
|
||||
|
||||
this._linkedItems = (updated.linkedItems ?? []).map(
|
||||
(linked) => new CatalogItemController(linked),
|
||||
);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink Item
|
||||
*
|
||||
* Removes the link between this catalog item and another.
|
||||
*
|
||||
* @param targetId - The internal ID of the catalog item to unlink
|
||||
* @returns {Promise<CatalogItemController>} - The updated controller
|
||||
*/
|
||||
public async unlinkItem(targetId: string): Promise<CatalogItemController> {
|
||||
const updated = await prisma.catalogItem.update({
|
||||
where: { id: this.id },
|
||||
data: {
|
||||
linkedItems: { disconnect: { id: targetId } },
|
||||
},
|
||||
include: { linkedItems: true },
|
||||
});
|
||||
|
||||
this._linkedItems = (updated.linkedItems ?? []).map(
|
||||
(linked) => new CatalogItemController(linked),
|
||||
);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* To JSON
|
||||
*
|
||||
* Serializes the catalog item into a safe, API-friendly object.
|
||||
*
|
||||
* @param opts - Options to control output
|
||||
* @returns - A JSON-safe representation of the catalog item
|
||||
*/
|
||||
public toJson(opts?: { includeLinkedItems?: boolean }): Record<string, any> {
|
||||
return {
|
||||
id: this.id,
|
||||
cwCatalogId: this.cwCatalogId,
|
||||
identifier: this.identifier,
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
customerDescription: this.customerDescription,
|
||||
internalNotes: this.internalNotes,
|
||||
manufacturer: this.manufacturer,
|
||||
manufactureCwId: this.manufactureCwId,
|
||||
partNumber: this.partNumber,
|
||||
vendorName: this.vendorName,
|
||||
vendorSku: this.vendorSku,
|
||||
vendorCwId: this.vendorCwId,
|
||||
price: this.price,
|
||||
cost: this.cost,
|
||||
inactive: this.inactive,
|
||||
salesTaxable: this.salesTaxable,
|
||||
onHand: this.onHand,
|
||||
cwLastUpdated: this.cwLastUpdated,
|
||||
linkedItems: opts?.includeLinkedItems
|
||||
? this._linkedItems.map((item) => item.toJson())
|
||||
: undefined,
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ export class CompanyController {
|
||||
public readonly cw_CompanyId: number;
|
||||
public readonly cw_Data?: {
|
||||
company: CWCompany;
|
||||
defaultContact: Contact;
|
||||
defaultContact: Contact | null;
|
||||
allContacts: Contact[];
|
||||
};
|
||||
|
||||
@@ -96,23 +96,25 @@ export class CompanyController {
|
||||
},
|
||||
primaryContact: !opts?.includePrimaryContact
|
||||
? undefined
|
||||
: {
|
||||
firstName: this.cw_Data?.defaultContact.firstName,
|
||||
lastName: this.cw_Data?.defaultContact.lastName,
|
||||
cwId: this.cw_Data?.defaultContact.id,
|
||||
inactive: this.cw_Data?.defaultContact.inactiveFlag,
|
||||
title: this.cw_Data?.defaultContact.title,
|
||||
phone: this.cw_Data?.defaultContact.defaultPhoneNbr,
|
||||
email: (() => {
|
||||
if (!this.cw_Data?.defaultContact.communicationItems)
|
||||
return null;
|
||||
return (
|
||||
this.cw_Data?.defaultContact.communicationItems.find(
|
||||
(v) => v.type.name === "Email",
|
||||
)?.value ?? null
|
||||
);
|
||||
})(),
|
||||
},
|
||||
: this.cw_Data?.defaultContact
|
||||
? {
|
||||
firstName: this.cw_Data.defaultContact.firstName,
|
||||
lastName: this.cw_Data.defaultContact.lastName,
|
||||
cwId: this.cw_Data.defaultContact.id,
|
||||
inactive: this.cw_Data.defaultContact.inactiveFlag,
|
||||
title: this.cw_Data.defaultContact.title,
|
||||
phone: this.cw_Data.defaultContact.defaultPhoneNbr,
|
||||
email: (() => {
|
||||
if (!this.cw_Data?.defaultContact?.communicationItems)
|
||||
return null;
|
||||
return (
|
||||
this.cw_Data.defaultContact.communicationItems.find(
|
||||
(v) => v.type.name === "Email",
|
||||
)?.value ?? null
|
||||
);
|
||||
})(),
|
||||
}
|
||||
: null,
|
||||
allContacts: !opts?.includeAllContacts
|
||||
? undefined
|
||||
: this.cw_Data?.allContacts.map((contact) => ({
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
import { Opportunity } from "../../generated/prisma/client";
|
||||
import { prisma } from "../constants";
|
||||
import { fetchOpportunity } from "../modules/cw-utils/opportunities/fetchOpportunity";
|
||||
import { CWOpportunity } from "../modules/cw-utils/opportunities/opportunity.types";
|
||||
|
||||
/**
|
||||
* Opportunity Controller
|
||||
*
|
||||
* Domain model class that encapsulates an Opportunity entity and provides
|
||||
* methods for accessing, refreshing from ConnectWise, and serializing
|
||||
* opportunity data.
|
||||
*/
|
||||
export class OpportunityController {
|
||||
public readonly id: string;
|
||||
public readonly cwOpportunityId: number;
|
||||
public name: string;
|
||||
public notes: string | null;
|
||||
|
||||
public typeName: string | null;
|
||||
public typeCwId: number | null;
|
||||
public stageName: string | null;
|
||||
public stageCwId: number | null;
|
||||
public statusName: string | null;
|
||||
public statusCwId: number | null;
|
||||
public priorityName: string | null;
|
||||
public priorityCwId: number | null;
|
||||
public ratingName: string | null;
|
||||
public ratingCwId: number | null;
|
||||
public source: string | null;
|
||||
public campaignName: string | null;
|
||||
public campaignCwId: number | null;
|
||||
|
||||
public primarySalesRepName: string | null;
|
||||
public primarySalesRepIdentifier: string | null;
|
||||
public primarySalesRepCwId: number | null;
|
||||
public secondarySalesRepName: string | null;
|
||||
public secondarySalesRepIdentifier: string | null;
|
||||
public secondarySalesRepCwId: number | null;
|
||||
|
||||
public companyCwId: number | null;
|
||||
public companyName: string | null;
|
||||
public contactCwId: number | null;
|
||||
public contactName: string | null;
|
||||
public siteCwId: number | null;
|
||||
public siteName: string | null;
|
||||
public customerPO: string | null;
|
||||
|
||||
public totalSalesTax: number;
|
||||
|
||||
public locationName: string | null;
|
||||
public locationCwId: number | null;
|
||||
public departmentName: string | null;
|
||||
public departmentCwId: number | null;
|
||||
|
||||
public expectedCloseDate: Date | null;
|
||||
public pipelineChangeDate: Date | null;
|
||||
public dateBecameLead: Date | null;
|
||||
public closedDate: Date | null;
|
||||
public closedFlag: boolean;
|
||||
public closedByName: string | null;
|
||||
public closedByCwId: number | null;
|
||||
|
||||
public companyId: string | null;
|
||||
public cwLastUpdated: Date | null;
|
||||
|
||||
public readonly createdAt: Date;
|
||||
public updatedAt: Date;
|
||||
|
||||
constructor(data: Opportunity) {
|
||||
this.id = data.id;
|
||||
this.cwOpportunityId = data.cwOpportunityId;
|
||||
this.name = data.name;
|
||||
this.notes = data.notes;
|
||||
|
||||
this.typeName = data.typeName;
|
||||
this.typeCwId = data.typeCwId;
|
||||
this.stageName = data.stageName;
|
||||
this.stageCwId = data.stageCwId;
|
||||
this.statusName = data.statusName;
|
||||
this.statusCwId = data.statusCwId;
|
||||
this.priorityName = data.priorityName;
|
||||
this.priorityCwId = data.priorityCwId;
|
||||
this.ratingName = data.ratingName;
|
||||
this.ratingCwId = data.ratingCwId;
|
||||
this.source = data.source;
|
||||
this.campaignName = data.campaignName;
|
||||
this.campaignCwId = data.campaignCwId;
|
||||
|
||||
this.primarySalesRepName = data.primarySalesRepName;
|
||||
this.primarySalesRepIdentifier = data.primarySalesRepIdentifier;
|
||||
this.primarySalesRepCwId = data.primarySalesRepCwId;
|
||||
this.secondarySalesRepName = data.secondarySalesRepName;
|
||||
this.secondarySalesRepIdentifier = data.secondarySalesRepIdentifier;
|
||||
this.secondarySalesRepCwId = data.secondarySalesRepCwId;
|
||||
|
||||
this.companyCwId = data.companyCwId;
|
||||
this.companyName = data.companyName;
|
||||
this.contactCwId = data.contactCwId;
|
||||
this.contactName = data.contactName;
|
||||
this.siteCwId = data.siteCwId;
|
||||
this.siteName = data.siteName;
|
||||
this.customerPO = data.customerPO;
|
||||
|
||||
this.totalSalesTax = data.totalSalesTax;
|
||||
|
||||
this.locationName = data.locationName;
|
||||
this.locationCwId = data.locationCwId;
|
||||
this.departmentName = data.departmentName;
|
||||
this.departmentCwId = data.departmentCwId;
|
||||
|
||||
this.expectedCloseDate = data.expectedCloseDate;
|
||||
this.pipelineChangeDate = data.pipelineChangeDate;
|
||||
this.dateBecameLead = data.dateBecameLead;
|
||||
this.closedDate = data.closedDate;
|
||||
this.closedFlag = data.closedFlag;
|
||||
this.closedByName = data.closedByName;
|
||||
this.closedByCwId = data.closedByCwId;
|
||||
|
||||
this.companyId = data.companyId;
|
||||
this.cwLastUpdated = data.cwLastUpdated;
|
||||
|
||||
this.createdAt = data.createdAt;
|
||||
this.updatedAt = data.updatedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh from ConnectWise
|
||||
*
|
||||
* Fetches the latest opportunity data from CW and updates
|
||||
* the local database record and controller state.
|
||||
*/
|
||||
public async refreshFromCW(): Promise<OpportunityController> {
|
||||
const cwData = await fetchOpportunity(this.cwOpportunityId);
|
||||
const mapped = OpportunityController.mapCwToDb(cwData);
|
||||
|
||||
const updated = await prisma.opportunity.update({
|
||||
where: { id: this.id },
|
||||
data: mapped,
|
||||
});
|
||||
|
||||
return new OpportunityController(updated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch raw CW data
|
||||
*
|
||||
* Returns the raw ConnectWise opportunity object without updating the DB.
|
||||
*/
|
||||
public async fetchCwData(): Promise<CWOpportunity> {
|
||||
return fetchOpportunity(this.cwOpportunityId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map CW Opportunity → Prisma create/update payload
|
||||
*
|
||||
* Static helper used by both the controller and the refresh sync.
|
||||
*/
|
||||
public static mapCwToDb(item: CWOpportunity) {
|
||||
return {
|
||||
name: item.name,
|
||||
notes: item.notes ?? null,
|
||||
|
||||
typeName: item.type?.name ?? null,
|
||||
typeCwId: item.type?.id ?? null,
|
||||
stageName: item.stage?.name ?? null,
|
||||
stageCwId: item.stage?.id ?? null,
|
||||
statusName: item.status?.name ?? null,
|
||||
statusCwId: item.status?.id ?? null,
|
||||
priorityName: item.priority?.name ?? null,
|
||||
priorityCwId: item.priority?.id ?? null,
|
||||
ratingName: item.rating?.name ?? null,
|
||||
ratingCwId: item.rating?.id ?? null,
|
||||
source: item.source ?? null,
|
||||
campaignName: item.campaign?.name ?? null,
|
||||
campaignCwId: item.campaign?.id ?? null,
|
||||
|
||||
primarySalesRepName: item.primarySalesRep?.name ?? null,
|
||||
primarySalesRepIdentifier: item.primarySalesRep?.identifier ?? null,
|
||||
primarySalesRepCwId: item.primarySalesRep?.id ?? null,
|
||||
secondarySalesRepName: item.secondarySalesRep?.name ?? null,
|
||||
secondarySalesRepIdentifier: item.secondarySalesRep?.identifier ?? null,
|
||||
secondarySalesRepCwId: item.secondarySalesRep?.id ?? null,
|
||||
|
||||
companyCwId: item.company?.id ?? null,
|
||||
companyName: item.company?.name ?? null,
|
||||
contactCwId: item.contact?.id ?? null,
|
||||
contactName: item.contact?.name ?? null,
|
||||
siteCwId: item.site?.id ?? null,
|
||||
siteName: item.site?.name ?? null,
|
||||
customerPO: item.customerPO ?? null,
|
||||
|
||||
totalSalesTax: item.totalSalesTax ?? 0,
|
||||
|
||||
locationName: item.location?.name ?? null,
|
||||
locationCwId: item.location?.id ?? null,
|
||||
departmentName: item.department?.name ?? null,
|
||||
departmentCwId: item.department?.id ?? null,
|
||||
|
||||
expectedCloseDate: item.expectedCloseDate
|
||||
? new Date(item.expectedCloseDate)
|
||||
: null,
|
||||
pipelineChangeDate: item.pipelineChangeDate
|
||||
? new Date(item.pipelineChangeDate)
|
||||
: null,
|
||||
dateBecameLead: item.dateBecameLead
|
||||
? new Date(item.dateBecameLead)
|
||||
: null,
|
||||
closedDate: item.closedDate ? new Date(item.closedDate) : null,
|
||||
closedFlag: item.closedFlag ?? false,
|
||||
closedByName: item.closedBy?.name ?? null,
|
||||
closedByCwId: item.closedBy?.id ?? null,
|
||||
|
||||
cwLastUpdated: item._info?.lastUpdated
|
||||
? new Date(item._info.lastUpdated)
|
||||
: new Date(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* To JSON
|
||||
*
|
||||
* Serializes the opportunity into a safe, API-friendly object.
|
||||
*/
|
||||
public toJson(): Record<string, any> {
|
||||
return {
|
||||
id: this.id,
|
||||
cwOpportunityId: this.cwOpportunityId,
|
||||
name: this.name,
|
||||
notes: this.notes,
|
||||
type: this.typeCwId ? { id: this.typeCwId, name: this.typeName } : null,
|
||||
stage: this.stageCwId
|
||||
? { id: this.stageCwId, name: this.stageName }
|
||||
: null,
|
||||
status: this.statusCwId
|
||||
? { id: this.statusCwId, name: this.statusName }
|
||||
: null,
|
||||
priority: this.priorityCwId
|
||||
? { id: this.priorityCwId, name: this.priorityName }
|
||||
: null,
|
||||
rating: this.ratingCwId
|
||||
? { id: this.ratingCwId, name: this.ratingName }
|
||||
: null,
|
||||
source: this.source,
|
||||
campaign: this.campaignCwId
|
||||
? { id: this.campaignCwId, name: this.campaignName }
|
||||
: null,
|
||||
primarySalesRep: this.primarySalesRepCwId
|
||||
? {
|
||||
id: this.primarySalesRepCwId,
|
||||
identifier: this.primarySalesRepIdentifier,
|
||||
name: this.primarySalesRepName,
|
||||
}
|
||||
: null,
|
||||
secondarySalesRep: this.secondarySalesRepCwId
|
||||
? {
|
||||
id: this.secondarySalesRepCwId,
|
||||
identifier: this.secondarySalesRepIdentifier,
|
||||
name: this.secondarySalesRepName,
|
||||
}
|
||||
: null,
|
||||
company: this.companyCwId
|
||||
? { id: this.companyCwId, name: this.companyName }
|
||||
: null,
|
||||
contact: this.contactCwId
|
||||
? { id: this.contactCwId, name: this.contactName }
|
||||
: null,
|
||||
site: this.siteCwId ? { id: this.siteCwId, name: this.siteName } : null,
|
||||
customerPO: this.customerPO,
|
||||
totalSalesTax: this.totalSalesTax,
|
||||
location: this.locationCwId
|
||||
? { id: this.locationCwId, name: this.locationName }
|
||||
: null,
|
||||
department: this.departmentCwId
|
||||
? { id: this.departmentCwId, name: this.departmentName }
|
||||
: null,
|
||||
expectedCloseDate: this.expectedCloseDate,
|
||||
pipelineChangeDate: this.pipelineChangeDate,
|
||||
dateBecameLead: this.dateBecameLead,
|
||||
closedDate: this.closedDate,
|
||||
closedFlag: this.closedFlag,
|
||||
closedBy: this.closedByCwId
|
||||
? { id: this.closedByCwId, name: this.closedByName }
|
||||
: null,
|
||||
companyId: this.companyId,
|
||||
cwLastUpdated: this.cwLastUpdated,
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -178,6 +178,46 @@ export default class UserController {
|
||||
return decoded.permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read Role Permissions
|
||||
*
|
||||
* Verifies and decodes a role permissions JWT and returns the permission nodes.
|
||||
* Returns an empty array if verification fails.
|
||||
*
|
||||
* @param role - Role record containing the signed permissions token
|
||||
* @returns {string[]} The role permission nodes
|
||||
*/
|
||||
private _readRolePermissions(role: Role): string[] {
|
||||
try {
|
||||
const decoded = jwt.verify(role.permissions, permissionsPrivateKey, {
|
||||
algorithms: ["RS256"],
|
||||
issuer: "roles",
|
||||
subject: role.id,
|
||||
}) as DecodedPermissionsBlock;
|
||||
|
||||
return decoded.permissions;
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read All Permissions
|
||||
*
|
||||
* Aggregates the user's direct permissions and all permissions from their assigned roles
|
||||
* into a single deduplicated array.
|
||||
*
|
||||
* @returns {Promise<string[]>} Combined array of all permission nodes
|
||||
*/
|
||||
public async readAllPermissions(): Promise<string[]> {
|
||||
const directPermissions = this.readPermissions();
|
||||
const rolePermissions = this._roles
|
||||
.map((role) => this._readRolePermissions(role))
|
||||
.flatMap((permissions) => permissions);
|
||||
|
||||
return [...new Set([...directPermissions, ...rolePermissions])];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch Roles
|
||||
*
|
||||
@@ -262,7 +302,16 @@ export default class UserController {
|
||||
: this._roles.size > 0
|
||||
? this._roles.map((v) => v.moniker)
|
||||
: undefined,
|
||||
permissions: opts?.safeReturn ? undefined : this.readPermissions(),
|
||||
permissions: opts?.safeReturn
|
||||
? undefined
|
||||
: (() => {
|
||||
const directPermissions = this.readPermissions();
|
||||
const rolePermissions = this._roles
|
||||
.map((role) => this._readRolePermissions(role))
|
||||
.flatMap((permissions) => permissions);
|
||||
|
||||
return [...new Set([...directPermissions, ...rolePermissions])];
|
||||
})(),
|
||||
login: opts?.safeReturn ? undefined : this.login,
|
||||
email: opts?.safeReturn ? undefined : this.email,
|
||||
image: this.image,
|
||||
|
||||
Reference in New Issue
Block a user