From 989502753d086116c5d875e3e2c6e5f50fefe6f0 Mon Sep 17 00:00:00 2001 From: Stanislas Polu Date: Thu, 4 Apr 2024 15:22:53 +0200 Subject: [PATCH 1/4] Remove userId from MembershipResource interface --- front/admin/cli.ts | 3 +- front/lib/amplitude/node/index.ts | 16 ++--- front/lib/api/assistant/recent_authors.ts | 23 +++---- front/lib/api/user.ts | 38 +++++++---- front/lib/api/workspace.ts | 4 -- front/lib/auth.ts | 23 ++----- front/lib/iam/invitations.ts | 5 +- front/lib/iam/session.ts | 3 +- front/lib/iam/users.ts | 7 +- front/lib/resources/membership_resource.ts | 65 ++++++++++--------- front/pages/api/create-new-workspace.ts | 2 +- front/pages/api/login.ts | 40 ++++++------ .../pages/api/poke/workspaces/[wId]/revoke.ts | 17 ++++- front/pages/api/poke/workspaces/index.ts | 3 +- .../pages/api/w/[wId]/members/[uId]/index.ts | 2 +- front/pages/no-workspace.tsx | 2 +- front/poke/temporal/activities.ts | 37 ++++++----- types/src/shared/utils/general.ts | 4 +- 18 files changed, 156 insertions(+), 138 deletions(-) diff --git a/front/admin/cli.ts b/front/admin/cli.ts index 8602e1312bbf..81a0e03c96aa 100644 --- a/front/admin/cli.ts +++ b/front/admin/cli.ts @@ -13,6 +13,7 @@ import { import { MembershipResource } from "@app/lib/resources/membership_resource"; import { generateModelSId } from "@app/lib/utils"; import logger from "@app/logger/logger"; +import { renderUserType } from "@app/lib/api/user"; const { DUST_DATA_SOURCES_BUCKET = "", SERVICE_ACCOUNT } = process.env; @@ -127,7 +128,7 @@ const user = async (command: string, args: parseArgs.ParsedArgs) => { console.log(` email: ${u.email}`); const memberships = await MembershipResource.getLatestMemberships({ - userIds: [u.id], + users: [renderUserType(u)], }); const workspaces = await Workspace.findAll({ diff --git a/front/lib/amplitude/node/index.ts b/front/lib/amplitude/node/index.ts index 1f542995a97c..beecbd445761 100644 --- a/front/lib/amplitude/node/index.ts +++ b/front/lib/amplitude/node/index.ts @@ -3,7 +3,6 @@ import type { AgentConfigurationType, AgentMessageType, DataSourceType, - ModelId, UserMessageType, UserType, WorkspaceType, @@ -25,7 +24,7 @@ import { import { isGlobalAgentId } from "@app/lib/api/assistant/global_agents"; import type { Authenticator } from "@app/lib/auth"; import { subscriptionForWorkspace } from "@app/lib/auth"; -import { User, Workspace } from "@app/lib/models"; +import { Workspace } from "@app/lib/models"; import { countActiveSeatsInWorkspace } from "@app/lib/plans/workspace_usage"; import { MembershipResource } from "@app/lib/resources/membership_resource"; @@ -52,12 +51,13 @@ export function getBackendClient() { return BACKEND_CLIENT; } -export async function trackUserMemberships(userId: ModelId) { +export async function trackUserMemberships(user: UserType) { const amplitude = getBackendClient(); - const user = await User.findByPk(userId); - const memberships = await MembershipResource.getActiveMemberships({ - userIds: [userId], - }); + const memberships = user + ? await MembershipResource.getActiveMemberships({ + users: [user], + }) + : []; const groups: string[] = []; for (const membership of memberships) { const workspace = await Workspace.findByPk(membership.workspaceId); @@ -70,7 +70,7 @@ export async function trackUserMemberships(userId: ModelId) { } if (groups.length > 0) { amplitude.client.setGroup(GROUP_TYPE, groups, { - user_id: `user-${userId}`, + user_id: `user-${user.id}`, }); } } diff --git a/front/lib/api/assistant/recent_authors.ts b/front/lib/api/assistant/recent_authors.ts index 3ff3bdfbd15b..257b0c519567 100644 --- a/front/lib/api/assistant/recent_authors.ts +++ b/front/lib/api/assistant/recent_authors.ts @@ -2,15 +2,14 @@ import type { AgentRecentAuthors, LightAgentConfigurationType, UserType, - UserTypeWithWorkspaces, } from "@dust-tt/types"; import { removeNulls } from "@dust-tt/types"; import { Sequelize } from "sequelize"; -import { getMembers } from "@app/lib/api/workspace"; import type { Authenticator } from "@app/lib/auth"; -import { AgentConfiguration } from "@app/lib/models"; +import { AgentConfiguration, User } from "@app/lib/models"; import { safeRedisClient } from "@app/lib/redis"; +import { renderUserType } from "@app/lib/user"; // We keep the most recent authorIds for 3 days. const recentAuthorIdsKeyTTL = 60 * 60 * 24 * 3; // 3 days. @@ -173,14 +172,16 @@ export async function getAgentsRecentAuthors({ {} ); - const authorByUserId: Record = ( - await getMembers(auth, { - userIds: removeNulls( - Array.from(new Set(Object.values(recentAuthorsIdsByAgentId).flat())) - ), + const authorByUserId: Record = ( + await User.findAll({ + where: { + id: removeNulls( + Array.from(new Set(Object.values(recentAuthorsIdsByAgentId).flat())) + ), + }, }) - ).reduce>((acc, member) => { - acc[member.id] = member; + ).reduce>((acc, user) => { + acc[user.id] = renderUserType(user); return acc; }, {}); @@ -190,7 +191,7 @@ export async function getAgentsRecentAuthors({ return [DUST_OWNED_ASSISTANTS_AUTHOR_NAME]; } return renderAuthors( - recentAuthorIds.map((id) => authorByUserId[id]), + removeNulls(recentAuthorIds.map((id) => authorByUserId[id])), currentUserId ); }); diff --git a/front/lib/api/user.ts b/front/lib/api/user.ts index b7ee6e752fc2..3c692c6df795 100644 --- a/front/lib/api/user.ts +++ b/front/lib/api/user.ts @@ -5,6 +5,21 @@ import { User, UserMetadata } from "@app/lib/models"; import { MembershipResource } from "../resources/membership_resource"; +export function renderUserType(user: User): UserType { + return { + sId: user.sId, + id: user.id, + createdAt: user.createdAt.getTime(), + provider: user.provider, + username: user.username, + email: user.email, + firstName: user.firstName, + lastName: user.lastName, + fullName: user.firstName + (user.lastName ? ` ${user.lastName}` : ""), + image: user.imageUrl, + }; +} + /** * This function checks that the user had at least one membership in the past for this workspace * otherwise returns null, preventing retrieving user information from their sId. @@ -30,7 +45,7 @@ export async function getUserForWorkspace( const membership = await MembershipResource.getLatestMembershipOfUserInWorkspace({ - userId: user.id, + user: renderUserType(user), workspace: owner, }); @@ -38,18 +53,15 @@ export async function getUserForWorkspace( return null; } - return { - sId: user.sId, - id: user.id, - createdAt: user.createdAt.getTime(), - provider: user.provider, - username: user.username, - email: user.email, - firstName: user.firstName, - lastName: user.lastName, - fullName: user.firstName + (user.lastName ? ` ${user.lastName}` : ""), - image: user.imageUrl, - }; + return renderUserType(user); +} + +export async function deleteUser(user: UserType): Promise { + await User.destroy({ + where: { + id: user.id, + }, + }); } /** diff --git a/front/lib/api/workspace.ts b/front/lib/api/workspace.ts index ddda964d664a..e1bca91e5e75 100644 --- a/front/lib/api/workspace.ts +++ b/front/lib/api/workspace.ts @@ -105,11 +105,9 @@ export async function getMembers( { roles, activeOnly, - userIds, }: { roles?: MembershipRoleType[]; activeOnly?: boolean; - userIds?: ModelId[]; } = {} ): Promise { const owner = auth.workspace(); @@ -121,12 +119,10 @@ export async function getMembers( ? await MembershipResource.getActiveMemberships({ workspace: owner, roles, - userIds, }) : await MembershipResource.getLatestMemberships({ workspace: owner, roles, - userIds, }); const users = await User.findAll({ diff --git a/front/lib/auth.ts b/front/lib/auth.ts index 48eb2989046c..943bacf136b0 100644 --- a/front/lib/auth.ts +++ b/front/lib/auth.ts @@ -23,6 +23,7 @@ import type { NextApiResponse, } from "next"; +import { renderUserType } from "@app/lib/api/user"; import { isDevelopment } from "@app/lib/development"; import type { SessionWithUser } from "@app/lib/iam/provider"; import { isValidSession } from "@app/lib/iam/provider"; @@ -37,14 +38,13 @@ import { import type { PlanAttributes } from "@app/lib/plans/free_plans"; import { FREE_NO_PLAN_DATA } from "@app/lib/plans/free_plans"; import { isUpgraded } from "@app/lib/plans/plan_codes"; +import { renderSubscriptionFromModels } from "@app/lib/plans/subscription"; import { getTrialVersionForPlan, isTrial } from "@app/lib/plans/trial"; import { MembershipResource } from "@app/lib/resources/membership_resource"; import { new_id } from "@app/lib/utils"; import { renderLightWorkspaceType } from "@app/lib/workspace"; import logger from "@app/logger/logger"; -import { renderSubscriptionFromModels } from "./plans/subscription"; - const { DUST_DEVELOPMENT_WORKSPACE_ID, DUST_DEVELOPMENT_SYSTEM_API_KEY, @@ -132,7 +132,7 @@ export class Authenticator { (async (): Promise => { const membership = await MembershipResource.getActiveMembershipOfUserInWorkspace({ - userId: user.id, + user: renderUserType(user), workspace: renderLightWorkspaceType({ workspace }), }); return membership && @@ -424,22 +424,7 @@ export class Authenticator { * @returns */ user(): UserType | null { - return this._user - ? { - sId: this._user.sId, - id: this._user.id, - createdAt: this._user.createdAt.getTime(), - provider: this._user.provider, - username: this._user.username, - email: this._user.email, - fullName: - this._user.firstName + - (this._user.lastName ? ` ${this._user.lastName}` : ""), - firstName: this._user.firstName, - lastName: this._user.lastName || null, - image: this._user.imageUrl, - } - : null; + return this._user ? renderUserType(this._user) : null; } isDustSuperUser(): boolean { diff --git a/front/lib/iam/invitations.ts b/front/lib/iam/invitations.ts index c13f3308e9af..a570ab2bf899 100644 --- a/front/lib/iam/invitations.ts +++ b/front/lib/iam/invitations.ts @@ -1,9 +1,8 @@ -import type { Result } from "@dust-tt/types"; +import type { Result, UserType } from "@dust-tt/types"; import { Err, Ok } from "@dust-tt/types"; import { verify } from "jsonwebtoken"; import { AuthFlowError } from "@app/lib/iam/errors"; -import type { User } from "@app/lib/models"; import { MembershipInvitation } from "@app/lib/models"; const { DUST_INVITE_TOKEN_SECRET = "" } = process.env; @@ -38,7 +37,7 @@ export async function getPendingMembershipInvitationForToken( export async function markInvitationAsConsumed( membershipInvite: MembershipInvitation, - user: User + user: UserType ) { membershipInvite.status = "consumed"; membershipInvite.invitedUserId = user.id; diff --git a/front/lib/iam/session.ts b/front/lib/iam/session.ts index d8ff70a9d939..75a40b077e43 100644 --- a/front/lib/iam/session.ts +++ b/front/lib/iam/session.ts @@ -7,6 +7,7 @@ import type { } from "next"; import type { ParsedUrlQuery } from "querystring"; +import { renderUserType } from "@app/lib/api/user"; import { Authenticator, getSession } from "@app/lib/auth"; import type { SessionWithUser } from "@app/lib/iam/provider"; import { isValidSession } from "@app/lib/iam/provider"; @@ -37,7 +38,7 @@ export async function getUserFromSession( } const memberships = await MembershipResource.getActiveMemberships({ - userIds: [user.id], + users: [renderUserType(user)], }); const workspaces = await Workspace.findAll({ where: { diff --git a/front/lib/iam/users.ts b/front/lib/iam/users.ts index 9fe0c91cad8b..587acc6e6e89 100644 --- a/front/lib/iam/users.ts +++ b/front/lib/iam/users.ts @@ -3,6 +3,7 @@ import type { UserProviderType, UserType } from "@dust-tt/types"; import { sanitizeString } from "@dust-tt/types"; import { trackSignup } from "@app/lib/amplitude/node"; +import { renderUserType } from "@app/lib/api/user"; import type { ExternalUser, SessionWithUser } from "@app/lib/iam/provider"; import { User } from "@app/lib/models/user"; import { guessFirstandLastNameFromFullName } from "@app/lib/user"; @@ -94,7 +95,7 @@ export async function maybeUpdateFromExternalUser( export async function createOrUpdateUser( session: SessionWithUser -): Promise<{ user: User; created: boolean }> { +): Promise<{ user: UserType; created: boolean }> { const { user: externalUser } = session; const user = await fetchUserFromSession(session); @@ -124,7 +125,7 @@ export async function createOrUpdateUser( await user.save(); - return { user, created: false }; + return { user: renderUserType(user), created: false }; } else { const { firstName, lastName } = guessFirstandLastNameFromFullName( externalUser.name @@ -154,6 +155,6 @@ export async function createOrUpdateUser( fullName: u.name, } satisfies UserType); - return { user: u, created: true }; + return { user: renderUserType(u), created: true }; } } diff --git a/front/lib/resources/membership_resource.ts b/front/lib/resources/membership_resource.ts index 3ace79a2a497..cdbfc604fd84 100644 --- a/front/lib/resources/membership_resource.ts +++ b/front/lib/resources/membership_resource.ts @@ -3,6 +3,7 @@ import type { MembershipRoleType, RequireAtLeastOne, Result, + UserType, } from "@dust-tt/types"; import { Err, Ok } from "@dust-tt/types"; import type { @@ -20,7 +21,7 @@ import type { ReadonlyAttributesType } from "@app/lib/resources/storage/types"; import logger from "@app/logger/logger"; type GetMembershipsOptions = RequireAtLeastOne<{ - userIds: number[]; + users: UserType[]; workspace: LightWorkspaceType; }> & { roles?: MembershipRoleType[]; @@ -43,12 +44,12 @@ export class MembershipResource extends BaseResource { } static async getActiveMemberships({ - userIds, + users, workspace, roles, transaction, }: GetMembershipsOptions): Promise { - if (!workspace && !userIds?.length) { + if (!workspace && !users?.length) { throw new Error("At least one of workspace or userIds must be provided."); } const whereClause: WhereOptions> = { @@ -60,8 +61,8 @@ export class MembershipResource extends BaseResource { }, }; - if (userIds) { - whereClause.userId = userIds; + if (users) { + whereClause.userId = users.map((u) => u.id); } if (workspace) { whereClause.workspaceId = workspace.id; @@ -83,7 +84,7 @@ export class MembershipResource extends BaseResource { } static async getLatestMemberships({ - userIds, + users, workspace, roles, transaction, @@ -99,17 +100,17 @@ export class MembershipResource extends BaseResource { if (roles) { where.role = roles; } - if (userIds) { - where.userId = userIds; + if (users) { + where.userId = users.map((u) => u.id); } if (workspace) { where.workspaceId = workspace.id; } - if (!workspace && !userIds?.length) { + if (!workspace && !users?.length) { throw new Error("At least one of workspace or userIds must be provided."); } - if (userIds && !userIds.length) return []; + if (users && !users.length) return []; // Get all the memberships matching the criteria. const memberships = await MembershipModel.findAll({ @@ -136,16 +137,16 @@ export class MembershipResource extends BaseResource { } static async getLatestMembershipOfUserInWorkspace({ - userId, + user, workspace, transaction, }: { - userId: number; + user: UserType; workspace: LightWorkspaceType; transaction?: Transaction; }): Promise { const memberships = await this.getLatestMemberships({ - userIds: [userId], + users: [user], workspace, transaction, }); @@ -156,30 +157,30 @@ export class MembershipResource extends BaseResource { logger.error( { panic: true, - userId, + userId: user.id, workspaceId: workspace.id, memberships, }, "Unreachable: Found multiple latest memberships for user in workspace." ); throw new Error( - `Unreachable: Found multiple latest memberships for user ${userId} in workspace ${workspace.id}` + `Unreachable: Found multiple latest memberships for user ${user.id} in workspace ${workspace.id}` ); } return memberships[0]; } static async getActiveMembershipOfUserInWorkspace({ - userId, + user, workspace, transaction, }: { - userId: number; + user: UserType; workspace: LightWorkspaceType; transaction?: Transaction; }): Promise { const memberships = await this.getActiveMemberships({ - userIds: [userId], + users: [user], workspace, transaction, }); @@ -190,14 +191,14 @@ export class MembershipResource extends BaseResource { logger.error( { panic: true, - userId, + userId: user.id, workspaceId: workspace.id, memberships, }, "Unreachable: Found multiple active memberships for user in workspace." ); throw new Error( - `Unreachable: Found multiple active memberships for user ${userId} in workspace ${workspace.id}` + `Unreachable: Found multiple active memberships for user ${user.id} in workspace ${workspace.id}` ); } return memberships[0]; @@ -234,13 +235,13 @@ export class MembershipResource extends BaseResource { } static async createMembership({ - userId, + user, workspace, role, startAt = new Date(), transaction, }: { - userId: number; + user: UserType; workspace: LightWorkspaceType; role: MembershipRoleType; startAt?: Date; @@ -252,7 +253,7 @@ export class MembershipResource extends BaseResource { if ( await MembershipModel.count({ where: { - userId, + userId: user.id, workspaceId: workspace.id, endAt: { [Op.or]: [{ [Op.eq]: null }, { [Op.gt]: startAt }], @@ -262,13 +263,13 @@ export class MembershipResource extends BaseResource { }) ) { throw new Error( - `User ${userId} already has an active membership in workspace ${workspace.id}` + `User ${user.id} already has an active membership in workspace ${workspace.id}` ); } const newMembership = await MembershipModel.create( { startAt, - userId, + userId: user.id, workspaceId: workspace.id, role, }, @@ -279,12 +280,12 @@ export class MembershipResource extends BaseResource { } static async revokeMembership({ - userId, + user, workspace, endAt = new Date(), transaction, }: { - userId: number; + user: UserType; workspace: LightWorkspaceType; endAt?: Date; transaction?: Transaction; @@ -297,7 +298,7 @@ export class MembershipResource extends BaseResource { > > { const membership = await this.getLatestMembershipOfUserInWorkspace({ - userId, + user, workspace, transaction, }); @@ -318,13 +319,13 @@ export class MembershipResource extends BaseResource { } static async updateMembershipRole({ - userId, + user, workspace, newRole, allowTerminated = false, transaction, }: { - userId: number; + user: UserType; workspace: LightWorkspaceType; newRole: Exclude; // If true, allow updating the role of a terminated membership (which will also un-terminate it). @@ -339,7 +340,7 @@ export class MembershipResource extends BaseResource { > > { const membership = await this.getLatestMembershipOfUserInWorkspace({ - userId, + user, workspace, transaction, }); @@ -363,7 +364,7 @@ export class MembershipResource extends BaseResource { } else { // If the last membership was terminated, we create a new membership with the new role. await this.createMembership({ - userId, + user, workspace, role: newRole, startAt: new Date(), diff --git a/front/pages/api/create-new-workspace.ts b/front/pages/api/create-new-workspace.ts index b3f07c2809c4..009c68518730 100644 --- a/front/pages/api/create-new-workspace.ts +++ b/front/pages/api/create-new-workspace.ts @@ -51,8 +51,8 @@ async function handler( const workspace = await createWorkspace(session); await createAndLogMembership({ + user, workspace, - userId: user.id, role: "admin", }); diff --git a/front/pages/api/login.ts b/front/pages/api/login.ts index e4e58b31adef..de766944642c 100644 --- a/front/pages/api/login.ts +++ b/front/pages/api/login.ts @@ -1,12 +1,14 @@ import type { ActiveRoleType, Result, + UserType, WithAPIErrorReponse, } from "@dust-tt/types"; import { Err, Ok } from "@dust-tt/types"; import type { NextApiRequest, NextApiResponse } from "next"; import { trackUserMemberships } from "@app/lib/amplitude/node"; +import { deleteUser } from "@app/lib/api/user"; import { evaluateWorkspaceSeatAvailability } from "@app/lib/api/workspace"; import { getSession, subscriptionForWorkspace } from "@app/lib/auth"; import { AuthFlowError, SSOEnforcedError } from "@app/lib/iam/errors"; @@ -21,7 +23,7 @@ import { createWorkspace, findWorkspaceWithVerifiedDomain, } from "@app/lib/iam/workspaces"; -import type { MembershipInvitation, User } from "@app/lib/models"; +import type { MembershipInvitation } from "@app/lib/models"; import { Workspace } from "@app/lib/models"; import { updateWorkspacePerSeatSubscriptionUsage } from "@app/lib/plans/subscription"; import { MembershipResource } from "@app/lib/resources/membership_resource"; @@ -33,7 +35,7 @@ import { apiError, withLogging } from "@app/logger/withlogging"; // all the checks (decoding the JWT) have been run before. Simply create the membership if // it does not already exist and mark the invitation as consumed. async function handleMembershipInvite( - user: User, + user: UserType, membershipInvite: MembershipInvitation ): Promise< Result< @@ -73,7 +75,7 @@ async function handleMembershipInvite( } const m = await MembershipResource.getLatestMembershipOfUserInWorkspace({ - userId: user.id, + user, workspace: renderLightWorkspaceType({ workspace }), }); @@ -88,7 +90,7 @@ async function handleMembershipInvite( if (!m) { await createAndLogMembership({ workspace, - userId: user.id, + user, role: membershipInvite.initialRole, }); } @@ -124,7 +126,7 @@ function canJoinTargetWorkspace( } async function handleEnterpriseSignUpFlow( - user: User, + user: UserType, enterpriseConnectionWorkspaceId: string ): Promise<{ flow: "unauthorized" | null; @@ -133,7 +135,7 @@ async function handleEnterpriseSignUpFlow( // Combine queries to optimize database calls. const [activeMemberships, workspace] = await Promise.all([ MembershipResource.getActiveMemberships({ - userIds: [user.id], + users: [user], }), Workspace.findOne({ where: { @@ -154,7 +156,7 @@ async function handleEnterpriseSignUpFlow( const membership = await MembershipResource.getLatestMembershipOfUserInWorkspace({ - userId: user.id, + user, workspace: renderLightWorkspaceType({ workspace }), }); @@ -162,7 +164,7 @@ async function handleEnterpriseSignUpFlow( if (!membership) { await createAndLogMembership({ workspace, - userId: user.id, + user, role: "user", }); } else if (membership.isRevoked()) { @@ -177,7 +179,7 @@ async function handleEnterpriseSignUpFlow( // The user will join this workspace if it exists; otherwise, a new workspace is created. async function handleRegularSignupFlow( session: SessionWithUser, - user: User, + user: UserType, targetWorkspaceId?: string ): Promise< Result< @@ -189,7 +191,7 @@ async function handleRegularSignupFlow( > > { const activeMemberships = await MembershipResource.getActiveMemberships({ - userIds: [user.id], + users: [user], }); // Return early if the user is already a member of a workspace and is not attempting to join another one. @@ -241,7 +243,7 @@ async function handleRegularSignupFlow( } const m = await MembershipResource.getLatestMembershipOfUserInWorkspace({ - userId: user.id, + user, workspace: renderLightWorkspaceType({ workspace: existingWorkspace }), }); @@ -252,7 +254,7 @@ async function handleRegularSignupFlow( if (!m) { await createAndLogMembership({ workspace: existingWorkspace, - userId: user.id, + user, role: "user", }); } @@ -262,7 +264,7 @@ async function handleRegularSignupFlow( const workspace = await createWorkspace(session); await createAndLogMembership({ workspace, - userId: user.id, + user, role: "admin", }); @@ -359,7 +361,7 @@ async function handler( // Delete newly created user if SSO is mandatory. if (userCreated) { - await user.destroy(); + await deleteUser(user); } res.redirect( @@ -399,20 +401,20 @@ async function handler( } export async function createAndLogMembership({ - userId, + user, workspace, role, }: { - userId: number; + user: UserType; workspace: Workspace; role: ActiveRoleType; }) { const m = await MembershipResource.createMembership({ - role: role, - userId: userId, + role, + user, workspace: renderLightWorkspaceType({ workspace }), }); - trackUserMemberships(m.userId).catch(logger.error); + trackUserMemberships(user).catch(logger.error); // If the user is joining a workspace with a subscription based on per_seat, // we need to update the Stripe subscription quantity. diff --git a/front/pages/api/poke/workspaces/[wId]/revoke.ts b/front/pages/api/poke/workspaces/[wId]/revoke.ts index 28d575a05ceb..b05540cfe191 100644 --- a/front/pages/api/poke/workspaces/[wId]/revoke.ts +++ b/front/pages/api/poke/workspaces/[wId]/revoke.ts @@ -2,6 +2,7 @@ import type { WithAPIErrorReponse } from "@dust-tt/types"; import { assertNever } from "@dust-tt/types"; import type { NextApiRequest, NextApiResponse } from "next"; +import { getUserForWorkspace } from "@app/lib/api/user"; import { Authenticator, getSession } from "@app/lib/auth"; import { updateWorkspacePerSeatSubscriptionUsage } from "@app/lib/plans/subscription"; import { MembershipResource } from "@app/lib/resources/membership_resource"; @@ -35,7 +36,7 @@ async function handler( switch (req.method) { case "POST": const { userId } = req.body; - if (!userId || typeof userId !== "number") { + if (!userId || typeof userId !== "string") { return apiError(req, res, { status_code: 404, api_error: { @@ -44,8 +45,20 @@ async function handler( }, }); } + + const user = await getUserForWorkspace(auth, { userId }); + if (!user) { + return apiError(req, res, { + status_code: 404, + api_error: { + type: "user_not_found", + message: "Could not find the user.", + }, + }); + } + const revokeResult = await MembershipResource.revokeMembership({ - userId, + user, workspace: owner, }); if (revokeResult.isErr()) { diff --git a/front/pages/api/poke/workspaces/index.ts b/front/pages/api/poke/workspaces/index.ts index 66f79ca4172f..44640a6a4193 100644 --- a/front/pages/api/poke/workspaces/index.ts +++ b/front/pages/api/poke/workspaces/index.ts @@ -3,6 +3,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; import type { FindOptions, WhereOptions } from "sequelize"; import { Op } from "sequelize"; +import { renderUserType } from "@app/lib/api/user"; import { Authenticator, getSession } from "@app/lib/auth"; import { Plan, Subscription, User, Workspace } from "@app/lib/models"; import { FREE_TEST_PLAN_CODE } from "@app/lib/plans/plan_codes"; @@ -131,7 +132,7 @@ async function handler( }); if (users.length) { const memberships = await MembershipResource.getLatestMemberships({ - userIds: users.map((u) => u.id), + users: users.map((u) => renderUserType(u)), }); if (memberships.length) { conditions.push({ diff --git a/front/pages/api/w/[wId]/members/[uId]/index.ts b/front/pages/api/w/[wId]/members/[uId]/index.ts index dcd03d707600..a12e62ec5744 100644 --- a/front/pages/api/w/[wId]/members/[uId]/index.ts +++ b/front/pages/api/w/[wId]/members/[uId]/index.ts @@ -74,7 +74,7 @@ async function handler( // TODO(@fontanierh): use DELETE for revoking membership if (req.body.role === "revoked") { const revokeResult = await MembershipResource.revokeMembership({ - userId: user.id, + user, workspace: owner, }); if (revokeResult.isErr()) { diff --git a/front/pages/no-workspace.tsx b/front/pages/no-workspace.tsx index 909443540062..07484f24d6fc 100644 --- a/front/pages/no-workspace.tsx +++ b/front/pages/no-workspace.tsx @@ -45,7 +45,7 @@ async function fetchRevokedWorkspace( // TODO(@fontanierh): this doesn't look very solid as it will start to behave // weirdly if a user has multiple revoked memberships. const memberships = await MembershipResource.getLatestMemberships({ - userIds: [user.id], + users: [user], }); if (!memberships.length) { diff --git a/front/poke/temporal/activities.ts b/front/poke/temporal/activities.ts index 6bfe072a56f2..5a582e3f611c 100644 --- a/front/poke/temporal/activities.ts +++ b/front/poke/temporal/activities.ts @@ -2,6 +2,7 @@ import { CoreAPI } from "@dust-tt/types"; import { Storage } from "@google-cloud/storage"; import { Op } from "sequelize"; +import { renderUserType } from "@app/lib/api/user"; import { Authenticator } from "@app/lib/auth"; import { AgentConfiguration, @@ -441,32 +442,36 @@ export async function deleteMembersActivity({ transaction: t, }); - if (memberships.length === 1) { - // We also delete the user if it has no other workspace. - const membership = memberships[0]; - const membershipsOfUser = await MembershipResource.getLatestMemberships({ - userIds: [membership.userId], - transaction: t, + for (const membership of memberships) { + const user = await User.findOne({ + where: { + id: membership.userId, + }, }); - if (membershipsOfUser.length === 1) { - const user = await User.findOne({ - where: { - id: membership.userId, - }, - }); - if (user) { + + if (user) { + const membershipsOfUser = await MembershipResource.getLatestMemberships( + { + users: [renderUserType(user)], + transaction: t, + } + ); + + // If the user we're removing the membership of only has one membership, we delete the user. + if (membershipsOfUser.length === 1) { await UserMetadata.destroy({ where: { userId: user.id, }, transaction: t, }); + logger.info( + `[Workspace delete] Deleting Membership ${membership.id} and user ${user.id}` + ); await membership.delete(t); await user.destroy({ transaction: t }); } - } - } else { - for (const membership of memberships) { + } else { logger.info(`[Workspace delete] Deleting Membership ${membership.id}`); await membership.delete(t); } diff --git a/types/src/shared/utils/general.ts b/types/src/shared/utils/general.ts index 0c3dce95ac43..aa4d12499f16 100644 --- a/types/src/shared/utils/general.ts +++ b/types/src/shared/utils/general.ts @@ -1,6 +1,6 @@ /** * Filters out nulls & undefineds from an array by correclty narrowing the type */ -export function removeNulls(arr: (T | null)[]): T[] { - return arr.filter((v): v is T => v !== null); +export function removeNulls(arr: (T | null | undefined)[]): T[] { + return arr.filter((v): v is T => v !== null && v !== undefined); } From 5bcb83a8c1d0be676d334a46cb372d8ee9a021f0 Mon Sep 17 00:00:00 2001 From: Stanislas Polu Date: Thu, 4 Apr 2024 15:47:53 +0200 Subject: [PATCH 2/4] lint --- front/lib/api/assistant/recent_authors.ts | 2 +- front/pages/api/w/[wId]/members/[uId]/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/front/lib/api/assistant/recent_authors.ts b/front/lib/api/assistant/recent_authors.ts index 257b0c519567..e54ce57375de 100644 --- a/front/lib/api/assistant/recent_authors.ts +++ b/front/lib/api/assistant/recent_authors.ts @@ -6,10 +6,10 @@ import type { import { removeNulls } from "@dust-tt/types"; import { Sequelize } from "sequelize"; +import { renderUserType } from "@app/lib/api/user"; import type { Authenticator } from "@app/lib/auth"; import { AgentConfiguration, User } from "@app/lib/models"; import { safeRedisClient } from "@app/lib/redis"; -import { renderUserType } from "@app/lib/user"; // We keep the most recent authorIds for 3 days. const recentAuthorIdsKeyTTL = 60 * 60 * 24 * 3; // 3 days. diff --git a/front/pages/api/w/[wId]/members/[uId]/index.ts b/front/pages/api/w/[wId]/members/[uId]/index.ts index a12e62ec5744..7d9f4bc8e3fb 100644 --- a/front/pages/api/w/[wId]/members/[uId]/index.ts +++ b/front/pages/api/w/[wId]/members/[uId]/index.ts @@ -111,7 +111,7 @@ async function handler( }); } const updateRoleResult = await MembershipResource.updateMembershipRole({ - userId: user.id, + user, workspace: owner, newRole: role, // We allow to re-activate a terminated membership when updating the role here. From 01b81ede9ce6a2254016aa89a47024c7b4209511 Mon Sep 17 00:00:00 2001 From: Stanislas Polu Date: Thu, 4 Apr 2024 15:51:39 +0200 Subject: [PATCH 3/4] fix poke endpoint call --- front/pages/poke/[wId]/memberships.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/pages/poke/[wId]/memberships.tsx b/front/pages/poke/[wId]/memberships.tsx index acb3a3840b76..77acb2ba33da 100644 --- a/front/pages/poke/[wId]/memberships.tsx +++ b/front/pages/poke/[wId]/memberships.tsx @@ -52,7 +52,7 @@ const MembershipsPage = ({ "Content-Type": "application/json", }, body: JSON.stringify({ - userId: m.id, + userId: m.sId, }), }); if (!r.ok) { From cf845e124a32534acb9c0be590b18ee44eff4f4c Mon Sep 17 00:00:00 2001 From: Stanislas Polu Date: Thu, 4 Apr 2024 16:04:04 +0200 Subject: [PATCH 4/4] lint --- front/lib/api/workspace.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/front/lib/api/workspace.ts b/front/lib/api/workspace.ts index e1bca91e5e75..28d0c80f196c 100644 --- a/front/lib/api/workspace.ts +++ b/front/lib/api/workspace.ts @@ -1,7 +1,6 @@ import type { LightWorkspaceType, MembershipRoleType, - ModelId, RoleType, SubscriptionType, UserTypeWithWorkspaces,