feat: completed all role routers

This commit is contained in:
=
2025-10-09 23:58:10 +05:30
parent 81b7ea1a5a
commit e232282bea
21 changed files with 581 additions and 347 deletions
@@ -0,0 +1,88 @@
import { NotFoundError } from "@app/lib/errors";
import { TMembershipDALFactory } from "../membership/membership-dal";
import { TProjectDALFactory } from "../project/project-dal";
import { AccessScope, AccessScopeData } from "@app/db/schemas";
type TConvertorServiceFactoryDep = {
projectDAL: Pick<TProjectDALFactory, "findOne">;
membershipDAL: Pick<TMembershipDALFactory, "findOne">;
};
export type TConvertorServiceFactory = ReturnType<typeof convertorServiceFactory>;
export const convertorServiceFactory = ({ projectDAL, membershipDAL }: TConvertorServiceFactoryDep) => {
const $getScopeDatabaseFields = (scopeData: AccessScopeData) => {
if (scopeData.scope === AccessScope.Organization) {
return { scopeOrgId: scopeData.orgId };
}
if (scopeData.scope === AccessScope.Namespace) {
return { scopeNamespaceId: scopeData.namespaceId, scopeOrgId: scopeData.orgId };
}
return { scopeProjectId: scopeData.projectId, scopeOrgId: scopeData.orgId };
};
const projectSlugToId = async (dto: { slug: string; orgId: string }) => {
const project = await projectDAL.findOne({
orgId: dto.orgId,
slug: dto.slug
});
if (!project) throw new NotFoundError({ message: `Project with slug ${slug} not found` });
return project;
};
const userMembershipIdToUserId = async (scopeData: AccessScopeData, membershipId: string) => {
const dbFields = $getScopeDatabaseFields(scopeData);
const membership = await membershipDAL.findOne({
scope: scopeData.scope,
id: membershipId,
...dbFields,
scopeOrgId: scopeData.orgId
});
if (!membership || !membership.actorUserId) {
throw new NotFoundError({ message: `Membership with id ${membershipId} not found` });
}
return membership.actorUserId;
};
const groupMembershipIdToGroupId = async (scopeData: AccessScopeData, membershipId: string) => {
const dbFields = $getScopeDatabaseFields(scopeData);
const membership = await membershipDAL.findOne({
scope: scopeData.scope,
id: membershipId,
...dbFields,
scopeOrgId: scopeData.orgId
});
if (!membership || !membership.actorGroupId) {
throw new NotFoundError({ message: `Membership with id ${membershipId} not found` });
}
return membership.actorGroupId;
};
const identityMembershipIdToGroupId = async (scopeData: AccessScopeData, membershipId: string) => {
const dbFields = $getScopeDatabaseFields(scopeData);
const membership = await membershipDAL.findOne({
scope: scopeData.scope,
id: membershipId,
...dbFields,
scopeOrgId: scopeData.orgId
});
if (!membership || !membership.actorIdentityId) {
throw new NotFoundError({ message: `Membership with id ${membershipId} not found` });
}
return membership.actorIdentityId;
};
return {
projectSlugToId,
userMembershipIdToUserId,
groupMembershipIdToGroupId,
identityMembershipIdToGroupId
};
};
@@ -43,10 +43,7 @@ type TGroupProjectServiceFactoryDep = {
projectRoleDAL: Pick<TProjectRoleDALFactory, "find">;
projectBotDAL: TProjectBotDALFactory;
groupDAL: Pick<TGroupDALFactory, "findOne" | "findAllGroupPossibleMembers">;
permissionService: Pick<
TPermissionServiceFactory,
"getProjectPermission" | "getProjectPermissionByRole" | "invalidateProjectPermissionCache"
>;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getProjectPermissionByRoles" | "invalidateProjectPermissionCache">;
};
export type TGroupProjectServiceFactory = ReturnType<typeof groupProjectServiceFactory>;
@@ -115,29 +115,27 @@ export const newProjectMembershipIdentityFactory = ({
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(dto.permission.orgId);
const permissionRoles = await permissionService.getProjectPermissionByRoles(
dto.data.roles.map((el) => el.role),
dto.data.roles.filter((el) => el.role !== ProjectMembershipRole.NoAccess).map((el) => el.role),
scope.value
);
for (const permissionRole of permissionRoles) {
if (permissionRole?.role?.name !== ProjectMembershipRole.NoAccess) {
const permissionBoundary = validatePrivilegeChangeOperation(
shouldUseNewPrivilegeSystem,
ProjectPermissionIdentityActions.GrantPrivileges,
ProjectPermissionSub.Identity,
permission,
permissionRole.permission
);
if (!permissionBoundary.isValid)
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to create identity project membership",
shouldUseNewPrivilegeSystem,
ProjectPermissionIdentityActions.GrantPrivileges,
ProjectPermissionSub.Identity
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
}
const permissionBoundary = validatePrivilegeChangeOperation(
shouldUseNewPrivilegeSystem,
ProjectPermissionIdentityActions.GrantPrivileges,
ProjectPermissionSub.Identity,
permission,
permissionRole.permission
);
if (!permissionBoundary.isValid)
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to create identity project membership",
shouldUseNewPrivilegeSystem,
ProjectPermissionIdentityActions.GrantPrivileges,
ProjectPermissionSub.Identity
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
}
};
@@ -44,6 +44,7 @@ export const membershipUserDALFactory = (db: TDbClient) => {
})
.where(`${TableName.Membership}.scopeOrgId`, scopeData.orgId)
.where(`${TableName.Membership}.actorUserId`, userId)
.where(`${TableName.Users}.isGhost`, false)
.where((qb) => {
if (scopeData.scope === AccessScope.Organization) {
void qb.where(`${TableName.Membership}.scope`, AccessScope.Organization);
@@ -60,7 +61,9 @@ export const membershipUserDALFactory = (db: TDbClient) => {
})
.select(selectAllTableCols(TableName.Membership))
.select(
db.ref("slug").withSchema(TableName.Role).as("roleSlug"),
db.ref("slug").withSchema(TableName.Role).as("customRoleSlug"),
db.ref("name").withSchema(TableName.Role).as("customRoleName"),
db.ref("id").withSchema(TableName.Role).as("customRoleId"),
db.ref("id").withSchema(TableName.MembershipRole).as("membershipRoleId"),
db.ref("role").withSchema(TableName.MembershipRole).as("membershipRole"),
db.ref("temporaryMode").withSchema(TableName.MembershipRole).as("membershipRoleTemporaryMode"),
@@ -78,19 +81,35 @@ export const membershipUserDALFactory = (db: TDbClient) => {
db.ref("updatedAt").withSchema(TableName.MembershipRole).as("membershipRoleUpdatedAt"),
db.ref("id").withSchema(TableName.IdentityMetadata).as("metadataId"),
db.ref("key").withSchema(TableName.IdentityMetadata).as("metadataKey"),
db.ref("value").withSchema(TableName.IdentityMetadata).as("metadataValue")
db.ref("value").withSchema(TableName.IdentityMetadata).as("metadataValue"),
db.ref("username").withSchema(TableName.Users).as("userUsername"),
db.ref("email").withSchema(TableName.Users).as("userEmail"),
db.ref("firstName").withSchema(TableName.Users).as("userFirstName"),
db.ref("lastName").withSchema(TableName.Users).as("userLastName"),
db.ref("id").withSchema(TableName.Users).as("userId")
);
const data = sqlNestRelationships({
data: docs,
key: "id",
parentMapper: (el) => MembershipsSchema.parse(el),
parentMapper: (el) => ({
...MembershipsSchema.parse(el),
user: {
username: el.userUsername,
email: el.userEmail,
firstName: el.userFirstName,
lastName: el.userLastName,
id: el.userId
}
}),
childrenMapper: [
{
key: "membershipRoleId",
label: "roles" as const,
mapper: ({
roleSlug,
customRoleSlug,
customRoleName,
customRoleId,
membershipRoleId,
membershipRole,
membershipRoleIsTemporary,
@@ -103,7 +122,9 @@ export const membershipUserDALFactory = (db: TDbClient) => {
}) => ({
id: membershipRoleId,
role: membershipRole,
customRoleSlug: roleSlug,
customRoleSlug,
customRoleName,
customRoleId,
temporaryRange: membershipRoleTemporaryRange,
temporaryMode: membershipRoleTemporaryMode,
temporaryAccessStartTime: membershipRoleTemporaryAccessStartTime,
@@ -140,6 +161,7 @@ export const membershipUserDALFactory = (db: TDbClient) => {
.leftJoin(TableName.Role, `${TableName.MembershipRole}.customRoleId`, `${TableName.Role}.id`)
.distinct(`${TableName.Membership}.id`)
.where(`${TableName.Membership}.scopeOrgId`, scopeData.orgId)
.where(`${TableName.Users}.isGhost`, false)
.where((qb) => {
if (scopeData.scope === AccessScope.Organization) {
void qb.where(`${TableName.Membership}.scope`, AccessScope.Organization);
@@ -188,7 +210,9 @@ export const membershipUserDALFactory = (db: TDbClient) => {
.whereIn(`${TableName.Membership}.id`, paginatedUsers)
.select(selectAllTableCols(TableName.Membership))
.select(
db.ref("slug").withSchema(TableName.Role).as("roleSlug"),
db.ref("slug").withSchema(TableName.Role).as("customRoleSlug"),
db.ref("name").withSchema(TableName.Role).as("customRoleName"),
db.ref("id").withSchema(TableName.Role).as("customRoleId"),
db.ref("id").withSchema(TableName.MembershipRole).as("membershipRoleId"),
db.ref("role").withSchema(TableName.MembershipRole).as("membershipRole"),
db.ref("temporaryMode").withSchema(TableName.MembershipRole).as("membershipRoleTemporaryMode"),
@@ -203,7 +227,12 @@ export const membershipUserDALFactory = (db: TDbClient) => {
.withSchema(TableName.MembershipRole)
.as("membershipRoleTemporaryAccessEndTime"),
db.ref("createdAt").withSchema(TableName.MembershipRole).as("membershipRoleCreatedAt"),
db.ref("updatedAt").withSchema(TableName.MembershipRole).as("membershipRoleUpdatedAt")
db.ref("updatedAt").withSchema(TableName.MembershipRole).as("membershipRoleUpdatedAt"),
db.ref("username").withSchema(TableName.Users).as("userUsername"),
db.ref("email").withSchema(TableName.Users).as("userEmail"),
db.ref("firstName").withSchema(TableName.Users).as("userFirstName"),
db.ref("lastName").withSchema(TableName.Users).as("userLastName"),
db.ref("id").withSchema(TableName.Users).as("userId")
)
.select(
db.raw(
@@ -214,13 +243,24 @@ export const membershipUserDALFactory = (db: TDbClient) => {
const data = sqlNestRelationships({
data: docs,
key: "id",
parentMapper: (el) => MembershipsSchema.parse(el),
parentMapper: (el) => ({
...MembershipsSchema.parse(el),
user: {
username: el.userUsername,
email: el.userEmail,
firstName: el.userFirstName,
lastName: el.userLastName,
id: el.userId
}
}),
childrenMapper: [
{
key: "membershipRoleId",
label: "roles" as const,
mapper: ({
roleSlug,
customRoleSlug,
customRoleName,
customRoleId,
membershipRoleId,
membershipRole,
membershipRoleIsTemporary,
@@ -233,7 +273,9 @@ export const membershipUserDALFactory = (db: TDbClient) => {
}) => ({
id: membershipRoleId,
role: membershipRole,
customRoleSlug: roleSlug,
customRoleSlug,
customRoleName,
customRoleId,
temporaryRange: membershipRoleTemporaryRange,
temporaryMode: membershipRoleTemporaryMode,
temporaryAccessStartTime: membershipRoleTemporaryAccessStartTime,
@@ -386,7 +386,7 @@ export const membershipUserServiceFactory = ({
limit: dto.data.limit,
offset: dto.data.offset,
username: dto.data.username,
role: dto.data.roles.length
role: dto.data?.roles?.length
? {
[SearchResourceOperators.$in]: dto.data.roles
}
@@ -55,14 +55,11 @@ export type TUpdateMembershipUserDTO = {
export type TListMembershipUserDTO = {
permission: OrgServiceActor;
scopeData: AccessScopeData;
selector: {
actorId: string;
};
data: {
limit?: number;
offset?: number;
username?: string;
roles: string[];
roles?: string[];
};
};
@@ -43,10 +43,7 @@ import {
import { TProjectUserMembershipRoleDALFactory } from "./project-user-membership-role-dal";
type TProjectMembershipServiceFactoryDep = {
permissionService: Pick<
TPermissionServiceFactory,
"getProjectPermission" | "getProjectPermissionByRole" | "invalidateProjectPermissionCache"
>;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getProjectPermissionByRoles"| "invalidateProjectPermissionCache">;
smtpService: TSmtpService;
projectBotDAL: TProjectBotDALFactory;
projectMembershipDAL: TProjectMembershipDALFactory;
@@ -573,6 +570,8 @@ export const projectMembershipServiceFactory = ({
tx
);
// TODO(simp): likewise check in all services if i am missing anything
// Deletion should handle all cleanups like wise
await secretReminderRecipientsDAL.delete(
{
projectId,
+7 -6
View File
@@ -2,6 +2,7 @@ import { AccessScope, TableName } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
import { unpackPermissions } from "@app/server/routes/sanitizedSchema/permission";
import { newNamespaceRoleFactory } from "./namespace/namespace-role-factory";
import { newOrgRoleFactory } from "./org/org-role-factory";
@@ -63,7 +64,7 @@ export const roleServiceFactory = ({ roleDAL, permissionService }: TRoleServiceF
[scope.key]: scope.value
});
return { ...role, [scope.key]: scope.value };
return { ...role, [scope.key]: scope.value, permissions: unpackPermissions(role.permissions) };
};
const updateRole = async (dto: TUpdateRoleDTO) => {
@@ -99,7 +100,7 @@ export const roleServiceFactory = ({ roleDAL, permissionService }: TRoleServiceF
permissions: data?.permissions
});
return { ...role, [scope.key]: scope.value };
return { ...role, [scope.key]: scope.value, permissions: unpackPermissions(role.permissions) };
};
const deleteRole = async (dto: TDeleteRoleDTO) => {
@@ -119,7 +120,7 @@ export const roleServiceFactory = ({ roleDAL, permissionService }: TRoleServiceF
[scope.key]: scope.value
});
return { ...role, [scope.key]: scope.value };
return { ...role, [scope.key]: scope.value, permissions: unpackPermissions(role.permissions) };
};
const listRoles = async (dto: TListRoleDTO) => {
@@ -136,7 +137,7 @@ export const roleServiceFactory = ({ roleDAL, permissionService }: TRoleServiceF
{ limit: dto.data.limit, offset: dto.data.offset, sort: [[`${TableName.Role}.slug` as "slug", "asc"]] }
);
return { roles };
return { roles: roles.map((el) => ({ ...el, permissions: unpackPermissions(el.permissions) })) };
};
const getRoleById = async (dto: TGetRoleByIdDTO) => {
@@ -152,7 +153,7 @@ export const roleServiceFactory = ({ roleDAL, permissionService }: TRoleServiceF
});
if (!role) throw new NotFoundError({ message: `Role with id ${dto.selector.id} not found` });
return { ...role, [scope.key]: scope.value };
return { ...role, [scope.key]: scope.value, permissions: unpackPermissions(role.permissions) };
};
const getRoleBySlug = async (dto: TGetRoleBySlugDTO) => {
@@ -168,7 +169,7 @@ export const roleServiceFactory = ({ roleDAL, permissionService }: TRoleServiceF
});
if (!role) throw new NotFoundError({ message: `Role with slug ${dto.selector.slug} not found` });
return { ...role, [scope.key]: scope.value };
return { ...role, [scope.key]: scope.value, permissions: unpackPermissions(role.permissions) };
};
return {
+2 -2
View File
@@ -16,7 +16,7 @@ export type TCreateRoleDTO = {
scopeData: AccessScopeData;
data: {
name: string;
description?: string;
description?: string | null;
slug: string;
permissions: unknown;
};
@@ -30,7 +30,7 @@ export type TUpdateRoleDTO = {
};
data: Partial<{
name: string;
description?: string;
description?: string | null;
slug: string;
permissions: unknown;
}>;