From 2feca7ef2ee03ddb18a9c88e02dd5b432dd4fe32 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 30 Jan 2024 18:11:21 +0400 Subject: [PATCH 1/7] Add project DAL --- backend/src/server/routes/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 16d32aada..f6971b7ea 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -236,6 +236,7 @@ export const registerRoutes = async ( orgDAL, incidentContactDAL, tokenService, + projectDAL, smtpService, userDAL, orgBotDAL From f180b5ed6a62845699c201d3e6ec66e9e72ad7e2 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 30 Jan 2024 18:11:32 +0400 Subject: [PATCH 2/7] Route --- .../server/routes/v2/organization-router.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/backend/src/server/routes/v2/organization-router.ts b/backend/src/server/routes/v2/organization-router.ts index ed4a894c5..d2fa7d450 100644 --- a/backend/src/server/routes/v2/organization-router.ts +++ b/backend/src/server/routes/v2/organization-router.ts @@ -46,6 +46,42 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { } }); + server.route({ + method: "GET", + url: "/:organizationId/workspaces", + schema: { + params: z.object({ + organizationId: z.string().trim() + }), + response: { + 200: z.object({ + workspaces: z + .object({ + name: z.string(), + organization: z.string(), + environments: z + .object({ + name: z.string(), + slug: z.string() + }) + .array() + }) + .array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const workspaces = await server.services.org.findAllWorkspaces({ + actor: req.permission.type, + actorId: req.permission.id, + orgId: req.params.organizationId + }); + + return { workspaces }; + } + }); + server.route({ method: "PATCH", url: "/:organizationId/memberships/:membershipId", From b73987d2c24d221d999ab8eaa7a44ffa309c6244 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 30 Jan 2024 18:12:37 +0400 Subject: [PATCH 3/7] DTO --- backend/src/services/org/org-types.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/backend/src/services/org/org-types.ts b/backend/src/services/org/org-types.ts index 6456e5de1..b9dbd74ad 100644 --- a/backend/src/services/org/org-types.ts +++ b/backend/src/services/org/org-types.ts @@ -1,3 +1,5 @@ +import { ActorType } from "../auth/auth-type"; + export type TUpdateOrgMembershipDTO = { userId: string; orgId: string; @@ -22,3 +24,9 @@ export type TVerifyUserToOrgDTO = { orgId: string; code: string; }; + +export type TFindAllWorkspacesDTO = { + actor: ActorType; + actorId: string; + orgId: string; +}; From e38dd9e2759b36b385eed3c6848bfa4eae21a059 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 30 Jan 2024 18:13:20 +0400 Subject: [PATCH 4/7] Find projects by identity --- backend/src/services/project/project-dal.ts | 59 ++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/backend/src/services/project/project-dal.ts b/backend/src/services/project/project-dal.ts index a82fbc3af..87a10b2e7 100644 --- a/backend/src/services/project/project-dal.ts +++ b/backend/src/services/project/project-dal.ts @@ -30,7 +30,7 @@ export const projectDALFactory = (db: TDbClient) => { db.ref("name").withSchema(TableName.Environment).as("envName") ) .orderBy("createdAt", "asc", "last"); - return sqlNestRelationships({ + const nestedWorkspaces = sqlNestRelationships({ data: workspaces, key: "id", parentMapper: ({ _id, ...el }) => ({ _id, ...ProjectsSchema.parse(el) }), @@ -46,11 +46,67 @@ export const projectDALFactory = (db: TDbClient) => { } ] }); + + return nestedWorkspaces.map((workspace) => ({ + ...workspace, + organization: workspace.id + })); } catch (error) { throw new DatabaseError({ error, name: "Find all projects" }); } }; + const findAllProjectsByIdentity = async (identityId: string) => { + try { + const workspaces = await db(TableName.IdentityProjectMembership) + .where({ identityId }) + .join( + TableName.Project, + `${TableName.IdentityProjectMembership}.projectId`, + `${TableName.Project}.id` + ) + .leftJoin( + TableName.Environment, + `${TableName.Environment}.projectId`, + `${TableName.Project}.id` + ) + .select( + selectAllTableCols(TableName.Project), + db.ref("id").withSchema(TableName.Project).as("_id"), + db.ref("id").withSchema(TableName.Environment).as("envId"), + db.ref("slug").withSchema(TableName.Environment).as("envSlug"), + db.ref("name").withSchema(TableName.Environment).as("envName") + ) + .orderBy("createdAt", "asc", "last"); + + const nestedWorkspaces = sqlNestRelationships({ + data: workspaces, + key: "id", + parentMapper: ({ _id, ...el }) => ({ _id, ...ProjectsSchema.parse(el) }), + childrenMapper: [ + { + key: "envId", + label: "environments" as const, + mapper: ({ envId: id, envSlug: slug, envName: name }) => ({ + id, + slug, + name + }) + } + ] + }); + + // we need to add "organization" to each workspace entry + + return nestedWorkspaces.map((workspace) => ({ + ...workspace, + organization: workspace.id + })); + } catch (error) { + throw new DatabaseError({ error, name: "Find all projects by identity" }); + } + }; + const findProjectById = async (id: string) => { try { const workspaces = await db(TableName.ProjectMembership) @@ -96,6 +152,7 @@ export const projectDALFactory = (db: TDbClient) => { return { ...projectOrm, findAllProjects, + findAllProjectsByIdentity, findProjectById }; }; From cae0c9afdb9eb58eeebcd5f322d353fb1f0b2aa3 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 30 Jan 2024 18:17:52 +0400 Subject: [PATCH 5/7] findAllWorkspaces operation --- backend/src/services/org/org-service.ts | 39 ++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/backend/src/services/org/org-service.ts b/backend/src/services/org/org-service.ts index 4587c3814..69772e883 100644 --- a/backend/src/services/org/org-service.ts +++ b/backend/src/services/org/org-service.ts @@ -3,6 +3,7 @@ import slugify from "@sindresorhus/slugify"; import jwt from "jsonwebtoken"; import { OrgMembershipRole, OrgMembershipStatus } from "@app/db/schemas"; +import { TProjects } from "@app/db/schemas/projects"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { OrgPermissionActions, @@ -17,9 +18,10 @@ import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; import { alphaNumericNanoId } from "@app/lib/nanoid"; import { isDisposableEmail } from "@app/lib/validator"; -import { AuthMethod, AuthTokenType } from "../auth/auth-type"; +import { ActorType, AuthMethod, AuthTokenType } from "../auth/auth-type"; import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service"; import { TokenType } from "../auth-token/auth-token-types"; +import { TProjectDALFactory } from "../project/project-dal"; import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service"; import { TUserDALFactory } from "../user/user-dal"; import { TIncidentContactsDALFactory } from "./incident-contacts-dal"; @@ -28,6 +30,7 @@ import { TOrgDALFactory } from "./org-dal"; import { TOrgRoleDALFactory } from "./org-role-dal"; import { TDeleteOrgMembershipDTO, + TFindAllWorkspacesDTO, TInviteUserToOrgDTO, TUpdateOrgMembershipDTO, TVerifyUserToOrgDTO @@ -38,6 +41,7 @@ type TOrgServiceFactoryDep = { orgBotDAL: TOrgBotDALFactory; orgRoleDAL: TOrgRoleDALFactory; userDAL: TUserDALFactory; + projectDAL: TProjectDALFactory; incidentContactDAL: TIncidentContactsDALFactory; samlConfigDAL: Pick; smtpService: TSmtpService; @@ -58,6 +62,7 @@ export const orgServiceFactory = ({ incidentContactDAL, permissionService, smtpService, + projectDAL, tokenService, orgBotDAL, licenseService, @@ -93,6 +98,37 @@ export const orgServiceFactory = ({ const members = await orgDAL.findAllOrgMembers(orgId); return members; }; + + const findAllWorkspaces = async ({ actor, actorId, orgId }: TFindAllWorkspacesDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + ForbiddenError.from(permission).throwUnlessCan( + OrgPermissionActions.Read, + OrgPermissionSubjects.Workspace + ); + + const organizationWorkspaceIds = new Set( + (await projectDAL.find({ orgId })).map((workspace) => workspace.id) + ); + + let workspaces: (TProjects & { organization: string } & { + environments: { + id: string; + slug: string; + name: string; + }[]; + })[]; + + if (actor === ActorType.USER) { + workspaces = await projectDAL.findAllProjects(actorId); + } else if (actor === ActorType.IDENTITY) { + workspaces = await projectDAL.findAllProjectsByIdentity(actorId); + } else { + throw new BadRequestError({ message: "Invalid actor type" }); + } + + return workspaces.filter((workspace) => organizationWorkspaceIds.has(workspace.id)); + }; + /* * Update organization settings * */ @@ -459,6 +495,7 @@ export const orgServiceFactory = ({ createOrganization, deleteOrganizationById, deleteOrgMembership, + findAllWorkspaces, updateOrgMembership, // incident contacts findIncidentContacts, From 5a4ed1dbe61018735ecd814378e8dd2149c28f9d Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 30 Jan 2024 18:31:05 +0400 Subject: [PATCH 6/7] Update project-dal.ts --- backend/src/services/project/project-dal.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/src/services/project/project-dal.ts b/backend/src/services/project/project-dal.ts index 87a10b2e7..6598613d1 100644 --- a/backend/src/services/project/project-dal.ts +++ b/backend/src/services/project/project-dal.ts @@ -96,8 +96,7 @@ export const projectDALFactory = (db: TDbClient) => { ] }); - // we need to add "organization" to each workspace entry - + // We need to add the organization field, as it's for one of our API endpoint responses. return nestedWorkspaces.map((workspace) => ({ ...workspace, organization: workspace.id From d12f7752029dc4f1159550c2af3cf73acfd2fde9 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 30 Jan 2024 18:31:22 +0400 Subject: [PATCH 7/7] Update project-dal.ts --- backend/src/services/project/project-dal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/services/project/project-dal.ts b/backend/src/services/project/project-dal.ts index 6598613d1..c286c379f 100644 --- a/backend/src/services/project/project-dal.ts +++ b/backend/src/services/project/project-dal.ts @@ -96,7 +96,7 @@ export const projectDALFactory = (db: TDbClient) => { ] }); - // We need to add the organization field, as it's for one of our API endpoint responses. + // We need to add the organization field, as it's required for one of our API endpoint responses. return nestedWorkspaces.map((workspace) => ({ ...workspace, organization: workspace.id