crewly 1.4.60 → 1.4.62
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/config/skills/agent/content-calendar/execute.sh +2 -2
- package/config/skills/agent/marketing/brand-onboarding/SKILL.md +76 -0
- package/config/skills/agent/marketing/brand-onboarding/execute.sh +312 -0
- package/config/skills/agent/marketing/submit-for-approval/SKILL.md +73 -0
- package/config/skills/agent/marketing/submit-for-approval/execute.sh +138 -0
- package/config/skills/agent/marketing/weekly-content-planning/SKILL.md +52 -0
- package/config/skills/agent/marketing/weekly-content-planning/execute.sh +202 -0
- package/config/skills/agent/marketing/weekly-content-planning/execute.test.sh +151 -0
- package/config/skills/agent/marketing/weekly-marketing-report/SKILL.md +51 -0
- package/config/skills/agent/marketing/weekly-marketing-report/execute.sh +190 -0
- package/config/skills/agent/marketing/weekly-marketing-report/execute.test.sh +241 -0
- package/config/skills/orchestrator/send-to-remote/SKILL.md +51 -0
- package/config/skills/orchestrator/send-to-remote/execute.sh +114 -0
- package/config/templates/marketing-team/README.md +42 -0
- package/config/templates/marketing-team/goals.md +21 -0
- package/config/templates/marketing-team/knowledge/docs/brand-voice-guide.md +61 -0
- package/config/templates/marketing-team/knowledge/docs/content-best-practices.md +64 -0
- package/config/templates/marketing-team/knowledge/index.json +24 -0
- package/config/templates/marketing-team/learned-patterns.json +5 -0
- package/config/templates/marketing-team/norms/brand-consistency.md +40 -0
- package/config/templates/marketing-team/norms/content-quality-checklist.md +45 -0
- package/config/templates/marketing-team/quality-gates.yaml +47 -0
- package/config/templates/marketing-team/roles/analyst.md +63 -0
- package/config/templates/marketing-team/roles/strategist.md +26 -0
- package/config/templates/marketing-team/roles/writer.md +58 -0
- package/config/templates/marketing-team/template.json +90 -0
- package/config/templates/marketing-team/workflows/weekly-content-cycle.yaml +48 -0
- package/dist/backend/backend/src/constants.d.ts +9 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +9 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/cross-machine/cross-machine.controller.d.ts +16 -0
- package/dist/backend/backend/src/controllers/cross-machine/cross-machine.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/cross-machine/cross-machine.controller.js +140 -0
- package/dist/backend/backend/src/controllers/cross-machine/cross-machine.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/cross-machine/index.d.ts +7 -0
- package/dist/backend/backend/src/controllers/cross-machine/index.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/cross-machine/index.js +7 -0
- package/dist/backend/backend/src/controllers/cross-machine/index.js.map +1 -0
- package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.js +3 -0
- package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.js +46 -6
- package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-builder.service.d.ts +13 -0
- package/dist/backend/backend/src/services/ai/prompt-builder.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-builder.service.js +50 -5
- package/dist/backend/backend/src/services/ai/prompt-builder.service.js.map +1 -1
- package/dist/backend/backend/src/services/cloud/device-auto-discovery.service.d.ts +8 -0
- package/dist/backend/backend/src/services/cloud/device-auto-discovery.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/device-auto-discovery.service.js +52 -3
- package/dist/backend/backend/src/services/cloud/device-auto-discovery.service.js.map +1 -1
- package/dist/backend/backend/src/services/cloud/relay-client.service.d.ts +5 -1
- package/dist/backend/backend/src/services/cloud/relay-client.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/relay-client.service.js +14 -2
- package/dist/backend/backend/src/services/cloud/relay-client.service.js.map +1 -1
- package/dist/backend/backend/src/services/index.d.ts +2 -0
- package/dist/backend/backend/src/services/index.d.ts.map +1 -1
- package/dist/backend/backend/src/services/index.js +2 -0
- package/dist/backend/backend/src/services/index.js.map +1 -1
- package/dist/backend/backend/src/services/onboarding/brand-onboarding.service.d.ts +155 -0
- package/dist/backend/backend/src/services/onboarding/brand-onboarding.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/onboarding/brand-onboarding.service.js +469 -0
- package/dist/backend/backend/src/services/onboarding/brand-onboarding.service.js.map +1 -0
- package/dist/backend/backend/src/services/onboarding/brand-onboarding.types.d.ts +107 -0
- package/dist/backend/backend/src/services/onboarding/brand-onboarding.types.d.ts.map +1 -0
- package/dist/backend/backend/src/services/onboarding/brand-onboarding.types.js +104 -0
- package/dist/backend/backend/src/services/onboarding/brand-onboarding.types.js.map +1 -0
- package/dist/backend/backend/src/services/onboarding/content-approval.service.d.ts +124 -0
- package/dist/backend/backend/src/services/onboarding/content-approval.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/onboarding/content-approval.service.js +256 -0
- package/dist/backend/backend/src/services/onboarding/content-approval.service.js.map +1 -0
- package/dist/backend/backend/src/services/onboarding/content-approval.types.d.ts +80 -0
- package/dist/backend/backend/src/services/onboarding/content-approval.types.d.ts.map +1 -0
- package/dist/backend/backend/src/services/onboarding/content-approval.types.js +16 -0
- package/dist/backend/backend/src/services/onboarding/content-approval.types.js.map +1 -0
- package/dist/backend/backend/src/services/onboarding/index.d.ts +12 -0
- package/dist/backend/backend/src/services/onboarding/index.d.ts.map +1 -0
- package/dist/backend/backend/src/services/onboarding/index.js +10 -0
- package/dist/backend/backend/src/services/onboarding/index.js.map +1 -0
- package/dist/backend/backend/src/services/slack/cross-machine-message.service.d.ts +147 -0
- package/dist/backend/backend/src/services/slack/cross-machine-message.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/slack/cross-machine-message.service.js +306 -0
- package/dist/backend/backend/src/services/slack/cross-machine-message.service.js.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts +7 -0
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js +76 -0
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack.service.js +8 -2
- package/dist/backend/backend/src/services/slack/slack.service.js.map +1 -1
- package/dist/backend/backend/src/types/cross-machine.types.d.ts +103 -0
- package/dist/backend/backend/src/types/cross-machine.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/cross-machine.types.js +47 -0
- package/dist/backend/backend/src/types/cross-machine.types.js.map +1 -0
- package/dist/cli/backend/src/constants.d.ts +9 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +9 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/cli/src/commands/cloud.d.ts +94 -0
- package/dist/cli/cli/src/commands/cloud.d.ts.map +1 -0
- package/dist/cli/cli/src/commands/cloud.js +323 -0
- package/dist/cli/cli/src/commands/cloud.js.map +1 -0
- package/dist/cli/cli/src/index.js +17 -0
- package/dist/cli/cli/src/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for the Brand Onboarding flow.
|
|
3
|
+
*
|
|
4
|
+
* The onboarding flow collects brand information from SMB owners
|
|
5
|
+
* and generates a Brand Voice Guide used by marketing team agents.
|
|
6
|
+
*
|
|
7
|
+
* @module brand-onboarding-types
|
|
8
|
+
*/
|
|
9
|
+
/** A single onboarding question with metadata. */
|
|
10
|
+
export interface OnboardingQuestion {
|
|
11
|
+
/** Unique question identifier */
|
|
12
|
+
id: string;
|
|
13
|
+
/** Question text displayed to the user */
|
|
14
|
+
text: string;
|
|
15
|
+
/** Input type determines UI rendering and validation */
|
|
16
|
+
type: 'text' | 'select' | 'multi-select';
|
|
17
|
+
/** Available options for select/multi-select types */
|
|
18
|
+
options?: string[];
|
|
19
|
+
/** Whether a response is required */
|
|
20
|
+
required: boolean;
|
|
21
|
+
/** Placeholder/hint text */
|
|
22
|
+
placeholder?: string;
|
|
23
|
+
/** Default value if not answered */
|
|
24
|
+
defaultValue?: string;
|
|
25
|
+
/** Order in the questionnaire (1-based) */
|
|
26
|
+
order: number;
|
|
27
|
+
}
|
|
28
|
+
/** User's answer to a single question. */
|
|
29
|
+
export interface OnboardingAnswer {
|
|
30
|
+
/** Matches OnboardingQuestion.id */
|
|
31
|
+
questionId: string;
|
|
32
|
+
/** User's response value */
|
|
33
|
+
value: string;
|
|
34
|
+
/** Timestamp of when the answer was provided */
|
|
35
|
+
answeredAt: string;
|
|
36
|
+
}
|
|
37
|
+
/** Status of the onboarding session. */
|
|
38
|
+
export type OnboardingStatus = 'not_started' | 'in_progress' | 'completed' | 'generating' | 'done' | 'failed';
|
|
39
|
+
/** A complete onboarding session tracking state. */
|
|
40
|
+
export interface OnboardingSession {
|
|
41
|
+
/** Unique session identifier */
|
|
42
|
+
id: string;
|
|
43
|
+
/** Team ID this onboarding belongs to */
|
|
44
|
+
teamId: string;
|
|
45
|
+
/** Template ID that triggered onboarding */
|
|
46
|
+
templateId: string;
|
|
47
|
+
/** Current status */
|
|
48
|
+
status: OnboardingStatus;
|
|
49
|
+
/** Collected answers so far */
|
|
50
|
+
answers: OnboardingAnswer[];
|
|
51
|
+
/** Index of the current question (0-based) */
|
|
52
|
+
currentQuestionIndex: number;
|
|
53
|
+
/** Total number of questions */
|
|
54
|
+
totalQuestions: number;
|
|
55
|
+
/** Generated Brand Voice Guide content (populated after completion) */
|
|
56
|
+
brandVoiceGuide?: string;
|
|
57
|
+
/** Path where the guide was saved */
|
|
58
|
+
guidePath?: string;
|
|
59
|
+
/** Session creation time */
|
|
60
|
+
createdAt: string;
|
|
61
|
+
/** Last update time */
|
|
62
|
+
updatedAt: string;
|
|
63
|
+
/** Error message if status is 'failed' */
|
|
64
|
+
error?: string;
|
|
65
|
+
}
|
|
66
|
+
/** Structured brand profile extracted from onboarding answers. */
|
|
67
|
+
export interface BrandProfile {
|
|
68
|
+
/** Business name */
|
|
69
|
+
businessName: string;
|
|
70
|
+
/** Industry/vertical */
|
|
71
|
+
industry: string;
|
|
72
|
+
/** One-sentence business description */
|
|
73
|
+
description: string;
|
|
74
|
+
/** Target customer description */
|
|
75
|
+
targetCustomer: string;
|
|
76
|
+
/** Top competitors */
|
|
77
|
+
competitors: string[];
|
|
78
|
+
/** Brand personality words (e.g., Professional, Friendly, Bold) */
|
|
79
|
+
personality: string[];
|
|
80
|
+
/** Content tone */
|
|
81
|
+
tone: 'formal' | 'casual' | 'playful' | 'authoritative';
|
|
82
|
+
/** Marketing goals */
|
|
83
|
+
goals: string[];
|
|
84
|
+
/** Active social platforms */
|
|
85
|
+
platforms: string[];
|
|
86
|
+
/** Example content URLs or descriptions */
|
|
87
|
+
contentExamples: string[];
|
|
88
|
+
}
|
|
89
|
+
/** Options for generating the Brand Voice Guide. */
|
|
90
|
+
export interface GenerateGuideOptions {
|
|
91
|
+
/** Brand profile from questionnaire answers */
|
|
92
|
+
profile: BrandProfile;
|
|
93
|
+
/** Output directory path for the guide file */
|
|
94
|
+
outputDir: string;
|
|
95
|
+
/** Whether to use LLM to enhance the guide with Do's/Don'ts */
|
|
96
|
+
useLLM?: boolean;
|
|
97
|
+
}
|
|
98
|
+
/** The 10 onboarding questions defined by the marketing template spec. */
|
|
99
|
+
export declare const ONBOARDING_QUESTIONS: OnboardingQuestion[];
|
|
100
|
+
/** Default content mix ratios for the Brand Voice Guide. */
|
|
101
|
+
export declare const DEFAULT_CONTENT_MIX: {
|
|
102
|
+
readonly educational: 60;
|
|
103
|
+
readonly behindTheScenes: 20;
|
|
104
|
+
readonly promotional: 15;
|
|
105
|
+
readonly interactive: 5;
|
|
106
|
+
};
|
|
107
|
+
//# sourceMappingURL=brand-onboarding.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brand-onboarding.types.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/onboarding/brand-onboarding.types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,kDAAkD;AAClD,MAAM,WAAW,kBAAkB;IACjC,iCAAiC;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,cAAc,CAAC;IACzC,sDAAsD;IACtD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,qCAAqC;IACrC,QAAQ,EAAE,OAAO,CAAC;IAClB,4BAA4B;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oCAAoC;IACpC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;CACf;AAED,0CAA0C;AAC1C,MAAM,WAAW,gBAAgB;IAC/B,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wCAAwC;AACxC,MAAM,MAAM,gBAAgB,GACxB,aAAa,GACb,aAAa,GACb,WAAW,GACX,YAAY,GACZ,MAAM,GACN,QAAQ,CAAC;AAEb,oDAAoD;AACpD,MAAM,WAAW,iBAAiB;IAChC,gCAAgC;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,yCAAyC;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,qBAAqB;IACrB,MAAM,EAAE,gBAAgB,CAAC;IACzB,+BAA+B;IAC/B,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,8CAA8C;IAC9C,oBAAoB,EAAE,MAAM,CAAC;IAC7B,gCAAgC;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,uEAAuE;IACvE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4BAA4B;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,uBAAuB;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAMD,kEAAkE;AAClE,MAAM,WAAW,YAAY;IAC3B,oBAAoB;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,wBAAwB;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,wCAAwC;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,kCAAkC;IAClC,cAAc,EAAE,MAAM,CAAC;IACvB,sBAAsB;IACtB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,mEAAmE;IACnE,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,mBAAmB;IACnB,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,eAAe,CAAC;IACxD,sBAAsB;IACtB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,8BAA8B;IAC9B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,2CAA2C;IAC3C,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,oDAAoD;AACpD,MAAM,WAAW,oBAAoB;IACnC,+CAA+C;IAC/C,OAAO,EAAE,YAAY,CAAC;IACtB,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,+DAA+D;IAC/D,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAMD,0EAA0E;AAC1E,eAAO,MAAM,oBAAoB,EAAE,kBAAkB,EAmFpD,CAAC;AAEF,4DAA4D;AAC5D,eAAO,MAAM,mBAAmB;;;;;CAKtB,CAAC"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for the Brand Onboarding flow.
|
|
3
|
+
*
|
|
4
|
+
* The onboarding flow collects brand information from SMB owners
|
|
5
|
+
* and generates a Brand Voice Guide used by marketing team agents.
|
|
6
|
+
*
|
|
7
|
+
* @module brand-onboarding-types
|
|
8
|
+
*/
|
|
9
|
+
// =============================================================================
|
|
10
|
+
// Constants
|
|
11
|
+
// =============================================================================
|
|
12
|
+
/** The 10 onboarding questions defined by the marketing template spec. */
|
|
13
|
+
export const ONBOARDING_QUESTIONS = [
|
|
14
|
+
{
|
|
15
|
+
id: 'business_name',
|
|
16
|
+
text: 'What is your business name?',
|
|
17
|
+
type: 'text',
|
|
18
|
+
required: true,
|
|
19
|
+
placeholder: 'e.g., Sunrise Bakery',
|
|
20
|
+
order: 1,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: 'industry',
|
|
24
|
+
text: 'What industry are you in?',
|
|
25
|
+
type: 'text',
|
|
26
|
+
required: true,
|
|
27
|
+
placeholder: 'e.g., Fashion, Food, Fitness, Tech, Education',
|
|
28
|
+
order: 2,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: 'description',
|
|
32
|
+
text: 'Describe your business in one sentence.',
|
|
33
|
+
type: 'text',
|
|
34
|
+
required: true,
|
|
35
|
+
placeholder: 'e.g., We make artisan sourdough bread for health-conscious families.',
|
|
36
|
+
order: 3,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'target_customer',
|
|
40
|
+
text: 'Who is your target customer? (Age, interests, problems they have)',
|
|
41
|
+
type: 'text',
|
|
42
|
+
required: true,
|
|
43
|
+
placeholder: 'e.g., Health-conscious millennials, 25-40, interested in organic food',
|
|
44
|
+
order: 4,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: 'competitors',
|
|
48
|
+
text: 'What are your top 3 competitors?',
|
|
49
|
+
type: 'text',
|
|
50
|
+
required: false,
|
|
51
|
+
placeholder: 'e.g., Blue Apron, HelloFresh, Local Bakery XYZ',
|
|
52
|
+
defaultValue: 'Not specified',
|
|
53
|
+
order: 5,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: 'personality',
|
|
57
|
+
text: 'Describe your brand personality in 3 words.',
|
|
58
|
+
type: 'text',
|
|
59
|
+
required: true,
|
|
60
|
+
placeholder: 'e.g., Professional, Friendly, Bold',
|
|
61
|
+
order: 6,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'tone',
|
|
65
|
+
text: 'What tone should your content have?',
|
|
66
|
+
type: 'select',
|
|
67
|
+
options: ['Formal', 'Casual', 'Playful', 'Authoritative'],
|
|
68
|
+
required: true,
|
|
69
|
+
order: 7,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: 'goals',
|
|
73
|
+
text: 'What are your marketing goals?',
|
|
74
|
+
type: 'multi-select',
|
|
75
|
+
options: ['Brand awareness', 'Lead generation', 'Sales', 'Community building'],
|
|
76
|
+
required: true,
|
|
77
|
+
order: 8,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: 'platforms',
|
|
81
|
+
text: 'Which platforms do you use?',
|
|
82
|
+
type: 'multi-select',
|
|
83
|
+
options: ['X (Twitter)', 'LinkedIn', 'Instagram', 'Facebook'],
|
|
84
|
+
required: true,
|
|
85
|
+
order: 9,
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: 'content_examples',
|
|
89
|
+
text: 'Share 2-3 examples of content you like (URLs or descriptions).',
|
|
90
|
+
type: 'text',
|
|
91
|
+
required: false,
|
|
92
|
+
placeholder: 'e.g., https://twitter.com/brand/status/123, "Wendy\'s snarky tone on Twitter"',
|
|
93
|
+
defaultValue: 'Not specified',
|
|
94
|
+
order: 10,
|
|
95
|
+
},
|
|
96
|
+
];
|
|
97
|
+
/** Default content mix ratios for the Brand Voice Guide. */
|
|
98
|
+
export const DEFAULT_CONTENT_MIX = {
|
|
99
|
+
educational: 60,
|
|
100
|
+
behindTheScenes: 20,
|
|
101
|
+
promotional: 15,
|
|
102
|
+
interactive: 5,
|
|
103
|
+
};
|
|
104
|
+
//# sourceMappingURL=brand-onboarding.types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brand-onboarding.types.js","sourceRoot":"","sources":["../../../../../../backend/src/services/onboarding/brand-onboarding.types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA+GH,gFAAgF;AAChF,YAAY;AACZ,gFAAgF;AAEhF,0EAA0E;AAC1E,MAAM,CAAC,MAAM,oBAAoB,GAAyB;IACxD;QACE,EAAE,EAAE,eAAe;QACnB,IAAI,EAAE,6BAA6B;QACnC,IAAI,EAAE,MAAM;QACZ,QAAQ,EAAE,IAAI;QACd,WAAW,EAAE,sBAAsB;QACnC,KAAK,EAAE,CAAC;KACT;IACD;QACE,EAAE,EAAE,UAAU;QACd,IAAI,EAAE,2BAA2B;QACjC,IAAI,EAAE,MAAM;QACZ,QAAQ,EAAE,IAAI;QACd,WAAW,EAAE,+CAA+C;QAC5D,KAAK,EAAE,CAAC;KACT;IACD;QACE,EAAE,EAAE,aAAa;QACjB,IAAI,EAAE,yCAAyC;QAC/C,IAAI,EAAE,MAAM;QACZ,QAAQ,EAAE,IAAI;QACd,WAAW,EAAE,sEAAsE;QACnF,KAAK,EAAE,CAAC;KACT;IACD;QACE,EAAE,EAAE,iBAAiB;QACrB,IAAI,EAAE,mEAAmE;QACzE,IAAI,EAAE,MAAM;QACZ,QAAQ,EAAE,IAAI;QACd,WAAW,EAAE,uEAAuE;QACpF,KAAK,EAAE,CAAC;KACT;IACD;QACE,EAAE,EAAE,aAAa;QACjB,IAAI,EAAE,kCAAkC;QACxC,IAAI,EAAE,MAAM;QACZ,QAAQ,EAAE,KAAK;QACf,WAAW,EAAE,gDAAgD;QAC7D,YAAY,EAAE,eAAe;QAC7B,KAAK,EAAE,CAAC;KACT;IACD;QACE,EAAE,EAAE,aAAa;QACjB,IAAI,EAAE,6CAA6C;QACnD,IAAI,EAAE,MAAM;QACZ,QAAQ,EAAE,IAAI;QACd,WAAW,EAAE,oCAAoC;QACjD,KAAK,EAAE,CAAC;KACT;IACD;QACE,EAAE,EAAE,MAAM;QACV,IAAI,EAAE,qCAAqC;QAC3C,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,eAAe,CAAC;QACzD,QAAQ,EAAE,IAAI;QACd,KAAK,EAAE,CAAC;KACT;IACD;QACE,EAAE,EAAE,OAAO;QACX,IAAI,EAAE,gCAAgC;QACtC,IAAI,EAAE,cAAc;QACpB,OAAO,EAAE,CAAC,iBAAiB,EAAE,iBAAiB,EAAE,OAAO,EAAE,oBAAoB,CAAC;QAC9E,QAAQ,EAAE,IAAI;QACd,KAAK,EAAE,CAAC;KACT;IACD;QACE,EAAE,EAAE,WAAW;QACf,IAAI,EAAE,6BAA6B;QACnC,IAAI,EAAE,cAAc;QACpB,OAAO,EAAE,CAAC,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,CAAC;QAC7D,QAAQ,EAAE,IAAI;QACd,KAAK,EAAE,CAAC;KACT;IACD;QACE,EAAE,EAAE,kBAAkB;QACtB,IAAI,EAAE,gEAAgE;QACtE,IAAI,EAAE,MAAM;QACZ,QAAQ,EAAE,KAAK;QACf,WAAW,EAAE,+EAA+E;QAC5F,YAAY,EAAE,eAAe;QAC7B,KAAK,EAAE,EAAE;KACV;CACF,CAAC;AAEF,4DAA4D;AAC5D,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,WAAW,EAAE,EAAE;IACf,eAAe,EAAE,EAAE;IACnB,WAAW,EAAE,EAAE;IACf,WAAW,EAAE,CAAC;CACN,CAAC"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content Approval Service for Marketing Team templates.
|
|
3
|
+
*
|
|
4
|
+
* Manages the approval workflow where marketing agents submit content
|
|
5
|
+
* for human review. Content can be approved/rejected via:
|
|
6
|
+
* - Slack text replies ("approve" / "reject")
|
|
7
|
+
* - REST API endpoints
|
|
8
|
+
*
|
|
9
|
+
* This service is designed for the marketing team use case and is
|
|
10
|
+
* separate from the F27 ToolApprovalQueue (which handles tool execution).
|
|
11
|
+
*
|
|
12
|
+
* @module content-approval-service
|
|
13
|
+
*/
|
|
14
|
+
import { ApprovalResolution, ContentApprovalRequest, ContentApprovalStatus, SubmitForApprovalOptions } from './content-approval.types.js';
|
|
15
|
+
/**
|
|
16
|
+
* Service managing content approval requests for marketing teams.
|
|
17
|
+
*
|
|
18
|
+
* Follows singleton pattern. Stores approvals in memory with optional
|
|
19
|
+
* Slack message tracking for interactive approval via text replies.
|
|
20
|
+
*/
|
|
21
|
+
export declare class ContentApprovalService {
|
|
22
|
+
private static instance;
|
|
23
|
+
private approvals;
|
|
24
|
+
/**
|
|
25
|
+
* Get or create the singleton instance.
|
|
26
|
+
*
|
|
27
|
+
* @returns Singleton instance
|
|
28
|
+
*/
|
|
29
|
+
static getInstance(): ContentApprovalService;
|
|
30
|
+
/**
|
|
31
|
+
* Reset the singleton (for testing).
|
|
32
|
+
*/
|
|
33
|
+
static resetInstance(): void;
|
|
34
|
+
/**
|
|
35
|
+
* Submit content for approval.
|
|
36
|
+
*
|
|
37
|
+
* @param options - Content submission details
|
|
38
|
+
* @returns Created approval request
|
|
39
|
+
* @throws Error if team has too many pending approvals
|
|
40
|
+
*/
|
|
41
|
+
submit(options: SubmitForApprovalOptions): ContentApprovalRequest;
|
|
42
|
+
/**
|
|
43
|
+
* Approve a content request.
|
|
44
|
+
*
|
|
45
|
+
* @param approvalId - ID of the approval request
|
|
46
|
+
* @param resolvedBy - Who approved (user ID or identifier)
|
|
47
|
+
* @param feedback - Optional feedback or notes
|
|
48
|
+
* @returns Updated approval request or null if not found
|
|
49
|
+
*/
|
|
50
|
+
approve(approvalId: string, resolvedBy: string, feedback?: string): ContentApprovalRequest | null;
|
|
51
|
+
/**
|
|
52
|
+
* Reject a content request.
|
|
53
|
+
*
|
|
54
|
+
* @param approvalId - ID of the approval request
|
|
55
|
+
* @param resolvedBy - Who rejected
|
|
56
|
+
* @param feedback - Reason for rejection or edit instructions
|
|
57
|
+
* @returns Updated approval request or null if not found
|
|
58
|
+
*/
|
|
59
|
+
reject(approvalId: string, resolvedBy: string, feedback?: string): ContentApprovalRequest | null;
|
|
60
|
+
/**
|
|
61
|
+
* Resolve an approval request (approve or reject).
|
|
62
|
+
*
|
|
63
|
+
* @param resolution - Resolution details
|
|
64
|
+
* @returns Updated request or null
|
|
65
|
+
*/
|
|
66
|
+
resolve(resolution: ApprovalResolution): ContentApprovalRequest | null;
|
|
67
|
+
/**
|
|
68
|
+
* Get a specific approval request.
|
|
69
|
+
*
|
|
70
|
+
* @param approvalId - Approval ID
|
|
71
|
+
* @returns The request or null
|
|
72
|
+
*/
|
|
73
|
+
get(approvalId: string): ContentApprovalRequest | null;
|
|
74
|
+
/**
|
|
75
|
+
* Get all pending approvals for a team.
|
|
76
|
+
*
|
|
77
|
+
* @param teamId - Team ID
|
|
78
|
+
* @returns Array of pending approval requests
|
|
79
|
+
*/
|
|
80
|
+
getPendingByTeam(teamId: string): ContentApprovalRequest[];
|
|
81
|
+
/**
|
|
82
|
+
* Get all approvals for a team (all statuses).
|
|
83
|
+
*
|
|
84
|
+
* @param teamId - Team ID
|
|
85
|
+
* @returns Array of all approval requests
|
|
86
|
+
*/
|
|
87
|
+
getAllByTeam(teamId: string): ContentApprovalRequest[];
|
|
88
|
+
/**
|
|
89
|
+
* Get approval by Slack message timestamp (for Slack reply-based approval).
|
|
90
|
+
*
|
|
91
|
+
* @param channelId - Slack channel ID
|
|
92
|
+
* @param messageTs - Slack message timestamp
|
|
93
|
+
* @returns Matching approval request or null
|
|
94
|
+
*/
|
|
95
|
+
getBySlackMessage(channelId: string, messageTs: string): ContentApprovalRequest | null;
|
|
96
|
+
/**
|
|
97
|
+
* Link a Slack message to an approval request (for tracking replies).
|
|
98
|
+
*
|
|
99
|
+
* @param approvalId - Approval ID
|
|
100
|
+
* @param channelId - Slack channel where the approval message was posted
|
|
101
|
+
* @param messageTs - Slack message timestamp
|
|
102
|
+
* @returns Updated request or null
|
|
103
|
+
*/
|
|
104
|
+
linkSlackMessage(approvalId: string, channelId: string, messageTs: string): ContentApprovalRequest | null;
|
|
105
|
+
/**
|
|
106
|
+
* Get counts by status for a team.
|
|
107
|
+
*
|
|
108
|
+
* @param teamId - Team ID
|
|
109
|
+
* @returns Object with status counts
|
|
110
|
+
*/
|
|
111
|
+
getStats(teamId: string): Record<ContentApprovalStatus, number>;
|
|
112
|
+
/**
|
|
113
|
+
* Format an approval request as a Slack message (plain text for MVP).
|
|
114
|
+
*
|
|
115
|
+
* @param request - The approval request to format
|
|
116
|
+
* @returns Formatted message string ready for Slack
|
|
117
|
+
*/
|
|
118
|
+
formatForSlack(request: ContentApprovalRequest): string;
|
|
119
|
+
/**
|
|
120
|
+
* Expire old pending approvals past the TTL.
|
|
121
|
+
*/
|
|
122
|
+
private expireOldApprovals;
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=content-approval.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-approval.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/onboarding/content-approval.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,EAGrB,wBAAwB,EACzB,MAAM,6BAA6B,CAAC;AAMrC;;;;;GAKG;AACH,qBAAa,sBAAsB;IACjC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAuC;IAC9D,OAAO,CAAC,SAAS,CAAkD;IAEnE;;;;OAIG;IACH,MAAM,CAAC,WAAW,IAAI,sBAAsB;IAO5C;;OAEG;IACH,MAAM,CAAC,aAAa,IAAI,IAAI;IAQ5B;;;;;;OAMG;IACH,MAAM,CAAC,OAAO,EAAE,wBAAwB,GAAG,sBAAsB;IAgCjE;;;;;;;OAOG;IACH,OAAO,CACL,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,GAChB,sBAAsB,GAAG,IAAI;IAIhC;;;;;;;OAOG;IACH,MAAM,CACJ,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,GAChB,sBAAsB,GAAG,IAAI;IAIhC;;;;;OAKG;IACH,OAAO,CAAC,UAAU,EAAE,kBAAkB,GAAG,sBAAsB,GAAG,IAAI;IAkBtE;;;;;OAKG;IACH,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,sBAAsB,GAAG,IAAI;IAItD;;;;;OAKG;IACH,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,sBAAsB,EAAE;IAO1D;;;;;OAKG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,sBAAsB,EAAE;IAMtD;;;;;;OAMG;IACH,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,sBAAsB,GAAG,IAAI;IAStF;;;;;;;OAOG;IACH,gBAAgB,CACd,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,sBAAsB,GAAG,IAAI;IAUhC;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,qBAAqB,EAAE,MAAM,CAAC;IAmB/D;;;;;OAKG;IACH,cAAc,CAAC,OAAO,EAAE,sBAAsB,GAAG,MAAM;IAiCvD;;OAEG;IACH,OAAO,CAAC,kBAAkB;CAa3B"}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content Approval Service for Marketing Team templates.
|
|
3
|
+
*
|
|
4
|
+
* Manages the approval workflow where marketing agents submit content
|
|
5
|
+
* for human review. Content can be approved/rejected via:
|
|
6
|
+
* - Slack text replies ("approve" / "reject")
|
|
7
|
+
* - REST API endpoints
|
|
8
|
+
*
|
|
9
|
+
* This service is designed for the marketing team use case and is
|
|
10
|
+
* separate from the F27 ToolApprovalQueue (which handles tool execution).
|
|
11
|
+
*
|
|
12
|
+
* @module content-approval-service
|
|
13
|
+
*/
|
|
14
|
+
import { randomUUID } from 'crypto';
|
|
15
|
+
import { CONTENT_APPROVAL_TTL_MS, MAX_PENDING_APPROVALS, } from './content-approval.types.js';
|
|
16
|
+
// =============================================================================
|
|
17
|
+
// Service
|
|
18
|
+
// =============================================================================
|
|
19
|
+
/**
|
|
20
|
+
* Service managing content approval requests for marketing teams.
|
|
21
|
+
*
|
|
22
|
+
* Follows singleton pattern. Stores approvals in memory with optional
|
|
23
|
+
* Slack message tracking for interactive approval via text replies.
|
|
24
|
+
*/
|
|
25
|
+
export class ContentApprovalService {
|
|
26
|
+
static instance = null;
|
|
27
|
+
approvals = new Map();
|
|
28
|
+
/**
|
|
29
|
+
* Get or create the singleton instance.
|
|
30
|
+
*
|
|
31
|
+
* @returns Singleton instance
|
|
32
|
+
*/
|
|
33
|
+
static getInstance() {
|
|
34
|
+
if (!ContentApprovalService.instance) {
|
|
35
|
+
ContentApprovalService.instance = new ContentApprovalService();
|
|
36
|
+
}
|
|
37
|
+
return ContentApprovalService.instance;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Reset the singleton (for testing).
|
|
41
|
+
*/
|
|
42
|
+
static resetInstance() {
|
|
43
|
+
ContentApprovalService.instance = null;
|
|
44
|
+
}
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Submission
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
/**
|
|
49
|
+
* Submit content for approval.
|
|
50
|
+
*
|
|
51
|
+
* @param options - Content submission details
|
|
52
|
+
* @returns Created approval request
|
|
53
|
+
* @throws Error if team has too many pending approvals
|
|
54
|
+
*/
|
|
55
|
+
submit(options) {
|
|
56
|
+
// Enforce max pending limit
|
|
57
|
+
this.expireOldApprovals();
|
|
58
|
+
const teamPending = this.getPendingByTeam(options.teamId);
|
|
59
|
+
if (teamPending.length >= MAX_PENDING_APPROVALS) {
|
|
60
|
+
throw new Error(`Team ${options.teamId} has ${teamPending.length} pending approvals (max: ${MAX_PENDING_APPROVALS}). Resolve some before submitting more.`);
|
|
61
|
+
}
|
|
62
|
+
const request = {
|
|
63
|
+
id: randomUUID(),
|
|
64
|
+
teamId: options.teamId,
|
|
65
|
+
submittedBy: options.submittedBy,
|
|
66
|
+
platform: options.platform,
|
|
67
|
+
contentType: options.contentType,
|
|
68
|
+
content: options.content,
|
|
69
|
+
hashtags: options.hashtags,
|
|
70
|
+
visualDirection: options.visualDirection,
|
|
71
|
+
scheduledTime: options.scheduledTime,
|
|
72
|
+
status: 'pending',
|
|
73
|
+
submittedAt: new Date().toISOString(),
|
|
74
|
+
};
|
|
75
|
+
this.approvals.set(request.id, request);
|
|
76
|
+
return request;
|
|
77
|
+
}
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// Resolution
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
/**
|
|
82
|
+
* Approve a content request.
|
|
83
|
+
*
|
|
84
|
+
* @param approvalId - ID of the approval request
|
|
85
|
+
* @param resolvedBy - Who approved (user ID or identifier)
|
|
86
|
+
* @param feedback - Optional feedback or notes
|
|
87
|
+
* @returns Updated approval request or null if not found
|
|
88
|
+
*/
|
|
89
|
+
approve(approvalId, resolvedBy, feedback) {
|
|
90
|
+
return this.resolve({ approvalId, status: 'approved', resolvedBy, feedback });
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Reject a content request.
|
|
94
|
+
*
|
|
95
|
+
* @param approvalId - ID of the approval request
|
|
96
|
+
* @param resolvedBy - Who rejected
|
|
97
|
+
* @param feedback - Reason for rejection or edit instructions
|
|
98
|
+
* @returns Updated approval request or null if not found
|
|
99
|
+
*/
|
|
100
|
+
reject(approvalId, resolvedBy, feedback) {
|
|
101
|
+
return this.resolve({ approvalId, status: 'rejected', resolvedBy, feedback });
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Resolve an approval request (approve or reject).
|
|
105
|
+
*
|
|
106
|
+
* @param resolution - Resolution details
|
|
107
|
+
* @returns Updated request or null
|
|
108
|
+
*/
|
|
109
|
+
resolve(resolution) {
|
|
110
|
+
const request = this.approvals.get(resolution.approvalId);
|
|
111
|
+
if (!request)
|
|
112
|
+
return null;
|
|
113
|
+
if (request.status !== 'pending')
|
|
114
|
+
return request; // Already resolved
|
|
115
|
+
request.status = resolution.status;
|
|
116
|
+
request.resolvedAt = new Date().toISOString();
|
|
117
|
+
request.resolvedBy = resolution.resolvedBy;
|
|
118
|
+
request.feedback = resolution.feedback;
|
|
119
|
+
this.approvals.set(request.id, request);
|
|
120
|
+
return request;
|
|
121
|
+
}
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
// Queries
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
/**
|
|
126
|
+
* Get a specific approval request.
|
|
127
|
+
*
|
|
128
|
+
* @param approvalId - Approval ID
|
|
129
|
+
* @returns The request or null
|
|
130
|
+
*/
|
|
131
|
+
get(approvalId) {
|
|
132
|
+
return this.approvals.get(approvalId) ?? null;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Get all pending approvals for a team.
|
|
136
|
+
*
|
|
137
|
+
* @param teamId - Team ID
|
|
138
|
+
* @returns Array of pending approval requests
|
|
139
|
+
*/
|
|
140
|
+
getPendingByTeam(teamId) {
|
|
141
|
+
this.expireOldApprovals();
|
|
142
|
+
return Array.from(this.approvals.values()).filter(r => r.teamId === teamId && r.status === 'pending');
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Get all approvals for a team (all statuses).
|
|
146
|
+
*
|
|
147
|
+
* @param teamId - Team ID
|
|
148
|
+
* @returns Array of all approval requests
|
|
149
|
+
*/
|
|
150
|
+
getAllByTeam(teamId) {
|
|
151
|
+
return Array.from(this.approvals.values()).filter(r => r.teamId === teamId);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Get approval by Slack message timestamp (for Slack reply-based approval).
|
|
155
|
+
*
|
|
156
|
+
* @param channelId - Slack channel ID
|
|
157
|
+
* @param messageTs - Slack message timestamp
|
|
158
|
+
* @returns Matching approval request or null
|
|
159
|
+
*/
|
|
160
|
+
getBySlackMessage(channelId, messageTs) {
|
|
161
|
+
for (const request of this.approvals.values()) {
|
|
162
|
+
if (request.slackChannelId === channelId && request.slackMessageTs === messageTs) {
|
|
163
|
+
return request;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Link a Slack message to an approval request (for tracking replies).
|
|
170
|
+
*
|
|
171
|
+
* @param approvalId - Approval ID
|
|
172
|
+
* @param channelId - Slack channel where the approval message was posted
|
|
173
|
+
* @param messageTs - Slack message timestamp
|
|
174
|
+
* @returns Updated request or null
|
|
175
|
+
*/
|
|
176
|
+
linkSlackMessage(approvalId, channelId, messageTs) {
|
|
177
|
+
const request = this.approvals.get(approvalId);
|
|
178
|
+
if (!request)
|
|
179
|
+
return null;
|
|
180
|
+
request.slackChannelId = channelId;
|
|
181
|
+
request.slackMessageTs = messageTs;
|
|
182
|
+
this.approvals.set(request.id, request);
|
|
183
|
+
return request;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Get counts by status for a team.
|
|
187
|
+
*
|
|
188
|
+
* @param teamId - Team ID
|
|
189
|
+
* @returns Object with status counts
|
|
190
|
+
*/
|
|
191
|
+
getStats(teamId) {
|
|
192
|
+
const all = this.getAllByTeam(teamId);
|
|
193
|
+
const stats = {
|
|
194
|
+
pending: 0,
|
|
195
|
+
approved: 0,
|
|
196
|
+
rejected: 0,
|
|
197
|
+
expired: 0,
|
|
198
|
+
};
|
|
199
|
+
for (const r of all) {
|
|
200
|
+
stats[r.status] = (stats[r.status] || 0) + 1;
|
|
201
|
+
}
|
|
202
|
+
return stats;
|
|
203
|
+
}
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
// Formatting
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
/**
|
|
208
|
+
* Format an approval request as a Slack message (plain text for MVP).
|
|
209
|
+
*
|
|
210
|
+
* @param request - The approval request to format
|
|
211
|
+
* @returns Formatted message string ready for Slack
|
|
212
|
+
*/
|
|
213
|
+
formatForSlack(request) {
|
|
214
|
+
const lines = [
|
|
215
|
+
`*Content Approval Request* (ID: \`${request.id.slice(0, 8)}\`)`,
|
|
216
|
+
'',
|
|
217
|
+
`*Platform:* ${request.platform}`,
|
|
218
|
+
`*Type:* ${request.contentType}`,
|
|
219
|
+
`*Submitted by:* ${request.submittedBy}`,
|
|
220
|
+
'',
|
|
221
|
+
'---',
|
|
222
|
+
request.content,
|
|
223
|
+
'---',
|
|
224
|
+
];
|
|
225
|
+
if (request.hashtags?.length) {
|
|
226
|
+
lines.push(`*Hashtags:* ${request.hashtags.join(' ')}`);
|
|
227
|
+
}
|
|
228
|
+
if (request.visualDirection) {
|
|
229
|
+
lines.push(`*Visual Direction:* ${request.visualDirection}`);
|
|
230
|
+
}
|
|
231
|
+
if (request.scheduledTime) {
|
|
232
|
+
lines.push(`*Scheduled:* ${request.scheduledTime}`);
|
|
233
|
+
}
|
|
234
|
+
lines.push('');
|
|
235
|
+
lines.push('Reply with *approve* or *reject [reason]* to this message.');
|
|
236
|
+
return lines.join('\n');
|
|
237
|
+
}
|
|
238
|
+
// ---------------------------------------------------------------------------
|
|
239
|
+
// Private Helpers
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
241
|
+
/**
|
|
242
|
+
* Expire old pending approvals past the TTL.
|
|
243
|
+
*/
|
|
244
|
+
expireOldApprovals() {
|
|
245
|
+
const now = Date.now();
|
|
246
|
+
for (const [id, request] of this.approvals.entries()) {
|
|
247
|
+
if (request.status === 'pending' &&
|
|
248
|
+
now - new Date(request.submittedAt).getTime() > CONTENT_APPROVAL_TTL_MS) {
|
|
249
|
+
request.status = 'expired';
|
|
250
|
+
request.resolvedAt = new Date().toISOString();
|
|
251
|
+
this.approvals.set(id, request);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
//# sourceMappingURL=content-approval.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-approval.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/onboarding/content-approval.service.ts"],"names":[],"mappings":"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"}
|