optimal-cli 1.0.0 → 1.0.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/dist/bin/optimal.d.ts +2 -0
- package/dist/bin/optimal.js +1590 -0
- package/dist/lib/assets/index.d.ts +79 -0
- package/dist/lib/assets/index.js +153 -0
- package/dist/lib/assets.d.ts +20 -0
- package/dist/lib/assets.js +112 -0
- package/dist/lib/auth/index.d.ts +83 -0
- package/dist/lib/auth/index.js +146 -0
- package/dist/lib/board/index.d.ts +39 -0
- package/dist/lib/board/index.js +285 -0
- package/dist/lib/board/types.d.ts +111 -0
- package/dist/lib/board/types.js +1 -0
- package/dist/lib/bot/claim.d.ts +3 -0
- package/dist/lib/bot/claim.js +20 -0
- package/dist/lib/bot/coordinator.d.ts +27 -0
- package/dist/lib/bot/coordinator.js +178 -0
- package/dist/lib/bot/heartbeat.d.ts +6 -0
- package/dist/lib/bot/heartbeat.js +30 -0
- package/dist/lib/bot/index.d.ts +9 -0
- package/dist/lib/bot/index.js +6 -0
- package/dist/lib/bot/protocol.d.ts +12 -0
- package/dist/lib/bot/protocol.js +74 -0
- package/dist/lib/bot/reporter.d.ts +3 -0
- package/dist/lib/bot/reporter.js +27 -0
- package/dist/lib/bot/skills.d.ts +26 -0
- package/dist/lib/bot/skills.js +69 -0
- package/dist/lib/budget/projections.d.ts +115 -0
- package/dist/lib/budget/projections.js +384 -0
- package/dist/lib/budget/scenarios.d.ts +93 -0
- package/dist/lib/budget/scenarios.js +214 -0
- package/dist/lib/cms/publish-blog.d.ts +62 -0
- package/dist/lib/cms/publish-blog.js +74 -0
- package/dist/lib/cms/strapi-client.d.ts +123 -0
- package/dist/lib/cms/strapi-client.js +213 -0
- package/dist/lib/config/registry.d.ts +17 -0
- package/dist/lib/config/registry.js +182 -0
- package/dist/lib/config/schema.d.ts +31 -0
- package/dist/lib/config/schema.js +25 -0
- package/dist/lib/config.d.ts +55 -0
- package/dist/lib/config.js +206 -0
- package/dist/lib/errors.d.ts +25 -0
- package/dist/lib/errors.js +91 -0
- package/dist/lib/format.d.ts +28 -0
- package/dist/lib/format.js +98 -0
- package/dist/lib/infra/deploy.d.ts +29 -0
- package/dist/lib/infra/deploy.js +58 -0
- package/dist/lib/infra/migrate.d.ts +34 -0
- package/dist/lib/infra/migrate.js +103 -0
- package/dist/lib/newsletter/distribute.d.ts +52 -0
- package/dist/lib/newsletter/distribute.js +193 -0
- package/{lib/newsletter/generate-insurance.ts → dist/lib/newsletter/generate-insurance.d.ts} +7 -24
- package/dist/lib/newsletter/generate-insurance.js +36 -0
- package/dist/lib/newsletter/generate.d.ts +104 -0
- package/dist/lib/newsletter/generate.js +571 -0
- package/dist/lib/returnpro/anomalies.d.ts +64 -0
- package/dist/lib/returnpro/anomalies.js +166 -0
- package/dist/lib/returnpro/audit.d.ts +32 -0
- package/dist/lib/returnpro/audit.js +147 -0
- package/dist/lib/returnpro/diagnose.d.ts +52 -0
- package/dist/lib/returnpro/diagnose.js +281 -0
- package/dist/lib/returnpro/kpis.d.ts +32 -0
- package/dist/lib/returnpro/kpis.js +192 -0
- package/dist/lib/returnpro/templates.d.ts +48 -0
- package/dist/lib/returnpro/templates.js +229 -0
- package/dist/lib/returnpro/upload-income.d.ts +25 -0
- package/dist/lib/returnpro/upload-income.js +235 -0
- package/dist/lib/returnpro/upload-netsuite.d.ts +37 -0
- package/dist/lib/returnpro/upload-netsuite.js +566 -0
- package/dist/lib/returnpro/upload-r1.d.ts +48 -0
- package/dist/lib/returnpro/upload-r1.js +398 -0
- package/dist/lib/returnpro/validate.d.ts +37 -0
- package/dist/lib/returnpro/validate.js +124 -0
- package/dist/lib/social/meta.d.ts +90 -0
- package/dist/lib/social/meta.js +160 -0
- package/dist/lib/social/post-generator.d.ts +83 -0
- package/dist/lib/social/post-generator.js +333 -0
- package/dist/lib/social/publish.d.ts +66 -0
- package/dist/lib/social/publish.js +226 -0
- package/dist/lib/social/scraper.d.ts +67 -0
- package/dist/lib/social/scraper.js +361 -0
- package/dist/lib/supabase.d.ts +4 -0
- package/dist/lib/supabase.js +20 -0
- package/dist/lib/transactions/delete-batch.d.ts +60 -0
- package/dist/lib/transactions/delete-batch.js +203 -0
- package/dist/lib/transactions/ingest.d.ts +43 -0
- package/dist/lib/transactions/ingest.js +555 -0
- package/dist/lib/transactions/stamp.d.ts +51 -0
- package/dist/lib/transactions/stamp.js +524 -0
- package/package.json +3 -4
- package/bin/optimal.ts +0 -1731
- package/lib/assets/index.ts +0 -225
- package/lib/assets.ts +0 -124
- package/lib/auth/index.ts +0 -189
- package/lib/board/index.ts +0 -309
- package/lib/board/types.ts +0 -124
- package/lib/bot/claim.ts +0 -43
- package/lib/bot/coordinator.ts +0 -254
- package/lib/bot/heartbeat.ts +0 -37
- package/lib/bot/index.ts +0 -9
- package/lib/bot/protocol.ts +0 -99
- package/lib/bot/reporter.ts +0 -42
- package/lib/bot/skills.ts +0 -81
- package/lib/budget/projections.ts +0 -561
- package/lib/budget/scenarios.ts +0 -312
- package/lib/cms/publish-blog.ts +0 -129
- package/lib/cms/strapi-client.ts +0 -302
- package/lib/config/registry.ts +0 -228
- package/lib/config/schema.ts +0 -58
- package/lib/config.ts +0 -247
- package/lib/errors.ts +0 -129
- package/lib/format.ts +0 -120
- package/lib/infra/.gitkeep +0 -0
- package/lib/infra/deploy.ts +0 -70
- package/lib/infra/migrate.ts +0 -141
- package/lib/newsletter/.gitkeep +0 -0
- package/lib/newsletter/distribute.ts +0 -256
- package/lib/newsletter/generate.ts +0 -735
- package/lib/returnpro/.gitkeep +0 -0
- package/lib/returnpro/anomalies.ts +0 -258
- package/lib/returnpro/audit.ts +0 -194
- package/lib/returnpro/diagnose.ts +0 -400
- package/lib/returnpro/kpis.ts +0 -255
- package/lib/returnpro/templates.ts +0 -323
- package/lib/returnpro/upload-income.ts +0 -311
- package/lib/returnpro/upload-netsuite.ts +0 -696
- package/lib/returnpro/upload-r1.ts +0 -563
- package/lib/returnpro/validate.ts +0 -154
- package/lib/social/meta.ts +0 -228
- package/lib/social/post-generator.ts +0 -468
- package/lib/social/publish.ts +0 -301
- package/lib/social/scraper.ts +0 -503
- package/lib/supabase.ts +0 -25
- package/lib/transactions/delete-batch.ts +0 -258
- package/lib/transactions/ingest.ts +0 -659
- package/lib/transactions/stamp.ts +0 -654
package/lib/infra/migrate.ts
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config'
|
|
2
|
-
import { execFile } from 'node:child_process'
|
|
3
|
-
import { promisify } from 'node:util'
|
|
4
|
-
import { readdir, writeFile } from 'node:fs/promises'
|
|
5
|
-
import { join } from 'node:path'
|
|
6
|
-
|
|
7
|
-
const run = promisify(execFile)
|
|
8
|
-
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
|
-
// Types
|
|
11
|
-
// ---------------------------------------------------------------------------
|
|
12
|
-
|
|
13
|
-
export interface MigrateOptions {
|
|
14
|
-
target: 'returnpro' | 'optimalos'
|
|
15
|
-
dryRun?: boolean // if true, just show what would be pushed
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface MigrateResult {
|
|
19
|
-
success: boolean
|
|
20
|
-
target: string
|
|
21
|
-
output: string
|
|
22
|
-
errors: string
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
|
-
// Constants
|
|
27
|
-
// ---------------------------------------------------------------------------
|
|
28
|
-
|
|
29
|
-
/** Hardcoded project directories — these live on Carlos's machine. */
|
|
30
|
-
const PROJECT_DIRS: Record<'returnpro' | 'optimalos', string> = {
|
|
31
|
-
returnpro: '/home/optimal/dashboard-returnpro',
|
|
32
|
-
optimalos: '/home/optimal/optimalos',
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// ---------------------------------------------------------------------------
|
|
36
|
-
// Helpers
|
|
37
|
-
// ---------------------------------------------------------------------------
|
|
38
|
-
|
|
39
|
-
function getProjectDir(target: 'returnpro' | 'optimalos'): string {
|
|
40
|
-
return PROJECT_DIRS[target]
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function migrationsDir(target: 'returnpro' | 'optimalos'): string {
|
|
44
|
-
return join(getProjectDir(target), 'supabase', 'migrations')
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Generate a timestamp string in YYYYMMDDHHMMSS format (UTC).
|
|
49
|
-
*/
|
|
50
|
-
function timestamp(): string {
|
|
51
|
-
const now = new Date()
|
|
52
|
-
const pad = (n: number, len = 2) => String(n).padStart(len, '0')
|
|
53
|
-
return [
|
|
54
|
-
now.getUTCFullYear(),
|
|
55
|
-
pad(now.getUTCMonth() + 1),
|
|
56
|
-
pad(now.getUTCDate()),
|
|
57
|
-
pad(now.getUTCHours()),
|
|
58
|
-
pad(now.getUTCMinutes()),
|
|
59
|
-
pad(now.getUTCSeconds()),
|
|
60
|
-
].join('')
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// ---------------------------------------------------------------------------
|
|
64
|
-
// Public API
|
|
65
|
-
// ---------------------------------------------------------------------------
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Run `supabase db push --linked` (or `--dry-run` if requested) against the
|
|
69
|
-
* given target project directory.
|
|
70
|
-
*
|
|
71
|
-
* Uses `execFile` (not `exec`) to avoid shell injection.
|
|
72
|
-
* The `cwd` option switches the Supabase CLI into the correct project.
|
|
73
|
-
*/
|
|
74
|
-
export async function migrateDb(opts: MigrateOptions): Promise<MigrateResult> {
|
|
75
|
-
const { target, dryRun = false } = opts
|
|
76
|
-
const projectDir = getProjectDir(target)
|
|
77
|
-
|
|
78
|
-
const args: string[] = ['db', 'push', '--linked']
|
|
79
|
-
if (dryRun) args.push('--dry-run')
|
|
80
|
-
|
|
81
|
-
try {
|
|
82
|
-
const { stdout, stderr } = await run('supabase', args, {
|
|
83
|
-
cwd: projectDir,
|
|
84
|
-
timeout: 120_000,
|
|
85
|
-
})
|
|
86
|
-
return {
|
|
87
|
-
success: true,
|
|
88
|
-
target,
|
|
89
|
-
output: stdout.trim(),
|
|
90
|
-
errors: stderr.trim(),
|
|
91
|
-
}
|
|
92
|
-
} catch (err: unknown) {
|
|
93
|
-
const e = err as { stdout?: string; stderr?: string; message?: string }
|
|
94
|
-
return {
|
|
95
|
-
success: false,
|
|
96
|
-
target,
|
|
97
|
-
output: (e.stdout ?? '').trim(),
|
|
98
|
-
errors: (e.stderr ?? e.message ?? String(err)).trim(),
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* List migration `.sql` files in the target's `supabase/migrations/` directory,
|
|
105
|
-
* sorted chronologically (by filename, which starts with a YYYYMMDDHHMMSS prefix).
|
|
106
|
-
*
|
|
107
|
-
* Returns only filenames, not full paths.
|
|
108
|
-
*/
|
|
109
|
-
export async function listPendingMigrations(
|
|
110
|
-
target: 'returnpro' | 'optimalos'
|
|
111
|
-
): Promise<string[]> {
|
|
112
|
-
const dir = migrationsDir(target)
|
|
113
|
-
const entries = await readdir(dir)
|
|
114
|
-
return entries
|
|
115
|
-
.filter((f) => f.endsWith('.sql'))
|
|
116
|
-
.sort() // lexicographic == chronological given the YYYYMMDDHHMMSS prefix
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Create a new empty migration file in the target's `supabase/migrations/`
|
|
121
|
-
* directory.
|
|
122
|
-
*
|
|
123
|
-
* The filename format is `{YYYYMMDDHHMMSS}_{name}.sql` (UTC timestamp).
|
|
124
|
-
* Returns the full absolute path of the created file.
|
|
125
|
-
*/
|
|
126
|
-
export async function createMigration(
|
|
127
|
-
target: 'returnpro' | 'optimalos',
|
|
128
|
-
name: string
|
|
129
|
-
): Promise<string> {
|
|
130
|
-
const sanitized = name.replace(/\s+/g, '_').replace(/[^a-zA-Z0-9_]/g, '')
|
|
131
|
-
const filename = `${timestamp()}_${sanitized}.sql`
|
|
132
|
-
const fullPath = join(migrationsDir(target), filename)
|
|
133
|
-
|
|
134
|
-
await writeFile(
|
|
135
|
-
fullPath,
|
|
136
|
-
`-- Migration: ${sanitized}\n-- Target: ${target}\n-- Created: ${new Date().toISOString()}\n\n`,
|
|
137
|
-
{ encoding: 'utf8' }
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
return fullPath
|
|
141
|
-
}
|
package/lib/newsletter/.gitkeep
DELETED
|
File without changes
|
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Newsletter Distribution Module
|
|
3
|
-
*
|
|
4
|
-
* Triggers newsletter distribution via the n8n webhook and updates
|
|
5
|
-
* Strapi delivery tracking fields accordingly.
|
|
6
|
-
*
|
|
7
|
-
* Functions:
|
|
8
|
-
* distributeNewsletter(documentId, opts?) — orchestrate distribution
|
|
9
|
-
* checkDistributionStatus(documentId) — read current delivery status
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import 'dotenv/config'
|
|
13
|
-
import { strapiGet, strapiPut } from '../cms/strapi-client.js'
|
|
14
|
-
|
|
15
|
-
// ── Types ─────────────────────────────────────────────────────────────
|
|
16
|
-
|
|
17
|
-
export interface DistributionResult {
|
|
18
|
-
success: boolean
|
|
19
|
-
documentId: string
|
|
20
|
-
channel: string
|
|
21
|
-
webhookResponse?: unknown
|
|
22
|
-
error?: string
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface DeliveryStatus {
|
|
26
|
-
documentId: string
|
|
27
|
-
delivery_status: string
|
|
28
|
-
delivered_at: string | null
|
|
29
|
-
recipients_count: number | null
|
|
30
|
-
ghl_campaign_id: string | null
|
|
31
|
-
delivery_errors: unknown[] | null
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Shape of the Strapi newsletter item's data fields we care about
|
|
35
|
-
interface NewsletterData {
|
|
36
|
-
documentId: string
|
|
37
|
-
publishedAt?: string | null
|
|
38
|
-
delivery_status?: string | null
|
|
39
|
-
delivered_at?: string | null
|
|
40
|
-
recipients_count?: number | null
|
|
41
|
-
ghl_campaign_id?: string | null
|
|
42
|
-
delivery_errors?: unknown[] | null
|
|
43
|
-
brand?: string
|
|
44
|
-
[key: string]: unknown
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// ── Environment helpers ───────────────────────────────────────────────
|
|
48
|
-
|
|
49
|
-
function getWebhookUrl(): string {
|
|
50
|
-
const url = process.env.N8N_WEBHOOK_URL
|
|
51
|
-
if (!url) {
|
|
52
|
-
throw new Error(
|
|
53
|
-
'Missing env var: N8N_WEBHOOK_URL\n' +
|
|
54
|
-
'Set it to your n8n base URL, e.g. https://n8n.op-hub.com\n' +
|
|
55
|
-
'The distribute module will POST to: $N8N_WEBHOOK_URL/webhook/newsletter-distribute',
|
|
56
|
-
)
|
|
57
|
-
}
|
|
58
|
-
return url.replace(/\/+$/, '')
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// ── 1. Distribute Newsletter ──────────────────────────────────────────
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Main orchestrator: fetch newsletter from Strapi, validate state,
|
|
65
|
-
* update delivery_status to 'sending', trigger the n8n webhook,
|
|
66
|
-
* and update tracking fields based on the result.
|
|
67
|
-
*
|
|
68
|
-
* @param documentId - Strapi documentId (UUID string) of the newsletter
|
|
69
|
-
* @param opts.channel - Distribution channel. Defaults to 'all'.
|
|
70
|
-
*
|
|
71
|
-
* @example
|
|
72
|
-
* const result = await distributeNewsletter('abc123-def456')
|
|
73
|
-
* const emailOnly = await distributeNewsletter('abc123-def456', { channel: 'email' })
|
|
74
|
-
*/
|
|
75
|
-
export async function distributeNewsletter(
|
|
76
|
-
documentId: string,
|
|
77
|
-
opts: { channel?: 'email' | 'all' } = {},
|
|
78
|
-
): Promise<DistributionResult> {
|
|
79
|
-
const channel = opts.channel ?? 'all'
|
|
80
|
-
|
|
81
|
-
// Step 1: Fetch the newsletter from Strapi
|
|
82
|
-
let newsletter: NewsletterData
|
|
83
|
-
try {
|
|
84
|
-
const response = await strapiGet<{ data: NewsletterData }>(
|
|
85
|
-
`/api/newsletters/${documentId}`,
|
|
86
|
-
)
|
|
87
|
-
newsletter = response.data
|
|
88
|
-
} catch (err) {
|
|
89
|
-
const msg = err instanceof Error ? err.message : String(err)
|
|
90
|
-
return {
|
|
91
|
-
success: false,
|
|
92
|
-
documentId,
|
|
93
|
-
channel,
|
|
94
|
-
error: `Failed to fetch newsletter from Strapi: ${msg}`,
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Step 2: Validate published state
|
|
99
|
-
if (!newsletter.publishedAt) {
|
|
100
|
-
return {
|
|
101
|
-
success: false,
|
|
102
|
-
documentId,
|
|
103
|
-
channel,
|
|
104
|
-
error:
|
|
105
|
-
'Newsletter is not published. Publish it in Strapi before distributing.',
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Step 3: Validate delivery_status — only distribute if pending or unset
|
|
110
|
-
const currentStatus = newsletter.delivery_status ?? 'pending'
|
|
111
|
-
if (currentStatus !== 'pending' && currentStatus !== undefined) {
|
|
112
|
-
if (
|
|
113
|
-
currentStatus === 'sending' ||
|
|
114
|
-
currentStatus === 'delivered' ||
|
|
115
|
-
currentStatus === 'partial'
|
|
116
|
-
) {
|
|
117
|
-
return {
|
|
118
|
-
success: false,
|
|
119
|
-
documentId,
|
|
120
|
-
channel,
|
|
121
|
-
error: `Newsletter already has delivery_status="${currentStatus}". Cannot re-distribute.`,
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Step 4: Mark as 'sending' in Strapi
|
|
127
|
-
try {
|
|
128
|
-
await strapiPut('/api/newsletters', documentId, {
|
|
129
|
-
delivery_status: 'sending',
|
|
130
|
-
})
|
|
131
|
-
} catch (err) {
|
|
132
|
-
const msg = err instanceof Error ? err.message : String(err)
|
|
133
|
-
return {
|
|
134
|
-
success: false,
|
|
135
|
-
documentId,
|
|
136
|
-
channel,
|
|
137
|
-
error: `Failed to update delivery_status to 'sending': ${msg}`,
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Step 5: Trigger n8n webhook
|
|
142
|
-
const webhookUrl = getWebhookUrl()
|
|
143
|
-
const webhookEndpoint = `${webhookUrl}/webhook/newsletter-distribute`
|
|
144
|
-
const brand = typeof newsletter.brand === 'string' ? newsletter.brand : ''
|
|
145
|
-
|
|
146
|
-
let webhookResponse: unknown
|
|
147
|
-
try {
|
|
148
|
-
const res = await fetch(webhookEndpoint, {
|
|
149
|
-
method: 'POST',
|
|
150
|
-
headers: { 'Content-Type': 'application/json' },
|
|
151
|
-
body: JSON.stringify({ documentId, brand, channel }),
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
if (!res.ok) {
|
|
155
|
-
let body: unknown
|
|
156
|
-
try {
|
|
157
|
-
body = await res.json()
|
|
158
|
-
} catch {
|
|
159
|
-
body = await res.text().catch(() => res.statusText)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Webhook failed — update Strapi to 'failed' with error details
|
|
163
|
-
const errorDetails = [
|
|
164
|
-
{ step: 'webhook', status: res.status, body },
|
|
165
|
-
]
|
|
166
|
-
await strapiPut('/api/newsletters', documentId, {
|
|
167
|
-
delivery_status: 'failed',
|
|
168
|
-
delivery_errors: errorDetails,
|
|
169
|
-
}).catch(() => {
|
|
170
|
-
// Best-effort update — don't mask the original error
|
|
171
|
-
})
|
|
172
|
-
|
|
173
|
-
return {
|
|
174
|
-
success: false,
|
|
175
|
-
documentId,
|
|
176
|
-
channel,
|
|
177
|
-
error: `n8n webhook returned ${res.status}: ${JSON.stringify(body)}`,
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
try {
|
|
182
|
-
webhookResponse = await res.json()
|
|
183
|
-
} catch {
|
|
184
|
-
webhookResponse = { status: res.status }
|
|
185
|
-
}
|
|
186
|
-
} catch (err) {
|
|
187
|
-
// Network/connection error
|
|
188
|
-
const msg = err instanceof Error ? err.message : String(err)
|
|
189
|
-
const errorDetails = [{ step: 'webhook', error: msg }]
|
|
190
|
-
|
|
191
|
-
await strapiPut('/api/newsletters', documentId, {
|
|
192
|
-
delivery_status: 'failed',
|
|
193
|
-
delivery_errors: errorDetails,
|
|
194
|
-
}).catch(() => {
|
|
195
|
-
// Best-effort
|
|
196
|
-
})
|
|
197
|
-
|
|
198
|
-
return {
|
|
199
|
-
success: false,
|
|
200
|
-
documentId,
|
|
201
|
-
channel,
|
|
202
|
-
error: `n8n webhook request failed: ${msg}`,
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Step 6: Webhook succeeded — return success
|
|
207
|
-
// Note: n8n will update delivery_status to 'delivered' (or 'partial') via its
|
|
208
|
-
// own Strapi PUT once it finishes sending. We don't update it here.
|
|
209
|
-
return {
|
|
210
|
-
success: true,
|
|
211
|
-
documentId,
|
|
212
|
-
channel,
|
|
213
|
-
webhookResponse,
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// ── 2. Check Distribution Status ─────────────────────────────────────
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Fetch the current delivery tracking fields for a newsletter from Strapi.
|
|
221
|
-
*
|
|
222
|
-
* @param documentId - Strapi documentId (UUID string) of the newsletter
|
|
223
|
-
*
|
|
224
|
-
* @example
|
|
225
|
-
* const status = await checkDistributionStatus('abc123-def456')
|
|
226
|
-
* console.log(status.delivery_status) // 'delivered'
|
|
227
|
-
* console.log(status.recipients_count) // 847
|
|
228
|
-
*/
|
|
229
|
-
export async function checkDistributionStatus(
|
|
230
|
-
documentId: string,
|
|
231
|
-
): Promise<DeliveryStatus> {
|
|
232
|
-
const response = await strapiGet<{ data: NewsletterData }>(
|
|
233
|
-
`/api/newsletters/${documentId}`,
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
const data = response.data
|
|
237
|
-
|
|
238
|
-
return {
|
|
239
|
-
documentId,
|
|
240
|
-
delivery_status: typeof data.delivery_status === 'string'
|
|
241
|
-
? data.delivery_status
|
|
242
|
-
: 'pending',
|
|
243
|
-
delivered_at: typeof data.delivered_at === 'string'
|
|
244
|
-
? data.delivered_at
|
|
245
|
-
: null,
|
|
246
|
-
recipients_count: typeof data.recipients_count === 'number'
|
|
247
|
-
? data.recipients_count
|
|
248
|
-
: null,
|
|
249
|
-
ghl_campaign_id: typeof data.ghl_campaign_id === 'string'
|
|
250
|
-
? data.ghl_campaign_id
|
|
251
|
-
: null,
|
|
252
|
-
delivery_errors: Array.isArray(data.delivery_errors)
|
|
253
|
-
? data.delivery_errors
|
|
254
|
-
: null,
|
|
255
|
-
}
|
|
256
|
-
}
|