Skip to content

Commit

Permalink
feat: Add updateBusinessJoinRequests API endpoint (#566)
Browse files Browse the repository at this point in the history
  • Loading branch information
lounjukk authored Jan 27, 2025
1 parent 5554648 commit 6a83518
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 2 deletions.
113 changes: 113 additions & 0 deletions src/BusinessService.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
/* eslint-disable jest/no-conditional-expect */
import nock from "nock";
import LensPlatformClient from "./LensPlatformClient";
import { minimumOptions as options, apiEndpointAddress } from "./LensPlatformClient.test";
import { type BusinessJoinRequest } from "./BusinessService";
import { MultiStatusException } from "./exceptions";

describe(".business.*", () => {
const businessId = "businessId";
Expand Down Expand Up @@ -48,4 +51,114 @@ describe(".business.*", () => {

expect(preview.balance).toEqual(498);
});

it("can call updateBusinessJoinRequests with successful response", async () => {
const joinRequests: Array<Pick<BusinessJoinRequest, "id" | "state">> = [
{
id: "req-01",
state: "accepted",
},
{
id: "req-02",
state: "rejected",
},
{
id: "req-03",
state: "accepted",
},
];

nock(apiEndpointAddress)
.patch(`/businesses/${businessId}/join-requests`, joinRequests)
.once()
.reply(200, joinRequests);

const data = await lensPlatformClient.business.updateBusinessJoinRequests(
"businessId",
joinRequests,
);

expect(data).toEqual(joinRequests);
});

it("can call updateBusinessJoinRequests with partly unsuccessful response", async () => {
const joinRequests: Array<Pick<BusinessJoinRequest, "id" | "state">> = [
{
id: "req-01",
state: "accepted",
},
{
id: "req-02",
state: "rejected",
},
{
id: "req-03",
state: "accepted",
},
];

nock(apiEndpointAddress)
.patch(`/businesses/${businessId}/join-requests`, joinRequests)
.once()
.reply(207, {
error: "Some join requests could not be processed",
"multi-status": [
{
id: joinRequests[0].id,
data: joinRequests[0],
status: "success",
},
{
id: joinRequests[1].id,
status: "failure",
error: `Join request ${joinRequests[1].id} is ${joinRequests[1].state}, can not update`,
statusCode: 422,
},
{
id: joinRequests[2].id,
status: "failure",
error: `Join request ${joinRequests[2].id} not found`,
statusCode: 404,
},
],
});

try {
await lensPlatformClient.business.updateBusinessJoinRequests("businessId", joinRequests);
// The test should fail if the exception is not thrown
expect(true).toBe(false);
} catch (e: unknown) {
if (e instanceof MultiStatusException) {
expect(e).toBeInstanceOf(MultiStatusException);
expect(e.message).toEqual("Failed to update 2 out of 3 join requests");
expect((e.rawException as any)?.body).toEqual({
error: "Some join requests could not be processed",
"multi-status": [
{
data: {
id: "req-01",
state: "accepted",
},
id: "req-01",
status: "success",
},
{
error: "Join request req-02 is rejected, can not update",
id: "req-02",
status: "failure",
statusCode: 422,
},
{
error: "Join request req-03 not found",
id: "req-03",
status: "failure",
statusCode: 404,
},
],
});
} else {
throw e;
}
}
});
});
44 changes: 43 additions & 1 deletion src/BusinessService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import {
NotFoundException,
ConflictException,
UnauthorizedException,
MultiStatusException,
} from "./exceptions";
import { BillingPageToken } from "./types/types";
import { BillingPageToken, MultiStatusBody } from "./types/types";
import {
BillingInfo,
Invoice,
Expand Down Expand Up @@ -703,6 +704,8 @@ export type BusinessJoinRequestWithCreatedBy = BusinessJoinRequest & {
};
};

export type BusinessJoinRequestMultiStatusBody = MultiStatusBody<BusinessJoinRequestWithCreatedBy>;

export type BusinessSCIMToken = {
/**
* The business SCIM token id
Expand Down Expand Up @@ -1557,6 +1560,45 @@ class BusinessService extends Base {
return json as unknown as BusinessJoinRequest;
}

/**
* Accept/reject/cancel multiple Lens Business ID join request.
*
* @remarks should be used by LBID admins for accept/reject, and by users for cancel.
*/
async updateBusinessJoinRequests(
businessID: Business["id"],
data: Array<Pick<BusinessJoinRequest, "id" | "state">>,
): Promise<Array<BusinessJoinRequest>> {
const { apiEndpointAddress, fetch } = this.lensPlatformClient;
const url = `${apiEndpointAddress}/businesses/${businessID}/join-requests`;
const json = await throwExpected(async () => fetch.patch(url, data), {
207: (error) => {
if (error?.body && error?.body.error) {
const response = error.body as BusinessJoinRequestMultiStatusBody;

let message = response.error;

// Better error message for multi-status
if (response["multi-status"]) {
const multiStatus = response["multi-status"];
const failed = multiStatus.filter((e) => e.status !== "success");

message = `Failed to update ${failed.length} out of ${multiStatus.length} join requests`;
}

return new MultiStatusException(message, error);
}

return new MultiStatusException(undefined, error);
},
403: (error) => new ForbiddenException(error?.body?.message),
404: (error) => new NotFoundException(error?.body?.message),
422: (error) => new UnprocessableEntityException(error?.body?.message),
});

return json as unknown as Array<BusinessJoinRequest>;
}

/**
* Create a Lens Business ID join request.
*
Expand Down
12 changes: 11 additions & 1 deletion src/LensPlatformClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { PlanService } from "./PlanService";
import { BillingPageTokenService } from "./BillingPageTokenService";
import { BusinessService } from "./BusinessService";
import { NotificationService } from "./NotificationService";
import axios, { type AxiosRequestConfig, type AxiosProxyConfig } from "axios";
import axios, { type AxiosRequestConfig, type AxiosProxyConfig, AxiosError } from "axios";
import pino from "pino";
import decode from "jwt-decode";
import { UserRolesService } from "./UserRolesService";
Expand Down Expand Up @@ -326,6 +326,16 @@ class LensPlatformClient {
// Body as JavaScript plain object
const body: unknown = response.data;

// Handle 207 multi-status as an error for later processing
if (response.status === 207) {
let message: string | undefined;

if (body && typeof body === "object" && "error" in body) {
message = body?.error as string;
}
throw new AxiosError(message, "207", undefined, undefined, response);
}

// Print HTTP response info in developer console
logger.debug(
`${key?.toUpperCase()} ${response?.status} ${response?.statusText} ${url} `,
Expand Down
7 changes: 7 additions & 0 deletions src/exceptions/common.exceptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,10 @@ export class InternalServerException extends LensSDKException {
Object.setPrototypeOf(this, InternalServerException.prototype);
}
}

export class MultiStatusException extends LensSDKException {
constructor(message = "Failed to update some of the items", rawException?: unknown) {
super(207, message, rawException);
Object.setPrototypeOf(this, MultiStatusException.prototype);
}
}
20 changes: 20 additions & 0 deletions src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,23 @@ export type License = {
export type BillingPageToken = {
hostedLoginToken: string;
};

/**
* 207 Multi-Status response body
*/
export type MultiStatusBody<T> = {
error: string;
"multi-status": Array<
| {
id: string;
status: "success";
data: T;
}
| {
id: string;
status: "failure";
error: string;
statusCode?: number;
}
>;
};

0 comments on commit 6a83518

Please sign in to comment.