Skip to content

Commit

Permalink
Merge branch 'develop' into 1564/add-logging-for-failed-subscriber-cr…
Browse files Browse the repository at this point in the history
…eation
  • Loading branch information
dhochbaum-dcp authored Oct 29, 2024
2 parents d87d2ac + 98554ab commit 05d0be5
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 4 deletions.
11 changes: 10 additions & 1 deletion server/src/subscriber/subscriber.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ const CHECKS_BEFORE_FAIL = 10;
export class SubscriberController {
apiKey = "";
list = "";
sendgridEnvironment = "";

constructor(
private readonly config: ConfigService,
private readonly subscriberService: SubscriberService
) {
this.apiKey = this.config.get("SENDGRID_API_KEY");
this.list = this.config.get("SENDGRID_LIST");
this.sendgridEnvironment = this.config.get("SENDGRID_ENVIRONMENT")
}

@Post("/subscribers")
Expand All @@ -30,6 +32,13 @@ export class SubscriberController {
return;
}

if(!this.subscriberService.validateSubscriptions(request.body.subscriptions)) {
response.status(400).send({
error: "Invalid list of subscriptions."
})
return;
}

const existingUser = await this.subscriberService.findByEmail(request.body.email)
if(![200, 404].includes(existingUser.code)) {
console.error(existingUser.code, existingUser.message);
Expand All @@ -50,7 +59,7 @@ export class SubscriberController {
}

// If we have reached this point, the user either doesn't exist or isn't signed up for the list
const addToQueue = await this.subscriberService.create(request.body.email, this.list, response)
const addToQueue = await this.subscriberService.create(request.body.email, this.list, this.sendgridEnvironment, request.body.subscriptions, response)

if(addToQueue.isError) {
response.status(addToQueue.code).send({errors: addToQueue.response.body.errors})
Expand Down
72 changes: 69 additions & 3 deletions server/src/subscriber/subscriber.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ import { ConfigService } from "../config/config.service";
import { Client } from "@sendgrid/client";
import crypto from 'crypto';
import * as Sentry from "@sentry/browser";
const validCustomFieldNames = ["K01", "K02", "K03", "K04", "K05", "K06", "K07", "K08", "K09", "K10", "K11", "K12", "K13", "K14", "K15", "K16", "K17", "K18", "X01", "X02", "X03", "X04", "X05", "X06", "X07", "X08", "X09", "X10", "X11", "X12", "M01", "M02", "M03", "M04", "M05", "M06", "M07", "M08", "M09", "M10", "M11", "M12", "Q01", "Q02", "Q03", "Q04", "Q05", "Q06", "Q07", "Q08", "Q09", "Q10", "Q11", "Q12", "Q13", "Q14", "R01", "R02", "R03", "CW"] as const;
export type CustomFieldNameTuple = typeof validCustomFieldNames;
type CustomFieldName = CustomFieldNameTuple[number];

const validCustomFieldValues = [1] as const;
export type CustomFieldValueTuple = typeof validCustomFieldValues;
type CustomFieldValue = CustomFieldValueTuple[number];


type HttpMethod = 'get'|'GET'|'post'|'POST'|'put'|'PUT'|'patch'|'PATCH'|'delete'|'DELETE';
Expand All @@ -15,13 +22,20 @@ function delay(milliseconds){

@Injectable()
export class SubscriberService {
sendgridEnvironmentIdVariable = "";
constructor(
private readonly config: ConfigService,
private client: Client
) {
this.client.setApiKey(this.config.get("SENDGRID_API_KEY"));
this.sendgridEnvironmentIdVariable = `zap_${this.config.get("SENDGRID_ENVIRONMENT")}_id`;
}

/**
* Find a user by their email address.
* @param {string} email
* @returns {object}
*/
async findByEmail(email: string) {
const searchRequest = {
url: "/v3/marketing/contacts/search/emails",
Expand All @@ -40,14 +54,29 @@ export class SubscriberService {
}
}

async create(email: string, list: string, @Res() response) {

/**
* Add a user.
* @param {string} email - The user's email address
* @param {string} list - The email list to which we will add the user
* @param {string} environment - Staging or production
* @param {object} subscriptions - The CDs the user is subscribing to
* @returns {object}
*/
async create(email: string, list: string, environment: string, subscriptions: object, @Res() response) {
const id = crypto.randomUUID();
const addRequest = {
var custom_fields = Object.entries(subscriptions).reduce((acc, curr) => ({...acc, [`zap_${environment}_${curr[0]}`]: curr[1]}), {[`zap_${environment}_confirmed`]: 0})
custom_fields[this.sendgridEnvironmentIdVariable] = id;

var addRequest = {
url: "/v3/marketing/contacts",
method:<HttpMethod> 'PUT',
body: {
"list_ids": [list],
"contacts": [{"email": email, "anonymous_id": id}]
"contacts": [{
"email": email,
custom_fields
}]
}
}

Expand Down Expand Up @@ -105,4 +134,41 @@ export class SubscriberService {
}
}

/**
* Validate a list of subscriptions.
* @param {object} subscriptions - The subscriptions to validate.
* @returns {boolean}
*/
validateSubscriptions(subscriptions: object) {
if (!subscriptions)
return false;

if(!(Object.entries(subscriptions).length>0))
return false;

for (const [key, value] of Object.entries(subscriptions)) {
if (!(this.validateSubscriptionKey(key) && this.validateSubscriptionValue(value))) {
return false
}
}
return true;
};

/**
* Validate the id of a subscription.
* @param {string} key - The board id, which corresponds to a custom field name
* @returns {boolean}
*/
private validateSubscriptionKey(key: string): key is CustomFieldName {
return validCustomFieldNames.includes(key as CustomFieldName);
}

/**
* Validate the status of a subscription.
* @param {number} value - The value which determines whether they wish to subscribe to the corresponding list
* @returns {boolean}
*/
private validateSubscriptionValue(value: number): value is CustomFieldValue {
return validCustomFieldValues.includes(value as CustomFieldValue);
}
}

0 comments on commit 05d0be5

Please sign in to comment.