diff --git a/package-lock.json b/package-lock.json index 1db4651b62d..95de2be7fdf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22688,9 +22688,10 @@ }, "node_modules/cz-lerna-changelog/node_modules/npm/node_modules/lodash._baseindexof": { "version": "3.1.0", - "extraneous": true, + "dev": true, "inBundle": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/cz-lerna-changelog/node_modules/npm/node_modules/lodash._baseuniq": { "version": "4.6.0", @@ -22705,21 +22706,24 @@ }, "node_modules/cz-lerna-changelog/node_modules/npm/node_modules/lodash._bindcallback": { "version": "3.0.1", - "extraneous": true, + "dev": true, "inBundle": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/cz-lerna-changelog/node_modules/npm/node_modules/lodash._cacheindexof": { "version": "3.0.2", - "extraneous": true, + "dev": true, "inBundle": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/cz-lerna-changelog/node_modules/npm/node_modules/lodash._createcache": { "version": "3.1.2", - "extraneous": true, + "dev": true, "inBundle": true, "license": "MIT", + "peer": true, "dependencies": { "lodash._getnative": "^3.0.0" } @@ -22733,9 +22737,10 @@ }, "node_modules/cz-lerna-changelog/node_modules/npm/node_modules/lodash._getnative": { "version": "3.9.1", - "extraneous": true, + "dev": true, "inBundle": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/cz-lerna-changelog/node_modules/npm/node_modules/lodash._root": { "version": "3.0.1", @@ -22753,9 +22758,10 @@ }, "node_modules/cz-lerna-changelog/node_modules/npm/node_modules/lodash.restparam": { "version": "3.6.1", - "extraneous": true, + "dev": true, "inBundle": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/cz-lerna-changelog/node_modules/npm/node_modules/lodash.union": { "version": "4.6.0", @@ -65010,7 +65016,7 @@ }, "packages/common": { "name": "@esri/hub-common", - "version": "14.103.0", + "version": "14.104.0", "license": "Apache-2.0", "dependencies": { "@terraformer/arcgis": "^2.1.2", @@ -83465,7 +83471,8 @@ "lodash._baseindexof": { "version": "3.1.0", "bundled": true, - "extraneous": true + "dev": true, + "peer": true }, "lodash._baseuniq": { "version": "4.6.0", @@ -83480,17 +83487,20 @@ "lodash._bindcallback": { "version": "3.0.1", "bundled": true, - "extraneous": true + "dev": true, + "peer": true }, "lodash._cacheindexof": { "version": "3.0.2", "bundled": true, - "extraneous": true + "dev": true, + "peer": true }, "lodash._createcache": { "version": "3.1.2", "bundled": true, - "extraneous": true, + "dev": true, + "peer": true, "requires": { "lodash._getnative": "^3.0.0" } @@ -83504,7 +83514,8 @@ "lodash._getnative": { "version": "3.9.1", "bundled": true, - "extraneous": true + "dev": true, + "peer": true }, "lodash._root": { "version": "3.0.1", @@ -83521,7 +83532,8 @@ "lodash.restparam": { "version": "3.6.1", "bundled": true, - "extraneous": true + "dev": true, + "peer": true }, "lodash.union": { "version": "4.6.0", diff --git a/packages/common/src/events/api/events.ts b/packages/common/src/events/api/events.ts index 88f27e5d94e..3d3486c7e43 100644 --- a/packages/common/src/events/api/events.ts +++ b/packages/common/src/events/api/events.ts @@ -42,7 +42,7 @@ export async function getEvents(options: IGetEventsParams): Promise { /** * get an event * - * @param {ICreateEventParams} options + * @param {IGetEventParams} options * @return {Promise} */ export async function getEvent(options: IGetEventParams): Promise { diff --git a/packages/common/src/events/api/utils/authenticate-request.ts b/packages/common/src/events/api/utils/authenticate-request.ts index e2d3fe2889f..2b137e12d41 100644 --- a/packages/common/src/events/api/utils/authenticate-request.ts +++ b/packages/common/src/events/api/utils/authenticate-request.ts @@ -4,7 +4,7 @@ import { IEventsRequestOptions } from "../types"; * return a token created using options.authentication or set on options.token * * @export - * @param {IDiscussionsRequestOptions} options + * @param {IEventsRequestOptions} options * @return {*} {Promise} */ export function authenticateRequest( diff --git a/packages/common/src/newsletters/api/index.ts b/packages/common/src/newsletters/api/index.ts new file mode 100644 index 00000000000..9e5a5ffe1c2 --- /dev/null +++ b/packages/common/src/newsletters/api/index.ts @@ -0,0 +1,3 @@ +export * from "./subscriptions"; +export * from "./users"; +export * from "./types"; diff --git a/packages/common/src/newsletters/api/orval/api/orval-newsletters.ts b/packages/common/src/newsletters/api/orval/api/orval-newsletters.ts new file mode 100644 index 00000000000..f3e5e89064a --- /dev/null +++ b/packages/common/src/newsletters/api/orval/api/orval-newsletters.ts @@ -0,0 +1,441 @@ +/* tslint:disable:interface-over-type-literal */ +import { Awaited } from "../awaited-type"; + +/** + * Generated by orval v6.24.0 🍺 + * Do not edit manually. + * Hub Newsletters Service + * OpenAPI spec version: 0.0.1 + */ +import { customClient } from "../custom-client"; +export type GetSubscriptionsParams = { + /** + * Optional array of matching notification spec names to filter by + */ + names?: string[]; + /** + * Optional cadence to filter by + */ + cadence?: Cadence; + /** + * Optional user id to filter by. Note this gets extracted/overridden from AGO token if provided + */ + userId?: string; + /** + * Optional flag for filtering active subscriptions + */ + active?: boolean; +}; + +export interface IUpdateNotificationSpec { + /** Description of the notification spec */ + description?: string; + /** Name identifier for the notification spec */ + name?: string; +} + +export interface ICreateNotificationSpec { + /** Description of the notification spec */ + description?: string; + /** Name identifier for the notification spec */ + name: string; +} + +export interface IUpdateUser { + /** Flag for deleted user */ + deleted?: boolean; + /** Email for the subscriber. Will always be extracted from the token unless service token is used. */ + email?: string; + /** First name for the subscriber. Will always be extracted from the token unless service token is used. */ + firstName?: string; + /** Last name for the subscriber. Will always be extracted from the token unless service token is used. */ + lastName?: string; + /** Flag for unsubscribed user */ + optedOut?: boolean; + /** Username for the subscriber. Will always be extracted from the token unless service token is used. */ + username?: string; +} + +export interface ICreateUser { + /** ArcGIS Online id for a user. Will always be extracted from the token unless service token is used. */ + agoId?: string; + /** Flag for deleted user */ + deleted?: boolean; + /** Email for the subscriber. Will always be extracted from the token unless service token is used. */ + email?: string; + /** First name for the subscriber. Will always be extracted from the token unless service token is used. */ + firstName?: string; + /** Last name for the subscriber. Will always be extracted from the token unless service token is used. */ + lastName?: string; + /** Flag for unsubscribed user */ + optedOut?: boolean; + /** Username for the subscriber. Will always be extracted from the token unless service token is used. */ + username?: string; +} + +/** + * Metadata for the subscription + */ +export type ISubscribeMetadata = + | ICreateEventMetadata + | ICreateTelemetryReportMetadata; + +export type ISubscriptionMetadata = { [key: string]: any }; + +export interface IUser { + agoId: string; + createdAt: string; + deleted: boolean; + email: string; + firstName: string; + lastName: string; + optedOut: boolean; + updatedAt: string; + username: string; +} + +export interface INotificationSpec { + createdAt: string; + createdById: string; + description: string; + id: number; + name: string; + updatedAt: string; +} + +/** + * Metadata for the subscription + */ +export type ICreateSubscriptionMetadata = + | ICreateEventMetadata + | ICreateTelemetryReportMetadata; + +export enum DeliveryMethod { + EMAIL = "EMAIL", +} +export enum Cadence { + ON_EVENT = "ON_EVENT", + DAILY = "DAILY", + WEEKLY = "WEEKLY", + MONTHLY = "MONTHLY", +} +export interface IUpdateSubscription { + /** Flag to opt user in or out of subscription */ + active?: boolean; + /** Frequency of the subscription */ + cadence?: Cadence; + /** Delivery method for subscription, ie email or text */ + deliveryMethod?: DeliveryMethod; + /** Last delivered datetime string of the subscription in ISO 8601 format */ + lastDelivery?: string; + /** ArcGIS Online id for a user. Will always be extracted from the token unless service token is used. */ + userId?: string; +} + +export interface ISubscription { + active: boolean; + cadence: Cadence; + createdAt: string; + deliveryMethod: DeliveryMethod; + id: number; + lastDelivery: string; + metadata: ISubscriptionMetadata; + notificationSpec?: INotificationSpec; + notificationSpecId: number; + updatedAt: string; + user?: IUser; + userId: string; +} + +export enum SystemNotificationSpecNames { + TELEMETRY_REPORT = "TELEMETRY_REPORT", + EVENT = "EVENT", +} +export interface ISubscribe { + /** ArcGIS Online id for a user. Will always be extracted from the token unless service token is used. */ + agoId?: string; + /** Frequency of the subscription */ + cadence: Cadence; + /** Flag for deleted user */ + deleted?: boolean; + /** Delivery method for subscription, ie email or text */ + deliveryMethod: DeliveryMethod; + /** Email for the subscriber. Will always be extracted from the token unless service token is used. */ + email?: string; + /** First name for the subscriber. Will always be extracted from the token unless service token is used. */ + firstName?: string; + /** Last name for the subscriber. Will always be extracted from the token unless service token is used. */ + lastName?: string; + /** Metadata for the subscription */ + metadata: ISubscribeMetadata; + /** Notification spec name for the subscription */ + notificationSpecName: SystemNotificationSpecNames; + /** Flag for unsubscribed user */ + optedOut?: boolean; + /** Username for the subscriber. Will always be extracted from the token unless service token is used. */ + username?: string; +} + +export interface ICreateSubscription { + /** Frequency of the subscription */ + cadence: Cadence; + /** Delivery method for subscription, ie email or text */ + deliveryMethod: DeliveryMethod; + /** Metadata for the subscription */ + metadata: ICreateSubscriptionMetadata; + /** Notification spec name for the subscription */ + notificationSpecName: SystemNotificationSpecNames; + /** AGO id for user which subscription belongs to */ + userId: string; +} + +export interface ICreateEventMetadata { + /** Event id for the subscription */ + eventId: string; +} + +export interface ICreateTelemetryReportMetadata { + /** Hostname for telemetry report. */ + hostname: string; +} + +type SecondParameter any> = Parameters[1]; + +export const createSubscription = ( + iCreateSubscription: ICreateSubscription, + options?: SecondParameter +) => { + return customClient( + { + url: `/api/newsletters/v1/subscriptions`, + method: "POST", + headers: { "Content-Type": "application/json" }, + data: iCreateSubscription, + }, + options + ); +}; + +export const getSubscriptions = ( + params?: GetSubscriptionsParams, + options?: SecondParameter +) => { + return customClient( + { url: `/api/newsletters/v1/subscriptions`, method: "GET", params }, + options + ); +}; + +export const subscribe = ( + iSubscribe: ISubscribe, + options?: SecondParameter +) => { + return customClient( + { + url: `/api/newsletters/v1/subscriptions/subscribe`, + method: "POST", + headers: { "Content-Type": "application/json" }, + data: iSubscribe, + }, + options + ); +}; + +export const getSubscription = ( + id: number, + options?: SecondParameter +) => { + return customClient( + { url: `/api/newsletters/v1/subscriptions/${id}`, method: "GET" }, + options + ); +}; + +export const updateSubscription = ( + id: number, + iUpdateSubscription: IUpdateSubscription, + options?: SecondParameter +) => { + return customClient( + { + url: `/api/newsletters/v1/subscriptions/${id}`, + method: "PATCH", + headers: { "Content-Type": "application/json" }, + data: iUpdateSubscription, + }, + options + ); +}; + +export const deleteSubscription = ( + id: number, + options?: SecondParameter +) => { + return customClient( + { url: `/api/newsletters/v1/subscriptions/${id}`, method: "DELETE" }, + options + ); +}; + +export const createUser = ( + iCreateUser: ICreateUser, + options?: SecondParameter +) => { + return customClient( + { + url: `/api/newsletters/v1/users`, + method: "POST", + headers: { "Content-Type": "application/json" }, + data: iCreateUser, + }, + options + ); +}; + +export const getUsers = (options?: SecondParameter) => { + return customClient( + { url: `/api/newsletters/v1/users`, method: "GET" }, + options + ); +}; + +export const getUser = ( + userId: string, + options?: SecondParameter +) => { + return customClient( + { url: `/api/newsletters/v1/users/${userId}`, method: "GET" }, + options + ); +}; + +export const updateUser = ( + userId: string, + iUpdateUser: IUpdateUser, + options?: SecondParameter +) => { + return customClient( + { + url: `/api/newsletters/v1/users/${userId}`, + method: "PATCH", + headers: { "Content-Type": "application/json" }, + data: iUpdateUser, + }, + options + ); +}; + +export const deleteUser = ( + userId: string, + options?: SecondParameter +) => { + return customClient( + { url: `/api/newsletters/v1/users/${userId}`, method: "DELETE" }, + options + ); +}; + +export const createNotificationSpec = ( + iCreateNotificationSpec: ICreateNotificationSpec, + options?: SecondParameter +) => { + return customClient( + { + url: `/api/newsletters/v1/notification-specs`, + method: "POST", + headers: { "Content-Type": "application/json" }, + data: iCreateNotificationSpec, + }, + options + ); +}; + +export const getNotificationSpecs = ( + options?: SecondParameter +) => { + return customClient( + { url: `/api/newsletters/v1/notification-specs`, method: "GET" }, + options + ); +}; + +export const getNotificationSpec = ( + id: number, + options?: SecondParameter +) => { + return customClient( + { url: `/api/newsletters/v1/notification-specs/${id}`, method: "GET" }, + options + ); +}; + +export const updateNotificationSpec = ( + id: number, + iUpdateNotificationSpec: IUpdateNotificationSpec, + options?: SecondParameter +) => { + return customClient( + { + url: `/api/newsletters/v1/notification-specs/${id}`, + method: "PATCH", + headers: { "Content-Type": "application/json" }, + data: iUpdateNotificationSpec, + }, + options + ); +}; + +export const deleteNotificationSpec = ( + id: number, + options?: SecondParameter +) => { + return customClient( + { url: `/api/newsletters/v1/notification-specs/${id}`, method: "DELETE" }, + options + ); +}; + +export type CreateSubscriptionResult = NonNullable< + Awaited> +>; +export type GetSubscriptionsResult = NonNullable< + Awaited> +>; +export type SubscribeResult = NonNullable< + Awaited> +>; +export type GetSubscriptionResult = NonNullable< + Awaited> +>; +export type UpdateSubscriptionResult = NonNullable< + Awaited> +>; +export type DeleteSubscriptionResult = NonNullable< + Awaited> +>; +export type CreateUserResult = NonNullable< + Awaited> +>; +export type GetUsersResult = NonNullable>>; +export type GetUserResult = NonNullable>>; +export type UpdateUserResult = NonNullable< + Awaited> +>; +export type DeleteUserResult = NonNullable< + Awaited> +>; +export type CreateNotificationSpecResult = NonNullable< + Awaited> +>; +export type GetNotificationSpecsResult = NonNullable< + Awaited> +>; +export type GetNotificationSpecResult = NonNullable< + Awaited> +>; +export type UpdateNotificationSpecResult = NonNullable< + Awaited> +>; +export type DeleteNotificationSpecResult = NonNullable< + Awaited> +>; diff --git a/packages/common/src/newsletters/api/orval/awaited-type.ts b/packages/common/src/newsletters/api/orval/awaited-type.ts new file mode 100644 index 00000000000..af18d30b7cb --- /dev/null +++ b/packages/common/src/newsletters/api/orval/awaited-type.ts @@ -0,0 +1,5 @@ +/** + * Orval generates return types for functions using utility type Awaited + * This was introduced in Typescript 4.5, but hub.js is using Typescript 3 + */ +export type Awaited = T extends PromiseLike ? U : T; diff --git a/packages/common/src/newsletters/api/orval/custom-client.ts b/packages/common/src/newsletters/api/orval/custom-client.ts new file mode 100644 index 00000000000..04a8bee5bfa --- /dev/null +++ b/packages/common/src/newsletters/api/orval/custom-client.ts @@ -0,0 +1,94 @@ +/** + * Generated and copied from the [hub engagement repo](https://github.com/ArcGIS/hub-newsletters/blob/master/orval/custom-client.ts) + * Do not edit manually + */ +export interface IOrvalParams { + url: string; + method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; + headers?: HeadersInit; + params?: Record; // query params + data?: Record; // request body +} + +export interface ICustomParams { + hubApiUrl?: string; + token?: string; + headers?: HeadersInit; + params?: Record; // query params + data?: Record; // request body + mode?: RequestMode; + cache?: RequestCache; + credentials?: RequestCredentials; +} + +export async function customClient( + orvalParams: IOrvalParams, + customParams: ICustomParams +): Promise { + const { url, method, data } = orvalParams; + const { mode, cache, credentials } = customParams; + const { headers, params } = combineParams(orvalParams, customParams); + + const baseUrl = removeTrailingSlash(customParams.hubApiUrl); + const requestUrl = `${baseUrl}${url}?${new URLSearchParams(params)}`; + + const requestOptions: RequestInit = { + headers, + method, + cache, + credentials, + mode, + }; + if (data) { + requestOptions.body = JSON.stringify(data); + } + + const res = await fetch(requestUrl, requestOptions); + const { statusText, status } = res; + + if (res.ok) { + return res.json(); + } + + const error = await res.json(); + throw new RemoteServerError( + statusText, + requestUrl, + status, + JSON.stringify(error.message) + ); +} + +function removeTrailingSlash(hubApiUrl = "https://hub.arcgis.com") { + return hubApiUrl.replace(/\/$/, ""); +} + +function combineParams(orvalParams: IOrvalParams, options: ICustomParams) { + const headers = new Headers({ + ...orvalParams.headers, + ...options.headers, + }); + if (options.token) { + headers.set("Authorization", options.token); + } + + const params = { + ...orvalParams.params, + ...options.params, + }; + + return { headers, params }; +} + +class RemoteServerError extends Error { + status: number; + url: string; + error: string; + + constructor(message: string, url: string, status: number, error: string) { + super(message); + this.status = status; + this.url = url; + this.error = error; + } +} diff --git a/packages/common/src/newsletters/api/subscriptions.ts b/packages/common/src/newsletters/api/subscriptions.ts new file mode 100644 index 00000000000..568175397ee --- /dev/null +++ b/packages/common/src/newsletters/api/subscriptions.ts @@ -0,0 +1,81 @@ +import { + ISubscribeParams, + ISubscription, + ICreateSubscriptionParams, + IGetSubscriptionsParams, + IGetSubscriptionParams, + IUpdateSubscriptionParams, +} from "./types"; +import { authenticateRequest } from "./utils/authenticate-request"; +import { + subscribe as _subscribe, + createSubscription as _createSubscription, + getSubscriptions as _getSubscriptions, + getSubscription as _getSubscription, + updateSubscription as _updateSubscription, +} from "./orval/api/orval-newsletters"; + +/** + * create a subscription for user (existing or not) to a newsletter + * + * @param {ISubscribeParams} options + * @return {Promise} + */ +export async function subscribe( + options: ISubscribeParams +): Promise { + options.token = await authenticateRequest(options); + return _subscribe(options.data, options); +} + +/** + * create a subscription for user (existing) to a newsletter + * + * @param {ICreateSubscriptionParams} options + * @return {Promise} + */ +export async function createSubscription( + options: ICreateSubscriptionParams +): Promise { + options.token = await authenticateRequest(options); + return _createSubscription(options.data, options); +} + +/** + * get subscriptions + * + * @param {IGetSubscriptionsParams} options + * @return {Promise} + */ +export async function getSubscriptions( + options: IGetSubscriptionsParams +): Promise { + options.token = await authenticateRequest(options); + return _getSubscriptions(options.data, options); +} + +/** + * get a subscription + * + * @param {IGetSubscriptionParams} options + * @return {Promise} + */ +export async function getSubscription( + options: IGetSubscriptionParams +): Promise { + options.token = await authenticateRequest(options); + return _getSubscription(options.subscriptionId, options); +} + +/** + * update a subscription + * + * @param {IUpdateSubscriptionParams} options + * @return {Promise} + */ +export async function updateSubscription( + options: IUpdateSubscriptionParams +): Promise { + options.token = await authenticateRequest(options); + return _updateSubscription(options.subscriptionId, options.data, options); +} diff --git a/packages/common/src/newsletters/api/types.ts b/packages/common/src/newsletters/api/types.ts new file mode 100644 index 00000000000..ef5add22001 --- /dev/null +++ b/packages/common/src/newsletters/api/types.ts @@ -0,0 +1,75 @@ +export { + Cadence, + DeliveryMethod, + ICreateEventMetadata, + ICreateTelemetryReportMetadata, + INotificationSpec, + ISubscribeMetadata, + ISubscription, + ISubscriptionMetadata, + IUser, + SystemNotificationSpecNames, +} from "./orval/api/orval-newsletters"; +import { IHubRequestOptions } from "../../types"; +import { + GetSubscriptionsParams, + ICreateSubscription, + ICreateUser, + ISubscribe, + IUpdateSubscription, + IUpdateUser, +} from "./orval/api/orval-newsletters"; + +/** + * options for making requests against the Newsletters API + * + * @export + * @interface INewslettersRequestOptions + * @extends IHubRequestOptions + */ +export interface INewslettersRequestOptions + extends Omit, + Pick { + httpMethod?: "GET" | "POST" | "PATCH" | "DELETE"; + isPortal?: boolean; + token?: string; + data?: { + [key: string]: any; + }; +} + +// users +export interface ICreateUserParams extends INewslettersRequestOptions { + data: ICreateUser; +} + +export interface IGetUserParams extends INewslettersRequestOptions { + userId: string; +} +export interface IUpdateUserParams extends INewslettersRequestOptions { + userId: string; + data: IUpdateUser; +} +export interface IDeleteUserParams extends INewslettersRequestOptions { + userId: string; +} + +// subscribe (create user if not exists and subscription) +export interface ISubscribeParams extends INewslettersRequestOptions { + data: ISubscribe; +} + +// subscriptions +export interface ICreateSubscriptionParams extends INewslettersRequestOptions { + data: ICreateSubscription; +} +export interface IGetSubscriptionsParams extends INewslettersRequestOptions { + data: GetSubscriptionsParams; +} +export interface IGetSubscriptionParams extends INewslettersRequestOptions { + subscriptionId: number; +} +export interface IUpdateSubscriptionParams extends INewslettersRequestOptions { + subscriptionId: number; + data: IUpdateSubscription; +} diff --git a/packages/common/src/newsletters/api/users.ts b/packages/common/src/newsletters/api/users.ts new file mode 100644 index 00000000000..454b60e4f57 --- /dev/null +++ b/packages/common/src/newsletters/api/users.ts @@ -0,0 +1,58 @@ +import { + ICreateUserParams, + IDeleteUserParams, + IGetUserParams, + IUpdateUserParams, + IUser, +} from "./types"; +import { authenticateRequest } from "./utils/authenticate-request"; +import { + createUser as _createUser, + getUser as _getUser, + updateUser as _updateUser, + deleteUser as _deleteUser, +} from "./orval/api/orval-newsletters"; + +/** + * create a user + * + * @param {ICreateUserParams} options + * @return {Promise} + */ +export async function createUser(options: ICreateUserParams): Promise { + options.token = await authenticateRequest(options); + return _createUser(options.data, options); +} + +/** + * get a user + * + * @param {IGetUserParams} options + * @return {Promise} + */ +export async function getUser(options: IGetUserParams): Promise { + options.token = await authenticateRequest(options); + return _getUser(options.userId, options); +} + +/** + * update a user + * + * @param {IUpdateUserParams} options + * @return {Promise} + */ +export async function updateUser(options: IUpdateUserParams): Promise { + options.token = await authenticateRequest(options); + return _updateUser(options.userId, options.data, options); +} + +/** + * delete a user + * + * @param {IDeleteUserParams} options + * @return {Promise} + */ +export async function deleteUser(options: IDeleteUserParams): Promise { + options.token = await authenticateRequest(options); + return _deleteUser(options.userId, options); +} diff --git a/packages/common/src/newsletters/api/utils/authenticate-request.ts b/packages/common/src/newsletters/api/utils/authenticate-request.ts new file mode 100644 index 00000000000..a5d540bc7a5 --- /dev/null +++ b/packages/common/src/newsletters/api/utils/authenticate-request.ts @@ -0,0 +1,20 @@ +import { INewslettersRequestOptions } from "../types"; + +/** + * return a token created using options.authentication or set on options.token + * + * @export + * @param {INewslettersRequestOptions} options + * @return {*} {Promise} + */ +export function authenticateRequest( + options: INewslettersRequestOptions +): Promise { + const { token, authentication } = options; + + if (authentication) { + return authentication.getToken(authentication.portal); + } + + return Promise.resolve(token); +} diff --git a/packages/common/src/newsletters/index.ts b/packages/common/src/newsletters/index.ts new file mode 100644 index 00000000000..d158c576401 --- /dev/null +++ b/packages/common/src/newsletters/index.ts @@ -0,0 +1 @@ +export * from "./api"; diff --git a/packages/common/test/newsletters/api/subscriptions.test.ts b/packages/common/test/newsletters/api/subscriptions.test.ts new file mode 100644 index 00000000000..c5777681493 --- /dev/null +++ b/packages/common/test/newsletters/api/subscriptions.test.ts @@ -0,0 +1,175 @@ +import { + ICreateSubscriptionParams, + ISubscription, + createSubscription, + Cadence, + DeliveryMethod, + SystemNotificationSpecNames, + subscribe, + getSubscriptions, + ISubscribeParams, + IGetSubscriptionParams, + getSubscription, + IGetSubscriptionsParams, + IUpdateSubscriptionParams, + updateSubscription, +} from "../../../src/newsletters/api"; +import * as authenticateRequestModule from "../../../src/newsletters/api/utils/authenticate-request"; +import * as orvalModule from "../../../src/newsletters/api/orval/api/orval-newsletters"; + +describe("Subscriptions", () => { + const token = "aaa"; + let authenticateRequestSpy: any; + + beforeEach(() => { + authenticateRequestSpy = spyOn( + authenticateRequestModule, + "authenticateRequest" + ).and.callFake(async () => token); + }); + + describe("subscribe", () => { + it("should subscribe", async () => { + const mockSubscription = { + subscription: "mock", + } as unknown as ISubscription; + const subscribeSpy = spyOn(orvalModule, "subscribe").and.callFake( + async () => mockSubscription + ); + + const options: ISubscribeParams = { + data: { + cadence: Cadence.WEEKLY, + deliveryMethod: DeliveryMethod.EMAIL, + notificationSpecName: SystemNotificationSpecNames.TELEMETRY_REPORT, + metadata: { + hostname: "mock.com", + }, + }, + }; + + const result = await subscribe(options); + expect(result).toEqual(mockSubscription); + + expect(authenticateRequestSpy).toHaveBeenCalledWith(options); + expect(subscribeSpy).toHaveBeenCalledWith(options.data, { + ...options, + token, + }); + }); + }); + + describe("createSubscription", () => { + it("should create a subscription", async () => { + const mockSubscription = { + subscription: "mock", + } as unknown as ISubscription; + const createSubscriptionSpy = spyOn( + orvalModule, + "createSubscription" + ).and.callFake(async () => mockSubscription); + + const options: ICreateSubscriptionParams = { + data: { + cadence: Cadence.WEEKLY, + deliveryMethod: DeliveryMethod.EMAIL, + notificationSpecName: SystemNotificationSpecNames.TELEMETRY_REPORT, + metadata: { + hostname: "mock.com", + }, + userId: "111", + }, + }; + + const result = await createSubscription(options); + expect(result).toEqual(mockSubscription); + + expect(authenticateRequestSpy).toHaveBeenCalledWith(options); + expect(createSubscriptionSpy).toHaveBeenCalledWith(options.data, { + ...options, + token, + }); + }); + }); + + describe("getSubscription", () => { + it("should get a subscription", async () => { + const mockSubscription = { + subscription: "mock", + } as unknown as ISubscription; + const getSubscriptionSpy = spyOn( + orvalModule, + "getSubscription" + ).and.callFake(async () => mockSubscription); + + const options: IGetSubscriptionParams = { + subscriptionId: 111, + }; + + const result = await getSubscription(options); + expect(result).toEqual(mockSubscription); + + expect(authenticateRequestSpy).toHaveBeenCalledWith(options); + expect(getSubscriptionSpy).toHaveBeenCalledWith(options.subscriptionId, { + ...options, + token, + }); + }); + }); + + describe("getSubscriptions", () => { + it("should get subscriptions", async () => { + const mockSubscription = { + subscription: "mock", + } as unknown as ISubscription; + const getSubscriptionsSpy = spyOn( + orvalModule, + "getSubscriptions" + ).and.callFake(async () => [mockSubscription]); + + const options: IGetSubscriptionsParams = { + data: { + userId: "111", + cadence: Cadence.WEEKLY, + active: true, + }, + }; + + const result = await getSubscriptions(options); + expect(result).toEqual([mockSubscription]); + + expect(authenticateRequestSpy).toHaveBeenCalledWith(options); + expect(getSubscriptionsSpy).toHaveBeenCalledWith(options.data, { + ...options, + token, + }); + }); + }); + + describe("updateSubscription", () => { + it("should update a subscription", async () => { + const mockSubscription = { + subscription: "mock", + } as unknown as ISubscription; + const updateSubscriptionSpy = spyOn( + orvalModule, + "updateSubscription" + ).and.callFake(async () => mockSubscription); + + const options: IUpdateSubscriptionParams = { + subscriptionId: 111, + data: { cadence: Cadence.MONTHLY }, + }; + + const result = await updateSubscription(options); + expect(result).toEqual(mockSubscription); + + expect(authenticateRequestSpy).toHaveBeenCalledWith(options); + expect(updateSubscriptionSpy).toHaveBeenCalledWith( + options.subscriptionId, + options.data, + { ...options, token } + ); + }); + }); +}); diff --git a/packages/common/test/newsletters/api/users.test.ts b/packages/common/test/newsletters/api/users.test.ts new file mode 100644 index 00000000000..c3b2346c105 --- /dev/null +++ b/packages/common/test/newsletters/api/users.test.ts @@ -0,0 +1,114 @@ +import { + ICreateUserParams, + IDeleteUserParams, + IGetUserParams, + IUpdateUserParams, + IUser, + createUser, + deleteUser, + getUser, + updateUser, +} from "../../../src/newsletters/api"; +import * as authenticateRequestModule from "../../../src/newsletters/api/utils/authenticate-request"; +import * as orvalModule from "../../../src/newsletters/api/orval/api/orval-newsletters"; + +describe("Users", () => { + const token = "aaa"; + let authenticateRequestSpy: any; + + beforeEach(() => { + authenticateRequestSpy = spyOn( + authenticateRequestModule, + "authenticateRequest" + ).and.callFake(async () => token); + }); + + describe("createUser", () => { + it("should create a new user", async () => { + const mockUser = { user: "mock" } as unknown as IUser; + const createUserSpy = spyOn(orvalModule, "createUser").and.callFake( + async () => mockUser + ); + + const options: ICreateUserParams = { + data: {}, + }; + + const result = await createUser(options); + expect(result).toEqual(mockUser); + + expect(authenticateRequestSpy).toHaveBeenCalledWith(options); + expect(createUserSpy).toHaveBeenCalledWith(options.data, { + ...options, + token, + }); + }); + }); + + describe("getUser", () => { + it("should get a user", async () => { + const mockUser = { user: "mock" } as unknown as IUser; + const getUserSpy = spyOn(orvalModule, "getUser").and.callFake( + async () => mockUser + ); + + const options: IGetUserParams = { + userId: "111", + }; + + const result = await getUser(options); + expect(result).toEqual(mockUser); + + expect(authenticateRequestSpy).toHaveBeenCalledWith(options); + expect(getUserSpy).toHaveBeenCalledWith(options.userId, { + ...options, + token, + }); + }); + }); + + describe("updateUser", () => { + it("should update a user", async () => { + const mockUser = { user: "mock" } as unknown as IUser; + const updateUserSpy = spyOn(orvalModule, "updateUser").and.callFake( + async () => mockUser + ); + + const options: IUpdateUserParams = { + userId: "111", + data: { email: "newemail@mock.com" }, + }; + + const result = await updateUser(options); + expect(result).toEqual(mockUser); + + expect(authenticateRequestSpy).toHaveBeenCalledWith(options); + expect(updateUserSpy).toHaveBeenCalledWith(options.userId, options.data, { + ...options, + token, + }); + }); + }); + + describe("deleteUser", () => { + it("should delete a user", async () => { + const mockUser = { user: "mock" } as unknown as IUser; + const deleteUserSpy = spyOn(orvalModule, "deleteUser").and.callFake( + async () => mockUser + ); + + const options: IDeleteUserParams = { + userId: "111", + }; + + const result = await deleteUser(options); + expect(result).toEqual(mockUser); + + expect(authenticateRequestSpy).toHaveBeenCalledWith(options); + expect(deleteUserSpy).toHaveBeenCalledWith(options.userId, { + ...options, + token, + }); + }); + }); +}); diff --git a/packages/common/test/newsletters/api/utils/authenticate-request.test.ts b/packages/common/test/newsletters/api/utils/authenticate-request.test.ts new file mode 100644 index 00000000000..0c7c3109b1d --- /dev/null +++ b/packages/common/test/newsletters/api/utils/authenticate-request.test.ts @@ -0,0 +1,36 @@ +import { IAuthenticationManager } from "@esri/arcgis-rest-request"; +import { INewslettersRequestOptions } from "../../../../src/newsletters/api"; +import { authenticateRequest } from "../../../../src/newsletters/api/utils/authenticate-request"; + +describe("authenticateRequest", () => { + let getTokenSpy: any; + + const portal = "https://foo.com"; + const token = "aaa"; + const authentication = { + portal, + async getToken() { + return token; + }, + } as IAuthenticationManager; + + beforeEach(() => { + getTokenSpy = spyOn(authentication, "getToken").and.callThrough(); + }); + + it("returns params.token if provided", async () => { + const options: INewslettersRequestOptions = { token: "bbb" }; + + const result = await authenticateRequest(options); + expect(result).toEqual("bbb"); + expect(getTokenSpy).not.toHaveBeenCalled(); + }); + + it("returns token from authentication if params.token not provided", async () => { + const options = { authentication } as INewslettersRequestOptions; + + const result = await authenticateRequest(options); + expect(result).toEqual(token); + expect(getTokenSpy).toHaveBeenCalledWith(portal); + }); +});