@open-loyalty/mcp-server 1.0.3 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +180 -177
- package/dist/auth/provider.js +2 -14
- package/dist/auth/storage.js +22 -0
- package/dist/client/http.d.ts +5 -0
- package/dist/client/http.js +62 -3
- package/dist/config.d.ts +6 -5
- package/dist/config.js +15 -11
- package/dist/http.js +170 -65
- package/dist/instructions.d.ts +5 -0
- package/dist/instructions.js +420 -0
- package/dist/prompts/fan-engagement-setup.d.ts +107 -0
- package/dist/prompts/fan-engagement-setup.js +492 -0
- package/dist/server.d.ts +1 -1
- package/dist/server.js +68 -278
- package/dist/tools/achievement/handlers.d.ts +117 -0
- package/dist/tools/achievement/handlers.js +161 -0
- package/dist/tools/achievement/index.d.ts +479 -0
- package/dist/tools/achievement/index.js +74 -0
- package/dist/tools/achievement/schemas.d.ts +433 -0
- package/dist/tools/achievement/schemas.js +142 -0
- package/dist/tools/achievement.d.ts +155 -121
- package/dist/tools/achievement.js +82 -39
- package/dist/tools/admin.d.ts +18 -6
- package/dist/tools/admin.js +24 -12
- package/dist/tools/analytics.d.ts +29 -11
- package/dist/tools/analytics.js +58 -48
- package/dist/tools/apikey.d.ts +10 -3
- package/dist/tools/apikey.js +13 -6
- package/dist/tools/audit.d.ts +6 -2
- package/dist/tools/audit.js +8 -4
- package/dist/tools/badge.d.ts +14 -6
- package/dist/tools/badge.js +36 -27
- package/dist/tools/campaign/handlers.d.ts +42 -0
- package/dist/tools/campaign/handlers.js +223 -0
- package/dist/tools/campaign/index.d.ts +783 -0
- package/dist/tools/campaign/index.js +112 -0
- package/dist/tools/campaign/member-handlers.d.ts +60 -0
- package/dist/tools/campaign/member-handlers.js +159 -0
- package/dist/tools/campaign/schemas.d.ts +704 -0
- package/dist/tools/campaign/schemas.js +259 -0
- package/dist/tools/campaign/types.d.ts +161 -0
- package/dist/tools/campaign/types.js +2 -0
- package/dist/tools/campaign.d.ts +41 -16
- package/dist/tools/campaign.js +38 -25
- package/dist/tools/custom-event.d.ts +315 -0
- package/dist/tools/custom-event.js +270 -0
- package/dist/tools/export.d.ts +12 -4
- package/dist/tools/export.js +25 -20
- package/dist/tools/import.d.ts +9 -3
- package/dist/tools/import.js +33 -21
- package/dist/tools/index.d.ts +3 -11
- package/dist/tools/index.js +17 -475
- package/dist/tools/member/handlers.d.ts +111 -0
- package/dist/tools/member/handlers.js +206 -0
- package/dist/tools/member/index.d.ts +169 -0
- package/dist/tools/member/index.js +92 -0
- package/dist/tools/member/schemas.d.ts +89 -0
- package/dist/tools/member/schemas.js +65 -0
- package/dist/tools/member.d.ts +21 -0
- package/dist/tools/member.js +56 -62
- package/dist/tools/points.d.ts +19 -6
- package/dist/tools/points.js +51 -49
- package/dist/tools/referral/handlers.d.ts +47 -0
- package/dist/tools/referral/handlers.js +115 -0
- package/dist/tools/referral/index.d.ts +44 -0
- package/dist/tools/referral/index.js +44 -0
- package/dist/tools/referral/schemas.d.ts +34 -0
- package/dist/tools/referral/schemas.js +52 -0
- package/dist/tools/reward/handlers.d.ts +110 -0
- package/dist/tools/reward/handlers.js +289 -0
- package/dist/tools/reward/index.d.ts +177 -0
- package/dist/tools/reward/index.js +90 -0
- package/dist/tools/reward/schemas.d.ts +116 -0
- package/dist/tools/reward/schemas.js +91 -0
- package/dist/tools/reward.d.ts +18 -0
- package/dist/tools/reward.js +56 -66
- package/dist/tools/role.d.ts +26 -7
- package/dist/tools/role.js +25 -12
- package/dist/tools/segment/handlers.d.ts +87 -0
- package/dist/tools/segment/handlers.js +174 -0
- package/dist/tools/segment/index.d.ts +395 -0
- package/dist/tools/segment/index.js +87 -0
- package/dist/tools/segment/schemas.d.ts +337 -0
- package/dist/tools/segment/schemas.js +79 -0
- package/dist/tools/segment.d.ts +29 -10
- package/dist/tools/segment.js +84 -50
- package/dist/tools/store.d.ts +12 -4
- package/dist/tools/store.js +16 -8
- package/dist/tools/tierset.d.ts +19 -7
- package/dist/tools/tierset.js +44 -35
- package/dist/tools/transaction.d.ts +16 -8
- package/dist/tools/transaction.js +25 -21
- package/dist/tools/wallet-type.d.ts +7 -3
- package/dist/tools/wallet-type.js +14 -12
- package/dist/tools/webhook.d.ts +23 -10
- package/dist/tools/webhook.js +135 -33
- package/dist/types/schemas/achievement.d.ts +12 -309
- package/dist/types/schemas/achievement.js +0 -13
- package/dist/types/schemas/admin.d.ts +10 -97
- package/dist/types/schemas/admin.js +0 -38
- package/dist/types/schemas/badge.d.ts +0 -37
- package/dist/types/schemas/badge.js +0 -11
- package/dist/types/schemas/campaign.d.ts +64 -832
- package/dist/types/schemas/campaign.js +2 -25
- package/dist/types/schemas/common.d.ts +5 -0
- package/dist/types/schemas/common.js +5 -0
- package/dist/types/schemas/export.d.ts +0 -17
- package/dist/types/schemas/export.js +0 -7
- package/dist/types/schemas/member.d.ts +37 -176
- package/dist/types/schemas/member.js +0 -27
- package/dist/types/schemas/points.d.ts +0 -63
- package/dist/types/schemas/points.js +0 -22
- package/dist/types/schemas/reward.d.ts +71 -68
- package/dist/types/schemas/reward.js +8 -28
- package/dist/types/schemas/role.d.ts +0 -100
- package/dist/types/schemas/role.js +0 -29
- package/dist/types/schemas/segment.d.ts +0 -58
- package/dist/types/schemas/segment.js +0 -17
- package/dist/types/schemas/tierset.d.ts +0 -176
- package/dist/types/schemas/tierset.js +0 -27
- package/dist/types/schemas/transaction.d.ts +23 -254
- package/dist/types/schemas/transaction.js +0 -7
- package/dist/types/schemas/wallet-type.d.ts +8 -8
- package/dist/types/schemas/wallet-type.js +1 -1
- package/dist/types/schemas/webhook.d.ts +0 -58
- package/dist/types/schemas/webhook.js +0 -12
- package/dist/utils/errors.js +30 -3
- package/dist/utils/payload.d.ts +12 -0
- package/dist/utils/payload.js +14 -0
- package/dist/workflows/app-login-streak.d.ts +39 -0
- package/dist/workflows/app-login-streak.js +298 -0
- package/dist/workflows/early-arrival.d.ts +33 -0
- package/dist/workflows/early-arrival.js +148 -0
- package/dist/workflows/index.d.ts +101 -0
- package/dist/workflows/index.js +208 -0
- package/dist/workflows/match-attendance.d.ts +45 -0
- package/dist/workflows/match-attendance.js +308 -0
- package/dist/workflows/sportsbar-visit.d.ts +41 -0
- package/dist/workflows/sportsbar-visit.js +284 -0
- package/dist/workflows/vod-watching.d.ts +43 -0
- package/dist/workflows/vod-watching.js +326 -0
- package/package.json +10 -2
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
export declare const WalletTypeSchema: z.ZodObject<{
|
|
3
3
|
walletTypeId: z.ZodString;
|
|
4
4
|
code: z.ZodString;
|
|
5
|
-
name: z.ZodString
|
|
5
|
+
name: z.ZodOptional<z.ZodString>;
|
|
6
6
|
unitSingularName: z.ZodOptional<z.ZodString>;
|
|
7
7
|
unitPluralName: z.ZodOptional<z.ZodString>;
|
|
8
8
|
active: z.ZodBoolean;
|
|
@@ -13,9 +13,9 @@ export declare const WalletTypeSchema: z.ZodObject<{
|
|
|
13
13
|
}, "strip", z.ZodTypeAny, {
|
|
14
14
|
code: string;
|
|
15
15
|
walletTypeId: string;
|
|
16
|
-
name: string;
|
|
17
16
|
active: boolean;
|
|
18
17
|
isDefault: boolean;
|
|
18
|
+
name?: string | undefined;
|
|
19
19
|
unitSingularName?: string | undefined;
|
|
20
20
|
unitPluralName?: string | undefined;
|
|
21
21
|
createdAt?: string | undefined;
|
|
@@ -24,9 +24,9 @@ export declare const WalletTypeSchema: z.ZodObject<{
|
|
|
24
24
|
}, {
|
|
25
25
|
code: string;
|
|
26
26
|
walletTypeId: string;
|
|
27
|
-
name: string;
|
|
28
27
|
active: boolean;
|
|
29
28
|
isDefault: boolean;
|
|
29
|
+
name?: string | undefined;
|
|
30
30
|
unitSingularName?: string | undefined;
|
|
31
31
|
unitPluralName?: string | undefined;
|
|
32
32
|
createdAt?: string | undefined;
|
|
@@ -37,7 +37,7 @@ export declare const WalletTypeListResponseSchema: z.ZodObject<{
|
|
|
37
37
|
items: z.ZodArray<z.ZodObject<{
|
|
38
38
|
walletTypeId: z.ZodString;
|
|
39
39
|
code: z.ZodString;
|
|
40
|
-
name: z.ZodString
|
|
40
|
+
name: z.ZodOptional<z.ZodString>;
|
|
41
41
|
unitSingularName: z.ZodOptional<z.ZodString>;
|
|
42
42
|
unitPluralName: z.ZodOptional<z.ZodString>;
|
|
43
43
|
active: z.ZodBoolean;
|
|
@@ -48,9 +48,9 @@ export declare const WalletTypeListResponseSchema: z.ZodObject<{
|
|
|
48
48
|
}, "strip", z.ZodTypeAny, {
|
|
49
49
|
code: string;
|
|
50
50
|
walletTypeId: string;
|
|
51
|
-
name: string;
|
|
52
51
|
active: boolean;
|
|
53
52
|
isDefault: boolean;
|
|
53
|
+
name?: string | undefined;
|
|
54
54
|
unitSingularName?: string | undefined;
|
|
55
55
|
unitPluralName?: string | undefined;
|
|
56
56
|
createdAt?: string | undefined;
|
|
@@ -59,9 +59,9 @@ export declare const WalletTypeListResponseSchema: z.ZodObject<{
|
|
|
59
59
|
}, {
|
|
60
60
|
code: string;
|
|
61
61
|
walletTypeId: string;
|
|
62
|
-
name: string;
|
|
63
62
|
active: boolean;
|
|
64
63
|
isDefault: boolean;
|
|
64
|
+
name?: string | undefined;
|
|
65
65
|
unitSingularName?: string | undefined;
|
|
66
66
|
unitPluralName?: string | undefined;
|
|
67
67
|
createdAt?: string | undefined;
|
|
@@ -72,9 +72,9 @@ export declare const WalletTypeListResponseSchema: z.ZodObject<{
|
|
|
72
72
|
items: {
|
|
73
73
|
code: string;
|
|
74
74
|
walletTypeId: string;
|
|
75
|
-
name: string;
|
|
76
75
|
active: boolean;
|
|
77
76
|
isDefault: boolean;
|
|
77
|
+
name?: string | undefined;
|
|
78
78
|
unitSingularName?: string | undefined;
|
|
79
79
|
unitPluralName?: string | undefined;
|
|
80
80
|
createdAt?: string | undefined;
|
|
@@ -85,9 +85,9 @@ export declare const WalletTypeListResponseSchema: z.ZodObject<{
|
|
|
85
85
|
items: {
|
|
86
86
|
code: string;
|
|
87
87
|
walletTypeId: string;
|
|
88
|
-
name: string;
|
|
89
88
|
active: boolean;
|
|
90
89
|
isDefault: boolean;
|
|
90
|
+
name?: string | undefined;
|
|
91
91
|
unitSingularName?: string | undefined;
|
|
92
92
|
unitPluralName?: string | undefined;
|
|
93
93
|
createdAt?: string | undefined;
|
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
export const WalletTypeSchema = z.object({
|
|
3
3
|
walletTypeId: z.string(),
|
|
4
4
|
code: z.string(),
|
|
5
|
-
name: z.string(),
|
|
5
|
+
name: z.string().optional(), // Optional: single wallet type endpoint may not return name
|
|
6
6
|
unitSingularName: z.string().optional(),
|
|
7
7
|
unitPluralName: z.string().optional(),
|
|
8
8
|
active: z.boolean(),
|
|
@@ -45,64 +45,6 @@ export declare const WebhookSubscriptionSchema: z.ZodObject<{
|
|
|
45
45
|
createdAt?: string | undefined;
|
|
46
46
|
}>;
|
|
47
47
|
export type WebhookSubscription = z.infer<typeof WebhookSubscriptionSchema>;
|
|
48
|
-
export declare const WebhookSubscriptionCreateInputSchema: z.ZodObject<{
|
|
49
|
-
eventName: z.ZodString;
|
|
50
|
-
url: z.ZodString;
|
|
51
|
-
headers: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
52
|
-
headerName: z.ZodString;
|
|
53
|
-
headerValue: z.ZodString;
|
|
54
|
-
}, "strip", z.ZodTypeAny, {
|
|
55
|
-
headerName: string;
|
|
56
|
-
headerValue: string;
|
|
57
|
-
}, {
|
|
58
|
-
headerName: string;
|
|
59
|
-
headerValue: string;
|
|
60
|
-
}>, "many">>;
|
|
61
|
-
}, "strip", z.ZodTypeAny, {
|
|
62
|
-
url: string;
|
|
63
|
-
eventName: string;
|
|
64
|
-
headers?: {
|
|
65
|
-
headerName: string;
|
|
66
|
-
headerValue: string;
|
|
67
|
-
}[] | undefined;
|
|
68
|
-
}, {
|
|
69
|
-
url: string;
|
|
70
|
-
eventName: string;
|
|
71
|
-
headers?: {
|
|
72
|
-
headerName: string;
|
|
73
|
-
headerValue: string;
|
|
74
|
-
}[] | undefined;
|
|
75
|
-
}>;
|
|
76
|
-
export type WebhookSubscriptionCreateInput = z.infer<typeof WebhookSubscriptionCreateInputSchema>;
|
|
77
|
-
export declare const WebhookSubscriptionUpdateInputSchema: z.ZodObject<{
|
|
78
|
-
eventName: z.ZodOptional<z.ZodString>;
|
|
79
|
-
url: z.ZodOptional<z.ZodString>;
|
|
80
|
-
headers: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
81
|
-
headerName: z.ZodString;
|
|
82
|
-
headerValue: z.ZodString;
|
|
83
|
-
}, "strip", z.ZodTypeAny, {
|
|
84
|
-
headerName: string;
|
|
85
|
-
headerValue: string;
|
|
86
|
-
}, {
|
|
87
|
-
headerName: string;
|
|
88
|
-
headerValue: string;
|
|
89
|
-
}>, "many">>;
|
|
90
|
-
}, "strip", z.ZodTypeAny, {
|
|
91
|
-
headers?: {
|
|
92
|
-
headerName: string;
|
|
93
|
-
headerValue: string;
|
|
94
|
-
}[] | undefined;
|
|
95
|
-
url?: string | undefined;
|
|
96
|
-
eventName?: string | undefined;
|
|
97
|
-
}, {
|
|
98
|
-
headers?: {
|
|
99
|
-
headerName: string;
|
|
100
|
-
headerValue: string;
|
|
101
|
-
}[] | undefined;
|
|
102
|
-
url?: string | undefined;
|
|
103
|
-
eventName?: string | undefined;
|
|
104
|
-
}>;
|
|
105
|
-
export type WebhookSubscriptionUpdateInput = z.infer<typeof WebhookSubscriptionUpdateInputSchema>;
|
|
106
48
|
export declare const WebhookEventTypesSchema: z.ZodObject<{
|
|
107
49
|
items: z.ZodArray<z.ZodString, "many">;
|
|
108
50
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -12,18 +12,6 @@ export const WebhookSubscriptionSchema = z.object({
|
|
|
12
12
|
headers: z.array(WebhookHeaderSchema).optional().describe("Custom headers to include in webhook requests."),
|
|
13
13
|
createdAt: z.string().optional().describe("Creation timestamp (ISO format)."),
|
|
14
14
|
});
|
|
15
|
-
// Input schema for creating a webhook subscription
|
|
16
|
-
export const WebhookSubscriptionCreateInputSchema = z.object({
|
|
17
|
-
eventName: z.string().describe("Event name to subscribe to. Use webhook_events to discover available events."),
|
|
18
|
-
url: z.string().describe("URL to receive webhook events."),
|
|
19
|
-
headers: z.array(WebhookHeaderSchema).optional().describe("Custom headers to include in webhook requests."),
|
|
20
|
-
});
|
|
21
|
-
// Input schema for updating a webhook subscription
|
|
22
|
-
export const WebhookSubscriptionUpdateInputSchema = z.object({
|
|
23
|
-
eventName: z.string().optional().describe("Event name to subscribe to."),
|
|
24
|
-
url: z.string().optional().describe("URL to receive webhook events."),
|
|
25
|
-
headers: z.array(WebhookHeaderSchema).optional().describe("Custom headers to include in webhook requests."),
|
|
26
|
-
});
|
|
27
15
|
// Response schema for webhook event types list
|
|
28
16
|
export const WebhookEventTypesSchema = z.object({
|
|
29
17
|
items: z.array(z.string()).describe("List of available webhook event type names."),
|
package/dist/utils/errors.js
CHANGED
|
@@ -31,6 +31,35 @@ export function formatApiError(error, relatedTool) {
|
|
|
31
31
|
if (status === 400) {
|
|
32
32
|
const errors = data?.errors;
|
|
33
33
|
const errorDetails = errors?.map(e => `${e.path || ''}: ${e.message}`).join('; ') || '';
|
|
34
|
+
const fullMessage = data?.message
|
|
35
|
+
? `${String(data.message)}${errorDetails ? ` (${errorDetails})` : ''}`
|
|
36
|
+
: "Invalid request data";
|
|
37
|
+
// Parse "extra fields" errors - usually means a field shouldn't be at top level
|
|
38
|
+
if (fullMessage.includes("extra fields") || fullMessage.includes("should not contain extra fields")) {
|
|
39
|
+
return new OpenLoyaltyError({
|
|
40
|
+
code: "VALIDATION_ERROR",
|
|
41
|
+
message: fullMessage,
|
|
42
|
+
hint: "The API rejected unexpected fields in your request. Common causes: " +
|
|
43
|
+
"1. A field name is misspelled, " +
|
|
44
|
+
"2. A field should be nested differently (e.g., badgeTypeId may not be supported at creation time), " +
|
|
45
|
+
"3. The field isn't supported for this endpoint. " +
|
|
46
|
+
"Try removing optional fields and adding them back one at a time to identify the problematic field.",
|
|
47
|
+
relatedTool,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
// Parse "selected choice is invalid" errors - enum value mismatch
|
|
51
|
+
if (fullMessage.includes("selected choice is invalid") || fullMessage.includes("The selected choice is invalid")) {
|
|
52
|
+
return new OpenLoyaltyError({
|
|
53
|
+
code: "VALIDATION_ERROR",
|
|
54
|
+
message: fullMessage,
|
|
55
|
+
hint: "An enum value is invalid. Common fixes: " +
|
|
56
|
+
"1. For interval types, use 'calendarDays' (not 'days'), 'calendarWeeks', 'calendarMonths', 'calendarYears'. " +
|
|
57
|
+
"2. For period types, use 'day' (not 'days'), 'week', 'month', 'year'. " +
|
|
58
|
+
"3. For trigger types, check the tool description for valid options. " +
|
|
59
|
+
"4. Omit interval entirely for lifetime/forever limits.",
|
|
60
|
+
relatedTool,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
34
63
|
// If API returns "Validation failed" with no details, it's likely an MCP implementation bug
|
|
35
64
|
const hasNoDetails = !errorDetails && data?.message === "Validation failed";
|
|
36
65
|
const hint = hasNoDetails
|
|
@@ -38,9 +67,7 @@ export function formatApiError(error, relatedTool) {
|
|
|
38
67
|
: "Check the input parameters match the expected format";
|
|
39
68
|
return new OpenLoyaltyError({
|
|
40
69
|
code: "VALIDATION_ERROR",
|
|
41
|
-
message:
|
|
42
|
-
? `${String(data.message)}${errorDetails ? ` (${errorDetails})` : ''}`
|
|
43
|
-
: "Invalid request data",
|
|
70
|
+
message: fullMessage,
|
|
44
71
|
hint,
|
|
45
72
|
relatedTool,
|
|
46
73
|
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Removes undefined values from an object.
|
|
3
|
+
* Useful for building API payloads where undefined fields should be omitted.
|
|
4
|
+
*
|
|
5
|
+
* Note: Only filters top-level undefined values. Nested objects are passed through as-is.
|
|
6
|
+
* Falsy values (false, 0, '', null) are preserved - only undefined is removed.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* omitUndefined({ a: 1, b: undefined, c: false, d: '' })
|
|
10
|
+
* // Returns: { a: 1, c: false, d: '' }
|
|
11
|
+
*/
|
|
12
|
+
export declare function omitUndefined<T extends Record<string, unknown>>(obj: T): Partial<T>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Removes undefined values from an object.
|
|
3
|
+
* Useful for building API payloads where undefined fields should be omitted.
|
|
4
|
+
*
|
|
5
|
+
* Note: Only filters top-level undefined values. Nested objects are passed through as-is.
|
|
6
|
+
* Falsy values (false, 0, '', null) are preserved - only undefined is removed.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* omitUndefined({ a: 1, b: undefined, c: false, d: '' })
|
|
10
|
+
* // Returns: { a: 1, c: false, d: '' }
|
|
11
|
+
*/
|
|
12
|
+
export function omitUndefined(obj) {
|
|
13
|
+
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined));
|
|
14
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App Login Streak Workflow
|
|
3
|
+
*
|
|
4
|
+
* Creates a campaign to reward fans for daily app logins,
|
|
5
|
+
* with achievements for consecutive login streaks.
|
|
6
|
+
*/
|
|
7
|
+
export interface AppLoginStreakConfig {
|
|
8
|
+
/** Coins awarded per daily login */
|
|
9
|
+
coinsPerLogin: number;
|
|
10
|
+
/** Whether to create streak bonus achievement */
|
|
11
|
+
createStreakBonus: boolean;
|
|
12
|
+
/** Number of consecutive days for streak achievement */
|
|
13
|
+
streakDays: number;
|
|
14
|
+
/** Bonus coins for completing the streak */
|
|
15
|
+
streakBonus: number;
|
|
16
|
+
/** Badge name for streak achievement (optional) */
|
|
17
|
+
badgeName?: string;
|
|
18
|
+
/** Season start date (ISO format) */
|
|
19
|
+
seasonStart: string;
|
|
20
|
+
/** Season end date (ISO format) */
|
|
21
|
+
seasonEnd: string;
|
|
22
|
+
}
|
|
23
|
+
export interface AppLoginStreakResult {
|
|
24
|
+
success: boolean;
|
|
25
|
+
loginCampaignId?: string;
|
|
26
|
+
streakAchievementId?: string;
|
|
27
|
+
streakBonusCampaignId?: string;
|
|
28
|
+
errors: string[];
|
|
29
|
+
summary: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Execute the app login streak workflow
|
|
33
|
+
*/
|
|
34
|
+
export declare function executeAppLoginStreakWorkflow(config?: Partial<AppLoginStreakConfig>): Promise<AppLoginStreakResult>;
|
|
35
|
+
export declare const appLoginStreakWorkflow: {
|
|
36
|
+
id: string;
|
|
37
|
+
name: string;
|
|
38
|
+
execute: typeof executeAppLoginStreakWorkflow;
|
|
39
|
+
};
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App Login Streak Workflow
|
|
3
|
+
*
|
|
4
|
+
* Creates a campaign to reward fans for daily app logins,
|
|
5
|
+
* with achievements for consecutive login streaks.
|
|
6
|
+
*/
|
|
7
|
+
import { campaignCreate, campaignList } from "../tools/campaign/handlers.js";
|
|
8
|
+
import { achievementCreate, achievementList } from "../tools/achievement.js";
|
|
9
|
+
import { badgeList } from "../tools/badge.js";
|
|
10
|
+
import { formatOLDate, DEFAULTS } from "../prompts/fan-engagement-setup.js";
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Workflow Implementation
|
|
13
|
+
// ============================================================================
|
|
14
|
+
/**
|
|
15
|
+
* Execute the app login streak workflow
|
|
16
|
+
*/
|
|
17
|
+
export async function executeAppLoginStreakWorkflow(config = {}) {
|
|
18
|
+
const result = {
|
|
19
|
+
success: false,
|
|
20
|
+
errors: [],
|
|
21
|
+
summary: "",
|
|
22
|
+
};
|
|
23
|
+
// Merge with defaults
|
|
24
|
+
const cfg = {
|
|
25
|
+
coinsPerLogin: config.coinsPerLogin ?? DEFAULTS.appLoginStreak.coinsPerLogin,
|
|
26
|
+
createStreakBonus: config.createStreakBonus ?? true,
|
|
27
|
+
streakDays: config.streakDays ?? DEFAULTS.appLoginStreak.streakDays,
|
|
28
|
+
streakBonus: config.streakBonus ?? DEFAULTS.appLoginStreak.streakBonus,
|
|
29
|
+
badgeName: config.badgeName ?? "Dedicated Fan",
|
|
30
|
+
seasonStart: config.seasonStart ?? DEFAULTS.seasonDates.start,
|
|
31
|
+
seasonEnd: config.seasonEnd ?? DEFAULTS.seasonDates.end,
|
|
32
|
+
};
|
|
33
|
+
try {
|
|
34
|
+
// Step 1: Create daily login campaign (limited to 1 per day)
|
|
35
|
+
const loginCampaignResult = await createLoginCampaign(cfg);
|
|
36
|
+
if (loginCampaignResult.error) {
|
|
37
|
+
result.errors.push(loginCampaignResult.error);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
result.loginCampaignId = loginCampaignResult.campaignId;
|
|
41
|
+
}
|
|
42
|
+
// Step 2: Create streak achievement and bonus campaign if enabled
|
|
43
|
+
if (cfg.createStreakBonus) {
|
|
44
|
+
const badges = await getAvailableBadges();
|
|
45
|
+
const achievementResult = await createStreakAchievement(cfg, badges);
|
|
46
|
+
if (achievementResult.error) {
|
|
47
|
+
result.errors.push(achievementResult.error);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
result.streakAchievementId = achievementResult.achievementId;
|
|
51
|
+
// Create bonus campaign for streak achievement
|
|
52
|
+
if (achievementResult.achievementId) {
|
|
53
|
+
const bonusCampaignResult = await createStreakBonusCampaign(achievementResult.achievementId, cfg);
|
|
54
|
+
if (bonusCampaignResult.error) {
|
|
55
|
+
result.errors.push(bonusCampaignResult.error);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
result.streakBonusCampaignId = bonusCampaignResult.campaignId;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Step 3: Verify setup
|
|
64
|
+
const verification = await verifySetup(result);
|
|
65
|
+
if (verification.warnings.length > 0) {
|
|
66
|
+
result.errors.push(...verification.warnings);
|
|
67
|
+
}
|
|
68
|
+
// Determine success
|
|
69
|
+
result.success = result.loginCampaignId !== undefined && result.errors.length === 0;
|
|
70
|
+
// Build summary
|
|
71
|
+
result.summary = buildSummary(cfg, result);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
result.errors.push(`Workflow error: ${error instanceof Error ? error.message : String(error)}`);
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
// ============================================================================
|
|
79
|
+
// Helper Functions
|
|
80
|
+
// ============================================================================
|
|
81
|
+
async function createLoginCampaign(cfg) {
|
|
82
|
+
try {
|
|
83
|
+
const response = await campaignCreate({
|
|
84
|
+
type: "direct",
|
|
85
|
+
trigger: "custom_event",
|
|
86
|
+
event: "app_login",
|
|
87
|
+
translations: {
|
|
88
|
+
en: {
|
|
89
|
+
name: "Daily App Login Reward",
|
|
90
|
+
description: `Earn ${cfg.coinsPerLogin} coins for logging into the app daily`,
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
activity: {
|
|
94
|
+
startsAt: formatOLDate(cfg.seasonStart),
|
|
95
|
+
endsAt: formatOLDate(cfg.seasonEnd),
|
|
96
|
+
},
|
|
97
|
+
rules: [
|
|
98
|
+
{
|
|
99
|
+
name: "Award coins for daily login",
|
|
100
|
+
effects: [
|
|
101
|
+
{
|
|
102
|
+
effect: "give_points",
|
|
103
|
+
pointsRule: { fixedValue: cfg.coinsPerLogin },
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
limits: {
|
|
109
|
+
// Strictly one login reward per day
|
|
110
|
+
executionsPerMember: {
|
|
111
|
+
value: 1,
|
|
112
|
+
interval: { type: "days", value: 1 },
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
active: true,
|
|
116
|
+
});
|
|
117
|
+
return { campaignId: response.campaignId };
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
return {
|
|
121
|
+
error: `Failed to create login campaign: ${error instanceof Error ? error.message : String(error)}`,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async function getAvailableBadges() {
|
|
126
|
+
try {
|
|
127
|
+
const response = await badgeList({});
|
|
128
|
+
const badgeMap = new Map();
|
|
129
|
+
for (const badge of response.badges) {
|
|
130
|
+
if (badge.name && badge.badgeTypeId) {
|
|
131
|
+
badgeMap.set(badge.name.toLowerCase(), badge.badgeTypeId);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return badgeMap;
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return new Map();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function createStreakAchievement(cfg, badges) {
|
|
141
|
+
try {
|
|
142
|
+
const badgeTypeId = cfg.badgeName
|
|
143
|
+
? badges.get(cfg.badgeName.toLowerCase())
|
|
144
|
+
: undefined;
|
|
145
|
+
/**
|
|
146
|
+
* Streak achievements in Open Loyalty use:
|
|
147
|
+
* - periodGoal: 1 (one login per period)
|
|
148
|
+
* - period: { consecutive: X, value: 1 } where X is streak days
|
|
149
|
+
*
|
|
150
|
+
* This tracks consecutive periods (days) where the goal is met.
|
|
151
|
+
*/
|
|
152
|
+
const achievementPayload = {
|
|
153
|
+
translations: {
|
|
154
|
+
en: {
|
|
155
|
+
name: `${cfg.streakDays}-Day Login Streak`,
|
|
156
|
+
description: `Log in ${cfg.streakDays} consecutive days to complete this achievement`,
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
active: true,
|
|
160
|
+
activity: {
|
|
161
|
+
startsAt: formatOLDate(cfg.seasonStart),
|
|
162
|
+
endsAt: formatOLDate(cfg.seasonEnd),
|
|
163
|
+
},
|
|
164
|
+
rules: [
|
|
165
|
+
{
|
|
166
|
+
trigger: "custom_event",
|
|
167
|
+
event: "app_login",
|
|
168
|
+
completeRule: {
|
|
169
|
+
periodGoal: 1,
|
|
170
|
+
period: {
|
|
171
|
+
consecutive: cfg.streakDays,
|
|
172
|
+
value: 1, // 1 day period
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
};
|
|
178
|
+
if (badgeTypeId) {
|
|
179
|
+
achievementPayload.badgeTypeId = badgeTypeId;
|
|
180
|
+
}
|
|
181
|
+
const response = await achievementCreate(achievementPayload);
|
|
182
|
+
return { achievementId: response.achievementId };
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
return {
|
|
186
|
+
error: `Failed to create streak achievement: ${error instanceof Error ? error.message : String(error)}`,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
async function createStreakBonusCampaign(achievementId, cfg) {
|
|
191
|
+
try {
|
|
192
|
+
const response = await campaignCreate({
|
|
193
|
+
type: "direct",
|
|
194
|
+
trigger: "achievement",
|
|
195
|
+
translations: {
|
|
196
|
+
en: {
|
|
197
|
+
name: `${cfg.streakDays}-Day Login Streak Bonus`,
|
|
198
|
+
description: `Bonus ${cfg.streakBonus} coins for maintaining a ${cfg.streakDays}-day login streak`,
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
activity: {
|
|
202
|
+
startsAt: formatOLDate(cfg.seasonStart),
|
|
203
|
+
endsAt: formatOLDate(cfg.seasonEnd),
|
|
204
|
+
},
|
|
205
|
+
rules: [
|
|
206
|
+
{
|
|
207
|
+
name: `Bonus for ${cfg.streakDays}-day streak`,
|
|
208
|
+
effects: [
|
|
209
|
+
{
|
|
210
|
+
effect: "give_points",
|
|
211
|
+
pointsRule: { fixedValue: cfg.streakBonus },
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
conditions: [
|
|
215
|
+
{
|
|
216
|
+
operator: "is_equal",
|
|
217
|
+
attribute: "achievement.achievementId",
|
|
218
|
+
data: { value: achievementId },
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
},
|
|
222
|
+
],
|
|
223
|
+
active: true,
|
|
224
|
+
});
|
|
225
|
+
return { campaignId: response.campaignId };
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
return {
|
|
229
|
+
error: `Failed to create streak bonus campaign: ${error instanceof Error ? error.message : String(error)}`,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
async function verifySetup(result) {
|
|
234
|
+
const warnings = [];
|
|
235
|
+
try {
|
|
236
|
+
if (result.loginCampaignId) {
|
|
237
|
+
const campaigns = await campaignList({ active: true });
|
|
238
|
+
const found = campaigns.campaigns.some((c) => c.campaignId === result.loginCampaignId);
|
|
239
|
+
if (!found) {
|
|
240
|
+
warnings.push("Login campaign created but not found in active campaigns list");
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (result.streakAchievementId) {
|
|
244
|
+
const achievements = await achievementList({ active: true });
|
|
245
|
+
const found = achievements.achievements.some((a) => a.achievementId === result.streakAchievementId);
|
|
246
|
+
if (!found) {
|
|
247
|
+
warnings.push("Streak achievement created but not found in active achievements list");
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
catch (error) {
|
|
252
|
+
warnings.push(`Verification error: ${error instanceof Error ? error.message : String(error)}`);
|
|
253
|
+
}
|
|
254
|
+
return { warnings };
|
|
255
|
+
}
|
|
256
|
+
function buildSummary(cfg, result) {
|
|
257
|
+
const lines = [];
|
|
258
|
+
if (result.loginCampaignId) {
|
|
259
|
+
lines.push(`Daily login campaign created!`);
|
|
260
|
+
lines.push(`\nDaily reward: ${cfg.coinsPerLogin} coins`);
|
|
261
|
+
lines.push(`Limit: 1 reward per day (prevents multiple login scans)`);
|
|
262
|
+
}
|
|
263
|
+
if (result.streakAchievementId) {
|
|
264
|
+
lines.push(`\nStreak Achievement: ${cfg.streakDays} consecutive days`);
|
|
265
|
+
if (cfg.badgeName) {
|
|
266
|
+
lines.push(`Badge: ${cfg.badgeName}`);
|
|
267
|
+
}
|
|
268
|
+
if (result.streakBonusCampaignId) {
|
|
269
|
+
lines.push(`Streak bonus: ${cfg.streakBonus} coins`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
lines.push(`\nSeason: ${cfg.seasonStart} to ${cfg.seasonEnd}`);
|
|
273
|
+
lines.push(`\nCustom event to trigger reward:`);
|
|
274
|
+
lines.push(` Event: app_login`);
|
|
275
|
+
lines.push(`\nExample event payload:`);
|
|
276
|
+
lines.push(`{`);
|
|
277
|
+
lines.push(` "event": "app_login"`);
|
|
278
|
+
lines.push(`}`);
|
|
279
|
+
lines.push(`\nStreak mechanics:`);
|
|
280
|
+
lines.push(`- Fan must log in at least once each day`);
|
|
281
|
+
lines.push(`- Missing a day resets the streak counter`);
|
|
282
|
+
lines.push(`- Achievement completes when ${cfg.streakDays} consecutive days are reached`);
|
|
283
|
+
if (result.errors.length > 0) {
|
|
284
|
+
lines.push(`\nWarnings/Errors:`);
|
|
285
|
+
for (const error of result.errors) {
|
|
286
|
+
lines.push(`- ${error}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return lines.join("\n");
|
|
290
|
+
}
|
|
291
|
+
// ============================================================================
|
|
292
|
+
// Exports
|
|
293
|
+
// ============================================================================
|
|
294
|
+
export const appLoginStreakWorkflow = {
|
|
295
|
+
id: "app-login-streak",
|
|
296
|
+
name: "App Login Streak Campaign",
|
|
297
|
+
execute: executeAppLoginStreakWorkflow,
|
|
298
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Early Arrival Workflow
|
|
3
|
+
*
|
|
4
|
+
* Creates a campaign to reward fans who arrive early to matches.
|
|
5
|
+
* Uses a custom event with minutes_before attribute for conditional rewards.
|
|
6
|
+
*/
|
|
7
|
+
export interface EarlyArrivalConfig {
|
|
8
|
+
/** How early (in minutes) counts as "early arrival" */
|
|
9
|
+
minutesBefore: number;
|
|
10
|
+
/** Coins awarded for early arrival */
|
|
11
|
+
coinsPerArrival: number;
|
|
12
|
+
/** Maximum early arrival bonuses per match (typically 1) */
|
|
13
|
+
limitPerMatch: number;
|
|
14
|
+
/** Season start date (ISO format) */
|
|
15
|
+
seasonStart: string;
|
|
16
|
+
/** Season end date (ISO format) */
|
|
17
|
+
seasonEnd: string;
|
|
18
|
+
}
|
|
19
|
+
export interface EarlyArrivalResult {
|
|
20
|
+
success: boolean;
|
|
21
|
+
campaignId?: string;
|
|
22
|
+
errors: string[];
|
|
23
|
+
summary: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Execute the early arrival workflow
|
|
27
|
+
*/
|
|
28
|
+
export declare function executeEarlyArrivalWorkflow(config?: Partial<EarlyArrivalConfig>): Promise<EarlyArrivalResult>;
|
|
29
|
+
export declare const earlyArrivalWorkflow: {
|
|
30
|
+
id: string;
|
|
31
|
+
name: string;
|
|
32
|
+
execute: typeof executeEarlyArrivalWorkflow;
|
|
33
|
+
};
|