From 47906c4dd4bb9d8b1745316dbcd650df49c1777b Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Tue, 12 Mar 2024 21:19:50 +0530 Subject: [PATCH] feat(server): removed role and roleid field dropping from project membership table for rolling forward migration --- .../migrations/20240308154949_temp-roles.ts | 26 ++-------- .../20240311093939_temp-role-identity.ts | 25 ++------- .../schemas/identity-project-memberships.ts | 2 + backend/src/db/schemas/project-memberships.ts | 4 +- backend/src/db/seeds/3-project.ts | 3 +- backend/src/db/seeds/4-machine-identity.ts | 3 +- .../ee/services/permission/permission-dal.ts | 16 +++++- .../routes/v1/project-membership-router.ts | 49 +++++++++--------- .../src/server/routes/v1/project-router.ts | 51 ++++++++++--------- .../identity-project-service.ts | 4 +- .../project-membership-service.ts | 6 ++- backend/src/services/project/project-queue.ts | 3 +- .../src/services/project/project-service.ts | 10 ++-- 13 files changed, 99 insertions(+), 103 deletions(-) diff --git a/backend/src/db/migrations/20240308154949_temp-roles.ts b/backend/src/db/migrations/20240308154949_temp-roles.ts index 462aec229..2c5a04f05 100644 --- a/backend/src/db/migrations/20240308154949_temp-roles.ts +++ b/backend/src/db/migrations/20240308154949_temp-roles.ts @@ -48,30 +48,14 @@ export async function up(knex: Knex): Promise { } } if (rows.length) await knex(TableName.ProjectUserMembershipRole).insert(rows); - await knex.schema.alterTable(TableName.ProjectMembership, (t) => { - t.dropColumn("roleId"); - t.dropColumn("role"); - }); + // will be dropped later + // await knex.schema.alterTable(TableName.ProjectMembership, (t) => { + // t.dropColumn("roleId"); + // t.dropColumn("role"); + // }); } export async function down(knex: Knex): Promise { - const projectUserMembershipRoleStream = knex.select("*").from(TableName.ProjectUserMembershipRole).stream(); - await knex.schema.alterTable(TableName.ProjectMembership, (t) => { - t.string("role"); - t.uuid("roleId"); - t.foreign("roleId").references("id").inTable(TableName.ProjectRoles); - }); - for await (const row of projectUserMembershipRoleStream) { - await knex(TableName.ProjectMembership).where({ id: row.projectMembershipId }).update({ - // @ts-ignore - since the latest one doesn't have roleId anymore there will be type error here - roleId: row.customRoleId, - role: row.role - }); - } - await knex.schema.alterTable(TableName.ProjectMembership, (t) => { - t.string("role").notNullable().alter({ alterNullable: true }); - }); - await knex.schema.dropTableIfExists(TableName.ProjectUserMembershipRole); await dropOnUpdateTrigger(knex, TableName.ProjectUserMembershipRole); } diff --git a/backend/src/db/migrations/20240311093939_temp-role-identity.ts b/backend/src/db/migrations/20240311093939_temp-role-identity.ts index b788bf259..e94827fe6 100644 --- a/backend/src/db/migrations/20240311093939_temp-role-identity.ts +++ b/backend/src/db/migrations/20240311093939_temp-role-identity.ts @@ -51,30 +51,13 @@ export async function up(knex: Knex): Promise { } } if(rows.length) await knex(TableName.IdentityProjectMembershipRole).insert(rows); - await knex.schema.alterTable(TableName.IdentityProjectMembership, (t) => { - t.dropColumn("roleId"); - t.dropColumn("role"); - }); + // await knex.schema.alterTable(TableName.IdentityProjectMembership, (t) => { + // t.dropColumn("roleId"); + // t.dropColumn("role"); + // }); } export async function down(knex: Knex): Promise { - const projectIdentityMembershipRoleStream = knex.select("*").from(TableName.IdentityProjectMembershipRole).stream(); - await knex.schema.alterTable(TableName.IdentityProjectMembership, (t) => { - t.string("role"); - t.uuid("roleId"); - t.foreign("roleId").references("id").inTable(TableName.ProjectRoles); - }); - for await (const row of projectIdentityMembershipRoleStream) { - await knex(TableName.IdentityProjectMembership).where({ id: row.projectMembershipId }).update({ - // @ts-ignore - since the latest one doesn't have roleId anymore there will be type error here - roleId: row.customRoleId, - role: row.role - }); - } - await knex.schema.alterTable(TableName.IdentityProjectMembership, (t) => { - t.string("role").notNullable().alter({ alterNullable: true }); - }); - await knex.schema.dropTableIfExists(TableName.IdentityProjectMembershipRole); await dropOnUpdateTrigger(knex, TableName.IdentityProjectMembershipRole); } diff --git a/backend/src/db/schemas/identity-project-memberships.ts b/backend/src/db/schemas/identity-project-memberships.ts index 2f17c36d8..276c9581e 100644 --- a/backend/src/db/schemas/identity-project-memberships.ts +++ b/backend/src/db/schemas/identity-project-memberships.ts @@ -9,6 +9,8 @@ import { TImmutableDBKeys } from "./models"; export const IdentityProjectMembershipsSchema = z.object({ id: z.string().uuid(), + role: z.string(), + roleId: z.string().uuid().nullable().optional(), projectId: z.string(), identityId: z.string().uuid(), createdAt: z.date(), diff --git a/backend/src/db/schemas/project-memberships.ts b/backend/src/db/schemas/project-memberships.ts index e522d6280..8576a318e 100644 --- a/backend/src/db/schemas/project-memberships.ts +++ b/backend/src/db/schemas/project-memberships.ts @@ -9,10 +9,12 @@ import { TImmutableDBKeys } from "./models"; export const ProjectMembershipsSchema = z.object({ id: z.string().uuid(), + role: z.string(), createdAt: z.date(), updatedAt: z.date(), userId: z.string().uuid(), - projectId: z.string() + projectId: z.string(), + roleId: z.string().uuid().nullable().optional() }); export type TProjectMemberships = z.infer; diff --git a/backend/src/db/seeds/3-project.ts b/backend/src/db/seeds/3-project.ts index 934130494..d41efb71c 100644 --- a/backend/src/db/seeds/3-project.ts +++ b/backend/src/db/seeds/3-project.ts @@ -33,7 +33,8 @@ export async function seed(knex: Knex): Promise { const projectMembership = await knex(TableName.ProjectMembership) .insert({ projectId: project.id, - userId: seedData1.id + userId: seedData1.id, + role: ProjectMembershipRole.Admin }) .returning("*"); await knex(TableName.ProjectUserMembershipRole).insert({ diff --git a/backend/src/db/seeds/4-machine-identity.ts b/backend/src/db/seeds/4-machine-identity.ts index 662232e02..618c47114 100644 --- a/backend/src/db/seeds/4-machine-identity.ts +++ b/backend/src/db/seeds/4-machine-identity.ts @@ -78,7 +78,8 @@ export async function seed(knex: Knex): Promise { const identityProjectMembership = await knex(TableName.IdentityProjectMembership) .insert({ identityId: seedData1.machineIdentity.id, - projectId: seedData1.project.id + projectId: seedData1.project.id, + role: ProjectMembershipRole.Admin }) .returning("*"); diff --git a/backend/src/ee/services/permission/permission-dal.ts b/backend/src/ee/services/permission/permission-dal.ts index 3cce464b0..d94589b43 100644 --- a/backend/src/ee/services/permission/permission-dal.ts +++ b/backend/src/ee/services/permission/permission-dal.ts @@ -63,6 +63,8 @@ export const permissionDALFactory = (db: TDbClient) => { .select(selectAllTableCols(TableName.ProjectUserMembershipRole)) .select( db.ref("id").withSchema(TableName.ProjectMembership).as("membershipId"), + // TODO(roll-forward-migration): remove this field when we drop this in next migration after a week + db.ref("role").withSchema(TableName.ProjectMembership).as("oldRoleField"), db.ref("createdAt").withSchema(TableName.ProjectMembership).as("membershipCreatedAt"), db.ref("updatedAt").withSchema(TableName.ProjectMembership).as("membershipUpdatedAt"), db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"), @@ -74,10 +76,18 @@ export const permissionDALFactory = (db: TDbClient) => { const permission = sqlNestRelationships({ data: docs, key: "membershipId", - parentMapper: ({ orgId, orgAuthEnforced, membershipId, membershipCreatedAt, membershipUpdatedAt }) => ({ + parentMapper: ({ + orgId, + orgAuthEnforced, + membershipId, + membershipCreatedAt, + membershipUpdatedAt, + oldRoleField + }) => ({ orgId, orgAuthEnforced, userId, + role: oldRoleField, id: membershipId, projectId, createdAt: membershipCreatedAt, @@ -124,6 +134,7 @@ export const permissionDALFactory = (db: TDbClient) => { .select(selectAllTableCols(TableName.IdentityProjectMembershipRole)) .select( db.ref("id").withSchema(TableName.IdentityProjectMembership).as("membershipId"), + db.ref("role").withSchema(TableName.IdentityProjectMembership).as("oldRoleField"), db.ref("createdAt").withSchema(TableName.IdentityProjectMembership).as("membershipCreatedAt"), db.ref("updatedAt").withSchema(TableName.IdentityProjectMembership).as("membershipUpdatedAt"), db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug") @@ -133,10 +144,11 @@ export const permissionDALFactory = (db: TDbClient) => { const permission = sqlNestRelationships({ data: docs, key: "membershipId", - parentMapper: ({ membershipId, membershipCreatedAt, membershipUpdatedAt }) => ({ + parentMapper: ({ membershipId, membershipCreatedAt, membershipUpdatedAt, oldRoleField }) => ({ id: membershipId, identityId, projectId, + role: oldRoleField, createdAt: membershipCreatedAt, updatedAt: membershipUpdatedAt, // just a prefilled value diff --git a/backend/src/server/routes/v1/project-membership-router.ts b/backend/src/server/routes/v1/project-membership-router.ts index a4989844d..999a5d025 100644 --- a/backend/src/server/routes/v1/project-membership-router.ts +++ b/backend/src/server/routes/v1/project-membership-router.ts @@ -30,30 +30,31 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider }), response: { 200: z.object({ - memberships: ProjectMembershipsSchema.merge( - z.object({ - user: UsersSchema.pick({ - email: true, - firstName: true, - lastName: true, - id: true - }).merge(UserEncryptionKeysSchema.pick({ publicKey: true })), - roles: z.array( - z.object({ - id: z.string(), - role: z.string(), - customRoleId: z.string().optional().nullable(), - customRoleName: z.string().optional().nullable(), - customRoleSlug: z.string().optional().nullable(), - isTemporary: z.boolean(), - temporaryMode: z.string().optional().nullable(), - temporaryRange: z.string().nullable().optional(), - temporaryAccessStartTime: z.date().nullable().optional(), - temporaryAccessEndTime: z.date().nullable().optional() - }) - ) - }) - ) + memberships: ProjectMembershipsSchema.omit({ role: true }) + .merge( + z.object({ + user: UsersSchema.pick({ + email: true, + firstName: true, + lastName: true, + id: true + }).merge(UserEncryptionKeysSchema.pick({ publicKey: true })), + roles: z.array( + z.object({ + id: z.string(), + role: z.string(), + customRoleId: z.string().optional().nullable(), + customRoleName: z.string().optional().nullable(), + customRoleSlug: z.string().optional().nullable(), + isTemporary: z.boolean(), + temporaryMode: z.string().optional().nullable(), + temporaryRange: z.string().nullable().optional(), + temporaryAccessStartTime: z.date().nullable().optional(), + temporaryAccessEndTime: z.date().nullable().optional() + }) + ) + }) + ) .omit({ createdAt: true, updatedAt: true }) .array() }) diff --git a/backend/src/server/routes/v1/project-router.ts b/backend/src/server/routes/v1/project-router.ts index 9346785ef..dc32702ff 100644 --- a/backend/src/server/routes/v1/project-router.ts +++ b/backend/src/server/routes/v1/project-router.ts @@ -60,31 +60,32 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { }), response: { 200: z.object({ - users: ProjectMembershipsSchema.merge( - z.object({ - user: UsersSchema.pick({ - username: true, - email: true, - firstName: true, - lastName: true, - id: true - }).merge(UserEncryptionKeysSchema.pick({ publicKey: true })), - roles: z.array( - z.object({ - id: z.string(), - role: z.string(), - customRoleId: z.string().optional().nullable(), - customRoleName: z.string().optional().nullable(), - customRoleSlug: z.string().optional().nullable(), - isTemporary: z.boolean(), - temporaryMode: z.string().optional().nullable(), - temporaryRange: z.string().nullable().optional(), - temporaryAccessStartTime: z.date().nullable().optional(), - temporaryAccessEndTime: z.date().nullable().optional() - }) - ) - }) - ) + users: ProjectMembershipsSchema.omit({ role: true }) + .merge( + z.object({ + user: UsersSchema.pick({ + username: true, + email: true, + firstName: true, + lastName: true, + id: true + }).merge(UserEncryptionKeysSchema.pick({ publicKey: true })), + roles: z.array( + z.object({ + id: z.string(), + role: z.string(), + customRoleId: z.string().optional().nullable(), + customRoleName: z.string().optional().nullable(), + customRoleSlug: z.string().optional().nullable(), + isTemporary: z.boolean(), + temporaryMode: z.string().optional().nullable(), + temporaryRange: z.string().nullable().optional(), + temporaryAccessStartTime: z.date().nullable().optional(), + temporaryAccessEndTime: z.date().nullable().optional() + }) + ) + }) + ) .omit({ createdAt: true, updatedAt: true }) .array() }) diff --git a/backend/src/services/identity-project/identity-project-service.ts b/backend/src/services/identity-project/identity-project-service.ts index d13a43659..b6f6e4343 100644 --- a/backend/src/services/identity-project/identity-project-service.ts +++ b/backend/src/services/identity-project/identity-project-service.ts @@ -86,7 +86,9 @@ export const identityProjectServiceFactory = ({ const identityProjectMembership = await identityProjectDAL.create( { identityId, - projectId: project.id + projectId: project.id, + role: isCustomRole ? ProjectMembershipRole.Custom : role, + roleId: customRole?.id }, tx ); diff --git a/backend/src/services/project-membership/project-membership-service.ts b/backend/src/services/project-membership/project-membership-service.ts index 68eb13732..1f751ee89 100644 --- a/backend/src/services/project-membership/project-membership-service.ts +++ b/backend/src/services/project-membership/project-membership-service.ts @@ -105,7 +105,8 @@ export const projectMembershipServiceFactory = ({ const projectMemberships = await projectMembershipDAL.insertMany( orgMembers.map(({ userId }) => ({ projectId, - userId: userId as string + userId: userId as string, + role: ProjectMembershipRole.Member })), tx ); @@ -223,7 +224,8 @@ export const projectMembershipServiceFactory = ({ const projectMemberships = await projectMembershipDAL.insertMany( orgMembers.map(({ user }) => ({ projectId, - userId: user.id + userId: user.id, + role: ProjectMembershipRole.Member })), tx ); diff --git a/backend/src/services/project/project-queue.ts b/backend/src/services/project/project-queue.ts index 81ecd6da1..4431855ce 100644 --- a/backend/src/services/project/project-queue.ts +++ b/backend/src/services/project/project-queue.ts @@ -232,7 +232,8 @@ export const projectQueueFactory = ({ const projectMembership = await projectMembershipDAL.create( { projectId: project.id, - userId: ghostUser.user.id + userId: ghostUser.user.id, + role: ProjectMembershipRole.Admin }, tx ); diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index 76b3cba72..873a7d36f 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -123,7 +123,8 @@ export const projectServiceFactory = ({ const projectMembership = await projectMembershipDAL.create( { userId: ghostUser.user.id, - projectId: project.id + projectId: project.id, + role: ProjectMembershipRole.Admin }, tx ); @@ -225,7 +226,8 @@ export const projectServiceFactory = ({ const userProjectMembership = await projectMembershipDAL.create( { projectId: project.id, - userId: user.id + userId: user.id, + role: projectAdmin.projectRole }, tx ); @@ -281,7 +283,9 @@ export const projectServiceFactory = ({ const identityProjectMembership = await identityProjectDAL.create( { identityId: actorId, - projectId: project.id + projectId: project.id, + role: isCustomRole ? ProjectMembershipRole.Custom : ProjectMembershipRole.Admin, + roleId: customRole?.id }, tx );