feat: fetch specific user group memberships

This commit is contained in:
Daniel Hougaard
2024-09-08 19:25:48 +04:00
parent 7b19d2aa6a
commit 556ae168dd
4 changed files with 176 additions and 2 deletions

View File

@@ -464,6 +464,7 @@ export const registerRoutes = async (
userAliasDAL,
orgMembershipDAL,
tokenService,
groupProjectDAL,
smtpService,
projectMembershipDAL
});

View File

@@ -1,6 +1,6 @@
import { z } from "zod";
import { UserEncryptionKeysSchema, UsersSchema } from "@app/db/schemas";
import { ProjectsSchema, UserEncryptionKeysSchema, UsersSchema } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { logger } from "@app/lib/logger";
import { authRateLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
@@ -134,4 +134,53 @@ export const registerUserRouter = async (server: FastifyZodProvider) => {
);
}
});
server.route({
method: "GET",
url: "/me/:username/groups",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
username: z.string().trim()
}),
response: {
200: z
.object({
id: z.string(),
name: z.string(),
slug: z.string(),
orgId: z.string(),
projectMemberships: z.array(
z.object({
id: z.string(),
project: ProjectsSchema.pick({ id: true, name: true, slug: true }),
roles: z.array(
z.object({
id: z.string(),
role: z.string(),
customRoleId: z.string().nullable(),
customRoleName: z.string().nullable(),
customRoleSlug: z.string().nullable(),
temporaryRange: z.string().nullable(),
temporaryMode: z.string().nullable(),
temporaryAccessEndTime: z.string().nullable(),
temporaryAccessStartTime: z.string().nullable(),
isTemporary: z.boolean()
})
)
})
)
})
.array()
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const groupMemberships = await server.services.user.listUserGroups(req.params.username, req.permission.orgId);
return groupMemberships;
}
});
};

View File

@@ -95,6 +95,116 @@ export const groupProjectDALFactory = (db: TDbClient) => {
}
};
const findByUserId = async (userId: string, orgId: string, tx?: Knex) => {
try {
const docs = await (tx || db.replicaNode())(TableName.UserGroupMembership)
.where(`${TableName.UserGroupMembership}.userId`, userId)
.join(TableName.Groups, function () {
this.on(`${TableName.UserGroupMembership}.groupId`, "=", `${TableName.Groups}.id`).andOn(
`${TableName.Groups}.orgId`,
"=",
db.raw("?", [orgId])
);
})
.leftJoin(
TableName.GroupProjectMembership,
`${TableName.GroupProjectMembership}.groupId`,
`${TableName.Groups}.id`
)
.leftJoin(
TableName.GroupProjectMembershipRole,
`${TableName.GroupProjectMembershipRole}.projectMembershipId`,
`${TableName.GroupProjectMembership}.id`
)
.leftJoin(
TableName.ProjectRoles,
`${TableName.GroupProjectMembershipRole}.customRoleId`,
`${TableName.ProjectRoles}.id`
)
.leftJoin(TableName.Project, `${TableName.GroupProjectMembership}.projectId`, `${TableName.Project}.id`)
.select(
db.ref("id").withSchema(TableName.Groups).as("groupId"),
db.ref("name").withSchema(TableName.Groups).as("groupName"),
db.ref("slug").withSchema(TableName.Groups).as("groupSlug"),
db.ref("orgId").withSchema(TableName.Groups),
db.ref("id").withSchema(TableName.GroupProjectMembership).as("projectMembershipId"),
db.ref("id").withSchema(TableName.Project).as("projectId"),
db.ref("name").withSchema(TableName.Project).as("projectName"),
db.ref("slug").withSchema(TableName.Project).as("projectSlug"),
db.ref("role").withSchema(TableName.GroupProjectMembershipRole),
db.ref("id").withSchema(TableName.GroupProjectMembershipRole).as("membershipRoleId"),
db.ref("customRoleId").withSchema(TableName.GroupProjectMembershipRole),
db.ref("name").withSchema(TableName.ProjectRoles).as("customRoleName"),
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug"),
db.ref("temporaryMode").withSchema(TableName.GroupProjectMembershipRole),
db.ref("isTemporary").withSchema(TableName.GroupProjectMembershipRole),
db.ref("temporaryRange").withSchema(TableName.GroupProjectMembershipRole),
db.ref("temporaryAccessStartTime").withSchema(TableName.GroupProjectMembershipRole),
db.ref("temporaryAccessEndTime").withSchema(TableName.GroupProjectMembershipRole)
);
const groupsWithProjects = sqlNestRelationships({
data: docs,
parentMapper: ({ groupId, groupName, groupSlug, orgId: organizationId }) => ({
id: groupId,
name: groupName,
slug: groupSlug,
orgId: organizationId,
projectMemberships: []
}),
key: "groupId",
childrenMapper: [
{
label: "projectMemberships" as const,
key: "projectMembershipId",
mapper: ({ projectId, projectName, projectSlug, projectMembershipId }) => ({
id: projectMembershipId,
project: {
id: projectId,
name: projectName,
slug: projectSlug
},
roles: []
}),
childrenMapper: [
{
label: "roles" as const,
key: "membershipRoleId",
mapper: ({
role,
customRoleId,
customRoleName,
customRoleSlug,
membershipRoleId,
temporaryRange,
temporaryMode,
temporaryAccessEndTime,
temporaryAccessStartTime,
isTemporary
}) => ({
id: membershipRoleId,
role,
customRoleId,
customRoleName,
customRoleSlug,
temporaryRange,
temporaryMode,
temporaryAccessEndTime,
temporaryAccessStartTime,
isTemporary
})
}
]
}
]
});
return groupsWithProjects;
} catch (error) {
throw new DatabaseError({ error, name: "FindByUserId" });
}
};
// The GroupProjectMembership table has a reference to the project (projectId) AND the group (groupId).
// We need to join the GroupProjectMembership table with the Groups table to get the group name and slug.
// We also need to join the GroupProjectMembershipRole table to get the role of the group in the project.
@@ -197,5 +307,5 @@ export const groupProjectDALFactory = (db: TDbClient) => {
return members;
};
return { ...groupProjectOrm, findByProjectId, findAllProjectGroupMembers };
return { ...groupProjectOrm, findByProjectId, findByUserId, findAllProjectGroupMembers };
};

View File

@@ -8,6 +8,7 @@ import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
import { TUserAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
import { AuthMethod } from "../auth/auth-type";
import { TGroupProjectDALFactory } from "../group-project/group-project-dal";
import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal";
import { TUserDALFactory } from "./user-dal";
@@ -27,6 +28,7 @@ type TUserServiceFactoryDep = {
| "delete"
>;
userAliasDAL: Pick<TUserAliasDALFactory, "find" | "insertMany">;
groupProjectDAL: Pick<TGroupProjectDALFactory, "findByUserId">;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "find" | "insertMany" | "findOne" | "updateById">;
tokenService: Pick<TAuthTokenServiceFactory, "createTokenForUser" | "validateTokenForUser">;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find">;
@@ -40,6 +42,7 @@ export const userServiceFactory = ({
userAliasDAL,
orgMembershipDAL,
projectMembershipDAL,
groupProjectDAL,
tokenService,
smtpService
}: TUserServiceFactoryDep) => {
@@ -295,6 +298,16 @@ export const userServiceFactory = ({
return updatedOrgMembership.projectFavorites;
};
const listUserGroups = async (username: string, orgId: string) => {
const user = await userDAL.findOne({
username
});
const memberships = await groupProjectDAL.findByUserId(user.id, orgId);
return memberships;
};
return {
sendEmailVerificationCode,
verifyEmailVerificationCode,
@@ -304,6 +317,7 @@ export const userServiceFactory = ({
deleteUser,
getMe,
createUserAction,
listUserGroups,
getUserAction,
unlockUser,
getUserPrivateKey,