Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
13,236 changes: 4,996 additions & 8,240 deletions packages/program-boilerplate/package-lock.json

Large diffs are not rendered by default.

22 changes: 11 additions & 11 deletions packages/program-boilerplate/package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"name": "@saasquatch/program-boilerplate",
"version": "3.10.1-1",
"version": "3.10.1-5",
"description": "Boilerplate for writing programs",
"main": "dist/index.js",
"files": [
"dist/"
],
"engines": {
"node": "^18 || ^20 || ^22"
"node": "^20 || ^22 || ^24"
},
"scripts": {
"build": "tsc --declaration",
Expand All @@ -28,27 +28,27 @@
"source-map": "^0.7.4"
},
"devDependencies": {
"@types/express": "^4.17.11",
"@types/node": "^13.13.50",
"@types/express": "^4.17.25",
"@types/node": "^24.10.0",
"@types/supertest": "^2.0.11",
"jest": "^26.6.3",
"jest-cucumber": "^3.0.1",
"jest": "^30.2.0",
"jest-cucumber": "^4.5.0",
"prettier": "^2.2.1",
"source-map": "^0.7.4",
"supertest": "^6.1.3",
"ts-jest": "^26.5.5",
"ts-node": "^9.1.1",
"ts-jest": "^29.4.5",
"ts-node": "^10.9.2",
"typedoc": "^0.17.8",
"typescript": "^3.9.9"
"typescript": "^5.9.3"
},
"dependencies": {
"@saasquatch/jsonata-paths-extractor": "^1.0.1",
"@saasquatch/logger": "^1.5.1",
"@saasquatch/logger": "^2.0.0",
"@saasquatch/schema": "^2.0.0",
"bson-objectid": "^1.3.1",
"compression": "^1.7.4",
"express": "^4.17.1",
"jsonata": "^1.8.4",
"winston": "^3.3.3"
"winston": "^3.18.3"
}
}
2 changes: 1 addition & 1 deletion packages/program-boilerplate/src/jsonata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export function safeJsonata(expression: string, inputData: any) {
return jsonataQuery.evaluate(inputData);
} catch (e) {
ssqtLogger("program-boilerplate").warn(
`Failed to evaluate JSONata expression: ${e.message}`
`Failed to evaluate JSONata expression: ${(e as any).message}`
);
}
}
Expand Down
24 changes: 22 additions & 2 deletions packages/program-boilerplate/src/queries.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export const rewardEmailQuery = `
query ($userId:String!, $accountId:String!, $rewardId:ID!, $programId:ID!, $referralId: ID!) {
query ($userId:String!, $accountId:String!, $rewardId:ID!, $programId:ID!, $referralId: ID!, $eventId: ID, $fetchEvent: Boolean!) {
reward(id:$rewardId) {
...AllFlatRewardFields
}
Expand All @@ -25,6 +25,16 @@ export const rewardEmailQuery = `
}
}

userEvents(filter: {id_eq: $eventId}) @include(if: $fetchEvent) {
data {
id
dateReceived
dateTriggered
dateProcessed
fields
}
}

referral(id:$referralId) {
referrerUser{
firstName
Expand Down Expand Up @@ -66,7 +76,7 @@ export const rewardEmailQuery = `
}
`;

export const nonRewardEmailQueryForReferralPrograms = `query ($userId:String!, $accountId:String!,$programId:ID!, $referralId: ID!) {
export const nonRewardEmailQueryForReferralPrograms = `query ($userId:String!, $accountId:String!,$programId:ID!, $referralId: ID!, $eventId: ID, $fetchEvent: Boolean!) {
user(id:$userId, accountId:$accountId) {
firstName
lastName
Expand All @@ -81,6 +91,16 @@ export const nonRewardEmailQueryForReferralPrograms = `query ($userId:String!, $
fbmessenger: messageLink(programId:$programId,shareMedium:FBMESSENGER)
}

userEvents(filter: {id_eq: $eventId}) @include(if: $fetchEvent) {
data {
id
dateReceived
dateTriggered
dateProcessed
fields
}
}

referral(id:$referralId) {
referrerUser{
firstName
Expand Down
14 changes: 13 additions & 1 deletion packages/program-boilerplate/src/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,17 +244,21 @@ export default class Transaction {
user,
referralId,
rewardId,
eventId,
}: {
emailKey: string;
user: User;
referralId: string;
rewardId?: string;
eventId?: string;
}) {
const variables = {
userId: user.id,
accountId: user.accountId,
programId: this.context.body.program.id,
referralId: referralId,
eventId: eventId,
fetchEvent: !!eventId,
};

const queryVariables = rewardId ? { ...variables, rewardId } : variables;
Expand Down Expand Up @@ -306,6 +310,7 @@ export default class Transaction {
status,
overrideProperties,
dynamicProperties,
eventId,
}: {
emailKey: string;
rewardKey: string;
Expand All @@ -314,6 +319,7 @@ export default class Transaction {
status?: string;
overrideProperties?: any;
dynamicProperties?: any;
eventId?: string;
}) {
const { rewardId } = this.generateReferralReward({
rewardKey,
Expand All @@ -326,7 +332,13 @@ export default class Transaction {
dynamicProperties,
});

this.generateReferralEmail({ emailKey, user, referralId, rewardId });
this.generateReferralEmail({
emailKey,
user,
referralId,
rewardId,
eventId,
});
}

generateRefunds() {
Expand Down
88 changes: 38 additions & 50 deletions packages/program-boilerplate/src/trigger.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,42 @@
import { getLogger as ssqtLogger } from "@saasquatch/logger";
import Transaction from "./transaction";

import {
ProgramTriggerResult,
ProgramTriggerBody,
Program,
ProgramIntrospectionBody,
ProgramTriggerBody,
ProgramTriggerResult,
ProgramValidationBody,
ProgramVariableSchemaRequestBody,
Program,
TriggerType,
ValidationResult,
} from "./types/rpc";
import { getLogger as ssqtLogger } from "@saasquatch/logger";

function handleTriggerError(
triggerType: string,
e: unknown
): { error: string; message?: unknown } {
const stack =
typeof e === "object" && e !== null && "stack" in e ? e.stack : undefined;

const message =
typeof e === "object" && e !== null && "message" in e
? e.message
: undefined;

const errorMes = {
error: `An error occurred in a webtask (${triggerType})`,
// consider not returning stack trace for security reasons
message:
typeof e === "object" && e !== null && "stack" in e ? e.stack : undefined,
};

ssqtLogger("program-boilerplate").error({
message: errorMes.error,
["error.message"]: message,
["error.stack"]: stack,
});

return errorMes;
}

/**
* Triggers the program and returns the result (JSON + HTTP code)
Expand Down Expand Up @@ -75,7 +101,7 @@ function handleProgramTrigger(
): ProgramTriggerResult {
const transaction = new Transaction({ body });

const triggerType = body.activeTrigger.type as TriggerType;
const triggerType = body.activeTrigger.type;
const handleTrigger: any = program[triggerType];

try {
Expand All @@ -88,22 +114,8 @@ function handleProgramTrigger(
code: 200,
};
} catch (e) {
const errorMes = {
error: "An error occurred in a webtask",
// consider not returning stack trace for security reasons
message: e.stack,
};

ssqtLogger("program-boilerplate").error({
message: errorMes.error,
["error.message"]: e.message,
["error.stack"]: e.stack,
});

return {
json: errorMes,
code: 500,
};
const errorMes = handleTriggerError(triggerType, e);
return { json: errorMes, code: 500 };
}
}

Expand Down Expand Up @@ -137,22 +149,8 @@ function handleProgramIntrospection(
code: 200,
};
} catch (e) {
const errorMes = {
error: "An error occurred in a webtask",
// consider not returning stack trace for security reasons
message: e.stack,
};

ssqtLogger("program-boilerplate").error({
message: "Error ocurred in a webtask",
["error.message"]: e.message,
["error.stack"]: e.stack,
});

return {
json: errorMes,
code: 500,
};
const errorMes = handleTriggerError(body.messageType, e);
return { json: errorMes, code: 500 };
}
}

Expand Down Expand Up @@ -218,17 +216,7 @@ function handleProgramVariableSchemaRequest(
try {
newSchema = handleSchemaRequest(schema, triggerType, scheduleKey);
} catch (e) {
const errorMes = {
error:
"An error occurred in a webtask (PROGRAM_TRIGGER_VARIABLES_SCHEMA_REQUEST)",
message: e.stack,
};

ssqtLogger("program-boilerplate").error({
message: errorMes.error,
["error.message"]: e.message,
["error.stack"]: e.stack,
});
handleTriggerError(body.messageType, e);
}

if (!newSchema) {
Expand Down
52 changes: 42 additions & 10 deletions packages/program-boilerplate/src/types/rpc.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ProgramTemplateBuilder } from "@saasquatch/schema/types/ProgramTemplate";
import Transaction from "../transaction";
import { Referral, RSJsonNode, User, UserEvent } from "./saasquatch";

/********************************************************/
/* API */
Expand All @@ -8,15 +9,23 @@ import Transaction from "../transaction";
/**
* The different trigger handlers the programs can export
*/
export type TriggerType =
export type ActiveTriggerType =
| "AFTER_USER_CREATED_OR_UPDATED"
| "AFTER_USER_EVENT_PROCESSED"
| "REFERRAL"
| "PROGRAM_INTROSPECTION"
| "SCHEDULED"
| "REWARD_SCHEDULED"
| "PROGRAM_VALIDATION"
| "PROGRAM_TRIGGER_VARIABLES_SCHEMA_REQUEST";
| "REFERRAL"
| "AFTER_USER_EVENT_PROCESSED";

/**
* The different trigger handlers the programs can export
*/
export type TriggerType =
| ActiveTriggerType
| (
| "PROGRAM_INTROSPECTION"
| "PROGRAM_VALIDATION"
| "PROGRAM_TRIGGER_VARIABLES_SCHEMA_REQUEST"
);

/**
* The Program type
Expand All @@ -37,13 +46,27 @@ export type Program = {
/********************************************************/

/**
* A JSON request body for the PROGRAM_TRIGGER case. This type
* may not be complete.
* A JSON request body for the PROGRAM_TRIGGER case. Defined in core under ProgramTriggerQuery.graphql
*/
export type ProgramTriggerBody = {
messageType: "PROGRAM_TRIGGER";
activeTrigger: any;
program: any;
activeTrigger: {
type: ActiveTriggerType;
time: number;
user: User;
// present for AFTER_USER_CREATED_OR_UPDATED
previous?: User;
// present for AFTER_USER_CREATED_OR_UPDATED and AFTER_USER_EVENT_PROCESSED
events?: UserEvent[];
// present for REFERRAL
referral?: Referral;
[key: string]: any;
};
program: {
id: string;
rules: RSJsonNode;
templateId: string;
};
tenant: {
impactBrandId: string | undefined | null;
settings: {
Expand Down Expand Up @@ -223,3 +246,12 @@ export type ProgramRequirement = {
[key: string]: any;
};
};

export type TriggerSchemaObject = {
type: ProgramTriggerBody["activeTrigger"]["type"];
time: ProgramTriggerBody["activeTrigger"]["time"];
user: ProgramTriggerBody["activeTrigger"]["user"];
previous?: User;
event?: UserEvent;
referral?: Referral;
};
Loading