Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/web/app/api/invite/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export async function GET(req: Request) {
return NextResponse.json({ error: 'Unauthorized' });
}

// WORKAROUND: Use combined request to get all invitations (EMPLOYEE + non-EMPLOYEE)
// Get all team invitations (all roles)
const { data } = await getAllTeamInvitationsRequest(
{
tenantId,
Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/api/roles/options/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export async function POST(req: Request) {
access_token
);

// WORKAROUND: Use combined request to get all invitations (EMPLOYEE + non-EMPLOYEE)
// Get all team invitations (all roles)
const { data } = await getAllTeamInvitationsRequest(
{
tenantId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,19 +88,17 @@ class InviteService extends APIService {
};

/**
* WORKAROUND: Get all team invitations by combining EMPLOYEE and non-EMPLOYEE requests
* Get team invitations with optional role filtering
*
* This is needed due to a bug in Gauzy API where:
* - No role filter = automatically applies `role: { name: Not(RolesEnum.EMPLOYEE) }`
* - This excludes EMPLOYEE invitations by default
*
* TODO: Remove this workaround once Gauzy API is fixed
* Now that the Gauzy API bug is fixed, we can:
* - Get all invitations by not specifying roles (returns all roles by default)
* - Filter by specific roles by passing a roles array
*/
getTeamInvitations = async (requestParams: IGetInvitationRequest): Promise<PaginationResponse<TInvite>> => {
try {
const { teamId, ...remainingParams } = requestParams;
const { teamId, roles, ...remainingParams } = requestParams;

const baseQuery: Record<string, string> = {
const baseQuery: Record<string, any> = {
'where[tenantId]': this.tenantId,
'where[organizationId]': this.organizationId,
'where[status]': EInviteStatus.INVITED
Expand All @@ -110,40 +108,33 @@ class InviteService extends APIService {
baseQuery['where[teams][id][0]'] = teamId;
}

// Add remaining params (excluding role as we handle it specially)
// Add role filter if roles are specified˝˝
if (roles && roles.length > 0) {
if (roles.length === 1) {
// Single role filter
baseQuery['where[role][name]'] = roles[0];
} else {
// Multiple roles filter - use array format
roles.forEach((role, index) => {
baseQuery[`where[role][name][${index}]`] = role;
});
}
}

// Add remaining params
(Object.keys(remainingParams) as (keyof typeof remainingParams)[]).forEach((key) => {
if (remainingParams[key] && key !== 'role') {
if (remainingParams[key]) {
baseQuery[`where[${key}]`] = remainingParams[key] as string;
}
});

// Execute both requests in parallel for better performance
const [employeeResponse, nonEmployeeResponse] = await Promise.all([
// Request 1: Get EMPLOYEE invitations explicitly
this.get<PaginationResponse<TInvite>>(
`/invite?${qs.stringify({ ...baseQuery, 'where[role][name]': 'EMPLOYEE' })}`,
{ tenantId: this.tenantId }
),
// Request 2: Get non-EMPLOYEE invitations (no role filter = gets MANAGER, ADMIN, etc.)
this.get<PaginationResponse<TInvite>>(`/invite?${qs.stringify(baseQuery)}`, { tenantId: this.tenantId })
]);

// Combine results and deduplicate by invitation ID
const allInvitations = [...(employeeResponse.data.items || []), ...(nonEmployeeResponse.data.items || [])];

// Remove duplicates based on invitation ID (safety measure)
const uniqueInvitations = allInvitations.filter(
(invitation, index, array) => array.findIndex((inv) => inv.id === invitation.id) === index
);

const combinedResult = {
...employeeResponse.data,
items: uniqueInvitations,
total: uniqueInvitations.length
};
// Single request to get invitations
const response = await this.get<PaginationResponse<TInvite>>(`/invite?${qs.stringify(baseQuery)}`, {
tenantId: this.tenantId
});

// Validate the combined response data using Zod schema
return validatePaginationResponse(inviteSchema, combinedResult, 'getTeamInvitations API response');
// Validate the response data using Zod schema
return validatePaginationResponse(inviteSchema, response.data, 'getTeamInvitations API response');
} catch (error) {
if (error instanceof ZodValidationError) {
this.logger.error(
Expand Down
65 changes: 23 additions & 42 deletions apps/web/core/services/server/requests/invite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export function removeTeamInvitationsRequest({
type ITeamInvitationsRequest = {
tenantId: string;
organizationId: string;
role?: ERoleName;
roles?: ERoleName[];
teamId: string;
};

Expand All @@ -66,19 +66,27 @@ type ITeamInvitationsRequest = {
* @returns
*/
export function getTeamInvitationsRequest(
{ teamId, tenantId, organizationId, role }: ITeamInvitationsRequest,
{ teamId, tenantId, organizationId, roles }: ITeamInvitationsRequest,
bearer_token: string
) {
const queryParams: Record<string, string> = {
const queryParams: Record<string, any> = {
'where[tenantId]': tenantId,
'where[organizationId]': organizationId,
'where[teams][id][0]': teamId,
'where[status]': EInviteStatus.INVITED
};

// Only add role filter if role is specified
if (role) {
queryParams['where[role][name]'] = role;
// Add role filter if roles are specified
if (roles && roles.length > 0) {
if (roles.length === 1) {
// Single role filter
queryParams['where[role][name]'] = roles[0];
} else {
// Multiple roles filter - use array format
roles.forEach((role, index) => {
queryParams[`where[role][name][${index}]`] = role;
});
}
}

const query = qs.stringify(queryParams);
Expand All @@ -92,50 +100,23 @@ export function getTeamInvitationsRequest(
}

/**
* WORKAROUND: Get all team invitations by combining EMPLOYEE and non-EMPLOYEE requests
* Get all team invitations for all roles
*
* This is a temporary solution due to a bug in Gauzy API where:
* - No role filter = automatically applies `role: { name: Not(RolesEnum.EMPLOYEE) }`
* - This excludes EMPLOYEE invitations by default
*
* TODO: Remove this workaround once Gauzy API is fixed
* Now that the Gauzy API bug is fixed, we can get all invitations in a single request
* by not specifying any role filter, which returns all roles by default.
*/
export async function getAllTeamInvitationsRequest(
{ teamId, tenantId, organizationId }: Omit<ITeamInvitationsRequest, 'role'>,
{ teamId, tenantId, organizationId }: Omit<ITeamInvitationsRequest, 'roles'>,
bearer_token: string
): Promise<{ data: PaginationResponse<TInvite> }> {
try {
// Execute both requests in parallel for better performance
const [employeeInvitations, nonEmployeeInvitations] = await Promise.all([
// Request 1: Get EMPLOYEE invitations explicitly
getTeamInvitationsRequest({ teamId, tenantId, organizationId, role: ERoleName.EMPLOYEE }, bearer_token),
// Request 2: Get non-EMPLOYEE invitations (MANAGER, ADMIN, etc.)
getTeamInvitationsRequest(
{ teamId, tenantId, organizationId }, // No role = gets non-EMPLOYEE
bearer_token
)
]);

// Combine results and deduplicate by invitation ID
const allInvitations = [
...(employeeInvitations.data.items || []),
...(nonEmployeeInvitations.data.items || [])
];

// Remove duplicates based on invitation ID (safety measure)
const uniqueInvitations = allInvitations.filter(
(invitation, index, array) => array.findIndex((inv) => inv.id === invitation.id) === index
// Single request to get all invitations (no role filter = all roles)
const response = await getTeamInvitationsRequest(
{ teamId, tenantId, organizationId }, // No roles specified = get all roles
bearer_token
);

return {
data: {
// Preserve other pagination properties from first response
...employeeInvitations.data,
// Override with our combined results
items: uniqueInvitations,
total: uniqueInvitations.length
}
};
return response;
} catch (error) {
console.error('Error fetching all team invitations:', error);
// Fallback to empty result
Expand Down
2 changes: 1 addition & 1 deletion apps/web/core/types/interfaces/user/invite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export interface TeamInvitationsQueryParams {
}

export interface IGetInvitationRequest {
role?: string;
roles?: string[];
teamId?: string;
status?: EInviteStatus;
}
Loading