@bernierllc/content-management-suite 0.4.2 → 0.7.0
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/index.d.mts +9 -1291
- package/dist/index.d.ts +9 -1291
- package/dist/index.js +1144 -831
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1134 -818
- package/dist/index.mjs.map +1 -1
- package/dist/prisma.d.mts +26 -0
- package/dist/prisma.d.ts +26 -0
- package/dist/prisma.js +35 -0
- package/dist/prisma.js.map +1 -0
- package/dist/prisma.mjs +10 -0
- package/dist/prisma.mjs.map +1 -0
- package/dist/types-DQpwJ5e3.d.ts +1234 -0
- package/dist/ui.d.mts +1 -0
- package/dist/ui.d.ts +1 -0
- package/dist/ui.js +37 -0
- package/dist/ui.js.map +1 -0
- package/dist/ui.mjs +14 -0
- package/dist/ui.mjs.map +1 -0
- package/dist/utils.d.mts +9 -0
- package/dist/utils.d.ts +9 -0
- package/dist/utils.js +117 -0
- package/dist/utils.js.map +1 -0
- package/dist/utils.mjs +88 -0
- package/dist/utils.mjs.map +1 -0
- package/package.json +40 -22
- package/dist/server.d.mts +0 -1
- package/dist/server.d.ts +0 -1
- package/dist/server.js +0 -1102
- package/dist/server.js.map +0 -1
- package/dist/server.mjs +0 -1089
- package/dist/server.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,45 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
// src/content-management-suite.ts
|
|
2
|
+
import { EventEmitter } from "events";
|
|
2
3
|
|
|
3
|
-
// src/
|
|
4
|
+
// src/config.ts
|
|
4
5
|
import { z } from "zod";
|
|
5
6
|
var ContentManagementConfigSchema = z.object({
|
|
6
|
-
// Server Configuration
|
|
7
|
-
server: z.object({
|
|
8
|
-
port: z.number().default(3e3),
|
|
9
|
-
host: z.string().default("localhost"),
|
|
10
|
-
cors: z.object({
|
|
11
|
-
origin: z.union([z.string(), z.array(z.string())]).default("*"),
|
|
12
|
-
credentials: z.boolean().default(true)
|
|
13
|
-
}).default({}),
|
|
14
|
-
security: z.object({
|
|
15
|
-
helmet: z.boolean().default(true),
|
|
16
|
-
rateLimit: z.object({
|
|
17
|
-
enabled: z.boolean().default(true),
|
|
18
|
-
windowMs: z.number().default(15 * 60 * 1e3),
|
|
19
|
-
// 15 minutes
|
|
20
|
-
max: z.number().default(100)
|
|
21
|
-
// limit each IP to 100 requests per windowMs
|
|
22
|
-
}).default({})
|
|
23
|
-
}).default({})
|
|
24
|
-
}).default({}),
|
|
25
|
-
// Database Configuration
|
|
26
|
-
database: z.object({
|
|
27
|
-
type: z.enum(["sqlite", "postgresql", "mysql", "mongodb"]).default("sqlite"),
|
|
28
|
-
url: z.string().optional(),
|
|
29
|
-
host: z.string().default("localhost"),
|
|
30
|
-
port: z.number().optional(),
|
|
31
|
-
name: z.string().default("content_management"),
|
|
32
|
-
username: z.string().optional(),
|
|
33
|
-
password: z.string().optional(),
|
|
34
|
-
ssl: z.boolean().default(false),
|
|
35
|
-
pool: z.object({
|
|
36
|
-
min: z.number().default(2),
|
|
37
|
-
max: z.number().default(10)
|
|
38
|
-
}).default({})
|
|
39
|
-
}).default({}),
|
|
40
|
-
// Content Configuration
|
|
41
7
|
content: z.object({
|
|
42
|
-
// Default editorial workflow
|
|
43
8
|
defaultWorkflow: z.object({
|
|
44
9
|
id: z.string().default("standard"),
|
|
45
10
|
name: z.string().default("Standard Workflow"),
|
|
@@ -53,24 +18,8 @@ var ContentManagementConfigSchema = z.object({
|
|
|
53
18
|
description: z.string().optional(),
|
|
54
19
|
permissions: z.array(z.string()).default([])
|
|
55
20
|
})).default([
|
|
56
|
-
{
|
|
57
|
-
|
|
58
|
-
name: "Write",
|
|
59
|
-
order: 1,
|
|
60
|
-
isPublishStage: false,
|
|
61
|
-
allowsScheduling: false,
|
|
62
|
-
description: "Write content",
|
|
63
|
-
permissions: ["content.edit"]
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
id: "publish",
|
|
67
|
-
name: "Publish",
|
|
68
|
-
order: 2,
|
|
69
|
-
isPublishStage: true,
|
|
70
|
-
allowsScheduling: true,
|
|
71
|
-
description: "Publish content",
|
|
72
|
-
permissions: ["content.publish"]
|
|
73
|
-
}
|
|
21
|
+
{ id: "write", name: "Write", order: 1, isPublishStage: false, allowsScheduling: false, description: "Write content", permissions: ["content.edit"] },
|
|
22
|
+
{ id: "publish", name: "Publish", order: 2, isPublishStage: true, allowsScheduling: true, description: "Publish content", permissions: ["content.publish"] }
|
|
74
23
|
]),
|
|
75
24
|
transitions: z.array(z.object({
|
|
76
25
|
id: z.string(),
|
|
@@ -79,34 +28,23 @@ var ContentManagementConfigSchema = z.object({
|
|
|
79
28
|
description: z.string().optional(),
|
|
80
29
|
permissions: z.array(z.string()).default([])
|
|
81
30
|
})).default([
|
|
82
|
-
{
|
|
83
|
-
id: "write-to-publish",
|
|
84
|
-
from: "write",
|
|
85
|
-
to: "publish",
|
|
86
|
-
description: "Move from write to publish",
|
|
87
|
-
permissions: ["content.publish"]
|
|
88
|
-
}
|
|
31
|
+
{ id: "write-to-publish", from: "write", to: "publish", description: "Move from write to publish", permissions: ["content.publish"] }
|
|
89
32
|
])
|
|
90
33
|
}).default({}),
|
|
91
|
-
// Content types
|
|
92
34
|
contentTypes: z.array(z.string()).default(["text", "image", "audio", "video"]),
|
|
93
|
-
// Auto-save configuration
|
|
94
35
|
autoSave: z.object({
|
|
95
36
|
enabled: z.boolean().default(true),
|
|
96
37
|
debounceMs: z.number().default(1e3),
|
|
97
38
|
maxRetries: z.number().default(3),
|
|
98
39
|
backoffMs: z.number().default(1e3)
|
|
99
40
|
}).default({}),
|
|
100
|
-
// Soft delete configuration
|
|
101
41
|
softDelete: z.object({
|
|
102
42
|
enabled: z.boolean().default(true),
|
|
103
43
|
showDeletedToUsers: z.boolean().default(false),
|
|
104
44
|
retentionDays: z.number().default(30)
|
|
105
45
|
}).default({}),
|
|
106
|
-
// File upload configuration
|
|
107
46
|
upload: z.object({
|
|
108
47
|
maxFileSize: z.number().default(104857600),
|
|
109
|
-
// 100MB
|
|
110
48
|
allowedTypes: z.array(z.string()).default([
|
|
111
49
|
"image/jpeg",
|
|
112
50
|
"image/png",
|
|
@@ -120,902 +58,1280 @@ var ContentManagementConfigSchema = z.object({
|
|
|
120
58
|
"video/ogg"
|
|
121
59
|
]),
|
|
122
60
|
storage: z.object({
|
|
123
|
-
type: z.enum(["local", "s3"
|
|
61
|
+
type: z.enum(["local", "s3"]).default("local"),
|
|
124
62
|
path: z.string().default("./uploads"),
|
|
125
63
|
bucket: z.string().optional(),
|
|
126
|
-
region: z.string().optional()
|
|
127
|
-
|
|
128
|
-
secretKey: z.string().optional()
|
|
129
|
-
}).default({})
|
|
64
|
+
region: z.string().optional()
|
|
65
|
+
}).default({}).optional()
|
|
130
66
|
}).default({})
|
|
131
67
|
}).default({}),
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
autoSave: z.boolean().default(true),
|
|
146
|
-
placeholder: z.string().default("Start writing your content...")
|
|
147
|
-
}).default({}),
|
|
148
|
-
list: z.object({
|
|
149
|
-
defaultView: z.enum(["table", "list", "grid", "kanban"]).default("table"),
|
|
150
|
-
pageSize: z.number().default(20),
|
|
151
|
-
showSearch: z.boolean().default(true),
|
|
152
|
-
showFilters: z.boolean().default(true),
|
|
153
|
-
showSorting: z.boolean().default(true),
|
|
154
|
-
showPagination: z.boolean().default(true)
|
|
68
|
+
database: z.object({
|
|
69
|
+
type: z.enum(["sqlite", "postgresql", "mysql", "mongodb"]).default("sqlite"),
|
|
70
|
+
url: z.string().optional(),
|
|
71
|
+
host: z.string().default("localhost"),
|
|
72
|
+
port: z.number().optional(),
|
|
73
|
+
name: z.string().default("content_management"),
|
|
74
|
+
username: z.string().optional(),
|
|
75
|
+
password: z.string().optional(),
|
|
76
|
+
ssl: z.boolean().default(false),
|
|
77
|
+
pool: z.object({
|
|
78
|
+
min: z.number().default(2),
|
|
79
|
+
max: z.number().default(10),
|
|
80
|
+
idleTimeoutMs: z.number().optional()
|
|
155
81
|
}).default({})
|
|
156
82
|
}).default({}),
|
|
157
|
-
// Integration Configuration
|
|
158
83
|
integrations: z.object({
|
|
159
84
|
neverAdmin: z.object({
|
|
160
85
|
enabled: z.boolean().default(false),
|
|
161
|
-
|
|
162
|
-
apiKey: z.string().optional(),
|
|
163
|
-
syncInterval: z.number().default(3e5)
|
|
164
|
-
// 5 minutes
|
|
86
|
+
endpoint: z.string().optional()
|
|
165
87
|
}).default({}),
|
|
166
88
|
neverHub: z.object({
|
|
167
89
|
enabled: z.boolean().default(false),
|
|
168
|
-
|
|
169
|
-
apiKey: z.string().optional(),
|
|
170
|
-
packageDiscovery: z.boolean().default(true)
|
|
90
|
+
endpoint: z.string().optional()
|
|
171
91
|
}).default({}),
|
|
172
92
|
analytics: z.object({
|
|
173
93
|
enabled: z.boolean().default(false),
|
|
174
|
-
provider: z.
|
|
175
|
-
trackingId: z.string().optional(),
|
|
176
|
-
config: z.record(z.any()).default({})
|
|
94
|
+
provider: z.string().optional()
|
|
177
95
|
}).default({})
|
|
178
96
|
}).default({}),
|
|
179
|
-
// Security Configuration
|
|
180
97
|
security: z.object({
|
|
181
|
-
jwt: z.object({
|
|
182
|
-
secret: z.string().default("your-secret-key"),
|
|
183
|
-
expiresIn: z.string().default("24h"),
|
|
184
|
-
issuer: z.string().default("content-management-suite")
|
|
185
|
-
}).default({}),
|
|
186
98
|
permissions: z.object({
|
|
187
99
|
enabled: z.boolean().default(true),
|
|
188
|
-
defaultRole: z.string().default("user")
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
},
|
|
204
|
-
{
|
|
205
|
-
name: "author",
|
|
206
|
-
permissions: ["content.edit"],
|
|
207
|
-
description: "Content creation and editing"
|
|
208
|
-
},
|
|
209
|
-
{
|
|
210
|
-
name: "user",
|
|
211
|
-
permissions: ["content.view"],
|
|
212
|
-
description: "Content viewing only"
|
|
213
|
-
}
|
|
214
|
-
])
|
|
215
|
-
}).default({})
|
|
100
|
+
defaultRole: z.string().default("user")
|
|
101
|
+
}).default({}),
|
|
102
|
+
roles: z.array(z.object({
|
|
103
|
+
name: z.string(),
|
|
104
|
+
permissions: z.array(z.string()),
|
|
105
|
+
description: z.string().optional()
|
|
106
|
+
})).default([
|
|
107
|
+
{ name: "admin", permissions: ["*"], description: "Full administrative access" },
|
|
108
|
+
{ name: "editor", permissions: ["content.edit", "content.publish", "content.schedule"], description: "Content editing and publishing" },
|
|
109
|
+
{ name: "author", permissions: ["content.edit"], description: "Content creation and editing" },
|
|
110
|
+
{ name: "user", permissions: ["content.view"], description: "Content viewing only" }
|
|
111
|
+
])
|
|
112
|
+
}).default({}),
|
|
113
|
+
workflows: z.object({
|
|
114
|
+
defaults: z.record(z.string(), z.string()).default({})
|
|
216
115
|
}).default({}),
|
|
217
|
-
// Logging Configuration
|
|
218
116
|
logging: z.object({
|
|
219
|
-
level: z.enum(["error", "warn", "info", "debug"]).default("info")
|
|
220
|
-
format: z.enum(["json", "text"]).default("json"),
|
|
221
|
-
file: z.object({
|
|
222
|
-
enabled: z.boolean().default(true),
|
|
223
|
-
path: z.string().default("./logs"),
|
|
224
|
-
maxSize: z.string().default("10MB"),
|
|
225
|
-
maxFiles: z.number().default(5)
|
|
226
|
-
}).default({}),
|
|
227
|
-
console: z.object({
|
|
228
|
-
enabled: z.boolean().default(true),
|
|
229
|
-
colorize: z.boolean().default(true)
|
|
230
|
-
}).default({})
|
|
117
|
+
level: z.enum(["error", "warn", "info", "debug"]).default("info")
|
|
231
118
|
}).default({}),
|
|
232
|
-
// Performance Configuration
|
|
233
119
|
performance: z.object({
|
|
234
120
|
cache: z.object({
|
|
235
121
|
enabled: z.boolean().default(true),
|
|
236
122
|
ttl: z.number().default(300),
|
|
237
|
-
// 5 minutes
|
|
238
123
|
maxSize: z.number().default(1e3)
|
|
239
|
-
}).default({}),
|
|
240
|
-
compression: z.object({
|
|
241
|
-
enabled: z.boolean().default(true),
|
|
242
|
-
level: z.number().min(1).max(9).default(6)
|
|
243
|
-
}).default({}),
|
|
244
|
-
rateLimit: z.object({
|
|
245
|
-
enabled: z.boolean().default(true),
|
|
246
|
-
windowMs: z.number().default(15 * 60 * 1e3),
|
|
247
|
-
// 15 minutes
|
|
248
|
-
max: z.number().default(100)
|
|
249
124
|
}).default({})
|
|
250
125
|
}).default({})
|
|
251
126
|
});
|
|
252
|
-
|
|
253
|
-
|
|
127
|
+
|
|
128
|
+
// src/errors.ts
|
|
129
|
+
var ContentManagementError = class extends Error {
|
|
130
|
+
constructor(message, options) {
|
|
254
131
|
super(message);
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
this.
|
|
132
|
+
if (options?.cause !== void 0) {
|
|
133
|
+
this.cause = options.cause;
|
|
134
|
+
}
|
|
135
|
+
this.name = this.constructor.name;
|
|
136
|
+
this.code = options?.code ?? "CONTENT_MANAGEMENT_ERROR";
|
|
137
|
+
if (options?.details !== void 0) {
|
|
138
|
+
this.details = options.details;
|
|
139
|
+
}
|
|
259
140
|
}
|
|
260
141
|
};
|
|
261
|
-
var
|
|
262
|
-
constructor(message,
|
|
263
|
-
super(message, "VALIDATION_ERROR"
|
|
264
|
-
this.name = "ContentManagementSuiteValidationError";
|
|
142
|
+
var ValidationError = class extends ContentManagementError {
|
|
143
|
+
constructor(message, options) {
|
|
144
|
+
super(message, { ...options, code: "VALIDATION_ERROR" });
|
|
265
145
|
}
|
|
266
146
|
};
|
|
267
|
-
var
|
|
268
|
-
constructor(message,
|
|
269
|
-
super(message, "NOT_FOUND"
|
|
270
|
-
this.name = "ContentManagementSuiteNotFoundError";
|
|
147
|
+
var NotFoundError = class extends ContentManagementError {
|
|
148
|
+
constructor(message, options) {
|
|
149
|
+
super(message, { ...options, code: "NOT_FOUND" });
|
|
271
150
|
}
|
|
272
151
|
};
|
|
273
|
-
var
|
|
274
|
-
constructor(message,
|
|
275
|
-
super(message, "UNAUTHORIZED"
|
|
276
|
-
this.name = "ContentManagementSuiteUnauthorizedError";
|
|
152
|
+
var UnauthorizedError = class extends ContentManagementError {
|
|
153
|
+
constructor(message, options) {
|
|
154
|
+
super(message, { ...options, code: "UNAUTHORIZED" });
|
|
277
155
|
}
|
|
278
156
|
};
|
|
279
|
-
var
|
|
280
|
-
constructor(message,
|
|
281
|
-
super(message, "FORBIDDEN"
|
|
282
|
-
this.name = "ContentManagementSuiteForbiddenError";
|
|
157
|
+
var ForbiddenError = class extends ContentManagementError {
|
|
158
|
+
constructor(message, options) {
|
|
159
|
+
super(message, { ...options, code: "FORBIDDEN" });
|
|
283
160
|
}
|
|
284
161
|
};
|
|
285
|
-
var
|
|
286
|
-
constructor(message,
|
|
287
|
-
super(message, "CONFLICT"
|
|
288
|
-
this.name = "ContentManagementSuiteConflictError";
|
|
162
|
+
var ConflictError = class extends ContentManagementError {
|
|
163
|
+
constructor(message, options) {
|
|
164
|
+
super(message, { ...options, code: "CONFLICT" });
|
|
289
165
|
}
|
|
290
166
|
};
|
|
291
|
-
var
|
|
292
|
-
constructor(message,
|
|
293
|
-
super(message, "INTERNAL_ERROR"
|
|
294
|
-
this.name = "ContentManagementSuiteInternalError";
|
|
167
|
+
var InternalError = class extends ContentManagementError {
|
|
168
|
+
constructor(message, options) {
|
|
169
|
+
super(message, { ...options, code: "INTERNAL_ERROR" });
|
|
295
170
|
}
|
|
296
171
|
};
|
|
297
172
|
|
|
298
|
-
// src/
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
WorkflowTimeline as WorkflowTimeline2,
|
|
303
|
-
WorkflowAdminConfig as WorkflowAdminConfig2
|
|
304
|
-
} from "@bernierllc/content-workflow-ui";
|
|
305
|
-
import { TextContentType as TextContentType2 } from "@bernierllc/content-type-text";
|
|
306
|
-
import { ImageContentType as ImageContentType2 } from "@bernierllc/content-type-image";
|
|
307
|
-
import { AudioContentTypeManager as AudioContentTypeManager2 } from "@bernierllc/content-type-audio";
|
|
308
|
-
import { VideoContentType as VideoContentType2 } from "@bernierllc/content-type-video";
|
|
309
|
-
import { ContentTypeRegistry as ContentTypeRegistry2 } from "@bernierllc/content-type-registry";
|
|
310
|
-
import { EditorialWorkflowEngine, WorkflowBuilder, WorkflowTemplates, WorkflowFactory } from "@bernierllc/content-editorial-workflow";
|
|
311
|
-
import { AutosaveManager } from "@bernierllc/content-autosave-manager";
|
|
312
|
-
import { ContentSoftDelete } from "@bernierllc/content-soft-delete";
|
|
313
|
-
|
|
314
|
-
// src/content-management-suite.ts
|
|
315
|
-
import { EventEmitter } from "events";
|
|
316
|
-
import express from "express";
|
|
317
|
-
import cors from "cors";
|
|
318
|
-
import helmet from "helmet";
|
|
319
|
-
import morgan from "morgan";
|
|
320
|
-
import compression from "compression";
|
|
321
|
-
import { v4 as uuidv4 } from "uuid";
|
|
322
|
-
import { ContentTypeRegistry } from "@bernierllc/content-type-registry";
|
|
323
|
-
import { TextContentType } from "@bernierllc/content-type-text";
|
|
324
|
-
import { ImageContentType } from "@bernierllc/content-type-image";
|
|
325
|
-
import { AudioContentTypeManager } from "@bernierllc/content-type-audio";
|
|
326
|
-
import { VideoContentType } from "@bernierllc/content-type-video";
|
|
327
|
-
import {
|
|
328
|
-
WorkflowStepper,
|
|
329
|
-
StageActionButtons,
|
|
330
|
-
WorkflowTimeline,
|
|
331
|
-
WorkflowAdminConfig
|
|
332
|
-
} from "@bernierllc/content-workflow-ui";
|
|
333
|
-
var ContentManagementSuiteImpl = class extends EventEmitter {
|
|
334
|
-
constructor(options = {}) {
|
|
335
|
-
super();
|
|
336
|
-
// Stub until @bernierllc/content-editor-service is published
|
|
337
|
-
this.autosaveManager = null;
|
|
338
|
-
this.softDelete = null;
|
|
339
|
-
this.workflowEngine = null;
|
|
340
|
-
// Content Types
|
|
341
|
-
this.contentTypes = /* @__PURE__ */ new Map();
|
|
342
|
-
// Plugins and Middleware
|
|
343
|
-
this.plugins = /* @__PURE__ */ new Map();
|
|
344
|
-
this.middleware = [];
|
|
345
|
-
this.hooks = /* @__PURE__ */ new Map();
|
|
346
|
-
// State
|
|
347
|
-
this.isStarted = false;
|
|
348
|
-
this.isStopped = false;
|
|
349
|
-
this.httpServer = null;
|
|
350
|
-
this.options = options;
|
|
351
|
-
this.config = this.loadConfiguration();
|
|
352
|
-
this.contentTypeRegistry = new ContentTypeRegistry();
|
|
353
|
-
this.configManager = this.createConfigManagerStub();
|
|
354
|
-
this.workflowService = this.createWorkflowServiceStub();
|
|
355
|
-
this.editorService = this.createEditorServiceStub();
|
|
356
|
-
this.workflow = {
|
|
357
|
-
stepper: WorkflowStepper,
|
|
358
|
-
actions: StageActionButtons,
|
|
359
|
-
timeline: WorkflowTimeline,
|
|
360
|
-
admin: WorkflowAdminConfig
|
|
361
|
-
};
|
|
362
|
-
this.server = express();
|
|
363
|
-
this.setupMiddleware();
|
|
364
|
-
this.setupRoutes();
|
|
365
|
-
this.editor = this.createEditorStub();
|
|
366
|
-
this.list = this.createListStub();
|
|
367
|
-
this.registerDefaultContentTypes();
|
|
368
|
-
if (options.plugins) {
|
|
369
|
-
options.plugins.forEach((plugin) => this.registerPlugin(plugin));
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
// Stub creators for services not yet published
|
|
373
|
-
createConfigManagerStub() {
|
|
374
|
-
return {
|
|
375
|
-
getConfig: () => this.config,
|
|
376
|
-
updateConfig: async (config) => {
|
|
377
|
-
this.config = { ...this.config, ...config };
|
|
378
|
-
},
|
|
379
|
-
start: async () => {
|
|
380
|
-
},
|
|
381
|
-
stop: async () => {
|
|
382
|
-
}
|
|
383
|
-
};
|
|
173
|
+
// src/in-memory-adapter.ts
|
|
174
|
+
var InMemoryContentStorage = class {
|
|
175
|
+
constructor() {
|
|
176
|
+
this.store = /* @__PURE__ */ new Map();
|
|
384
177
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
178
|
+
async list(filters) {
|
|
179
|
+
let items = Array.from(this.store.values());
|
|
180
|
+
if (filters?.type) {
|
|
181
|
+
items = items.filter((item) => item.type === filters.type);
|
|
182
|
+
}
|
|
183
|
+
if (filters?.status) {
|
|
184
|
+
items = items.filter((item) => item.status === filters.status);
|
|
185
|
+
}
|
|
186
|
+
const total = items.length;
|
|
187
|
+
const page = filters?.page ?? 1;
|
|
188
|
+
const limit = filters?.limit ?? items.length;
|
|
189
|
+
const start = (page - 1) * limit;
|
|
190
|
+
const paged = items.slice(start, start + limit);
|
|
191
|
+
return { items: paged, total, page, limit };
|
|
192
|
+
}
|
|
193
|
+
async get(id) {
|
|
194
|
+
return this.store.get(id) ?? null;
|
|
195
|
+
}
|
|
196
|
+
async create(item) {
|
|
197
|
+
this.store.set(item.id, item);
|
|
198
|
+
return item;
|
|
199
|
+
}
|
|
200
|
+
async update(id, partial) {
|
|
201
|
+
const existing = this.store.get(id);
|
|
202
|
+
if (!existing) {
|
|
203
|
+
throw new Error(`Content item not found: ${id}`);
|
|
204
|
+
}
|
|
205
|
+
const updated = { ...existing, ...partial };
|
|
206
|
+
this.store.set(id, updated);
|
|
207
|
+
return updated;
|
|
208
|
+
}
|
|
209
|
+
async delete(id) {
|
|
210
|
+
this.store.delete(id);
|
|
211
|
+
}
|
|
212
|
+
async findBySource(sourceType, sourceId) {
|
|
213
|
+
for (const item of this.store.values()) {
|
|
214
|
+
if (item.sourceType === sourceType && item.sourceId === sourceId) {
|
|
215
|
+
return item;
|
|
399
216
|
}
|
|
400
|
-
}
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
401
219
|
}
|
|
402
|
-
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
220
|
+
async search(query, options) {
|
|
221
|
+
const lowerQuery = query.toLowerCase();
|
|
222
|
+
let items = Array.from(this.store.values()).filter((item) => {
|
|
223
|
+
const titleMatch = item.title && item.title.toLowerCase().includes(lowerQuery);
|
|
224
|
+
const bodyMatch = item.body && item.body.toLowerCase().includes(lowerQuery);
|
|
225
|
+
const dataMatch = item.data && Object.values(item.data).some(
|
|
226
|
+
(value) => typeof value === "string" && value.toLowerCase().includes(lowerQuery)
|
|
227
|
+
);
|
|
228
|
+
return titleMatch || bodyMatch || dataMatch;
|
|
229
|
+
});
|
|
230
|
+
if (options?.type) {
|
|
231
|
+
items = items.filter((item) => item.type === options.type);
|
|
232
|
+
}
|
|
233
|
+
if (options?.status) {
|
|
234
|
+
items = items.filter((item) => item.status === options.status);
|
|
235
|
+
}
|
|
236
|
+
const total = items.length;
|
|
237
|
+
const page = options?.page ?? 1;
|
|
238
|
+
const limit = options?.limit ?? items.length;
|
|
239
|
+
const start = (page - 1) * limit;
|
|
240
|
+
const paged = items.slice(start, start + limit);
|
|
241
|
+
return { items: paged, total, page, limit };
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
var InMemoryWorkflowStorage = class {
|
|
245
|
+
constructor() {
|
|
246
|
+
this.store = /* @__PURE__ */ new Map();
|
|
247
|
+
this.contentTypeMap = /* @__PURE__ */ new Map();
|
|
248
|
+
}
|
|
249
|
+
// contentType -> workflowId
|
|
250
|
+
async list() {
|
|
251
|
+
return Array.from(this.store.values());
|
|
252
|
+
}
|
|
253
|
+
async get(id) {
|
|
254
|
+
return this.store.get(id) ?? null;
|
|
255
|
+
}
|
|
256
|
+
async create(workflow) {
|
|
257
|
+
this.store.set(workflow.id, workflow);
|
|
258
|
+
return workflow;
|
|
259
|
+
}
|
|
260
|
+
async update(id, partial) {
|
|
261
|
+
const existing = this.store.get(id);
|
|
262
|
+
if (!existing) {
|
|
263
|
+
throw new Error(`Workflow not found: ${id}`);
|
|
264
|
+
}
|
|
265
|
+
const updated = { ...existing, ...partial };
|
|
266
|
+
this.store.set(id, updated);
|
|
267
|
+
return updated;
|
|
268
|
+
}
|
|
269
|
+
async delete(id) {
|
|
270
|
+
this.store.delete(id);
|
|
271
|
+
for (const [ct, wId] of this.contentTypeMap.entries()) {
|
|
272
|
+
if (wId === id) {
|
|
273
|
+
this.contentTypeMap.delete(ct);
|
|
426
274
|
}
|
|
427
|
-
}
|
|
275
|
+
}
|
|
428
276
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
277
|
+
async findByContentType(contentType) {
|
|
278
|
+
const workflowId = this.contentTypeMap.get(contentType);
|
|
279
|
+
if (!workflowId) {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
return this.store.get(workflowId) ?? null;
|
|
283
|
+
}
|
|
284
|
+
setContentTypeMapping(contentType, workflowId) {
|
|
285
|
+
this.contentTypeMap.set(contentType, workflowId);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
var InMemoryContentTypeStorage = class {
|
|
289
|
+
constructor() {
|
|
290
|
+
this.store = /* @__PURE__ */ new Map();
|
|
291
|
+
}
|
|
292
|
+
async list() {
|
|
293
|
+
return Array.from(this.store.values());
|
|
294
|
+
}
|
|
295
|
+
async get(id) {
|
|
296
|
+
return this.store.get(id) ?? null;
|
|
297
|
+
}
|
|
298
|
+
async create(def) {
|
|
299
|
+
this.store.set(def.id, def);
|
|
300
|
+
return def;
|
|
301
|
+
}
|
|
302
|
+
async update(id, partial) {
|
|
303
|
+
const existing = this.store.get(id);
|
|
304
|
+
if (!existing) {
|
|
305
|
+
throw new Error(`Content type not found: ${id}`);
|
|
306
|
+
}
|
|
307
|
+
const updated = { ...existing, ...partial };
|
|
308
|
+
this.store.set(id, updated);
|
|
309
|
+
return updated;
|
|
310
|
+
}
|
|
311
|
+
async delete(id) {
|
|
312
|
+
this.store.delete(id);
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
var InMemorySocialPostStorage = class {
|
|
316
|
+
constructor() {
|
|
317
|
+
this.store = /* @__PURE__ */ new Map();
|
|
318
|
+
}
|
|
319
|
+
async list(filters) {
|
|
320
|
+
let items = Array.from(this.store.values());
|
|
321
|
+
if (filters?.contentId) {
|
|
322
|
+
items = items.filter((p) => p.contentId === filters.contentId);
|
|
323
|
+
}
|
|
324
|
+
if (filters?.platform) {
|
|
325
|
+
items = items.filter((p) => p.platform === filters.platform);
|
|
326
|
+
}
|
|
327
|
+
if (filters?.status) {
|
|
328
|
+
items = items.filter((p) => p.status === filters.status);
|
|
329
|
+
}
|
|
330
|
+
return items;
|
|
331
|
+
}
|
|
332
|
+
async get(id) {
|
|
333
|
+
return this.store.get(id) ?? null;
|
|
334
|
+
}
|
|
335
|
+
async create(post) {
|
|
336
|
+
this.store.set(post.id, post);
|
|
337
|
+
return post;
|
|
338
|
+
}
|
|
339
|
+
async update(id, partial) {
|
|
340
|
+
const existing = this.store.get(id);
|
|
341
|
+
if (!existing) {
|
|
342
|
+
throw new Error(`Social post not found: ${id}`);
|
|
343
|
+
}
|
|
344
|
+
const updated = { ...existing, ...partial };
|
|
345
|
+
this.store.set(id, updated);
|
|
346
|
+
return updated;
|
|
347
|
+
}
|
|
348
|
+
async delete(id) {
|
|
349
|
+
this.store.delete(id);
|
|
350
|
+
}
|
|
351
|
+
async findByContent(contentId) {
|
|
352
|
+
return Array.from(this.store.values()).filter((p) => p.contentId === contentId);
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
var InMemoryAIReviewStorage = class {
|
|
356
|
+
constructor() {
|
|
357
|
+
this.store = /* @__PURE__ */ new Map();
|
|
358
|
+
}
|
|
359
|
+
async list(filters) {
|
|
360
|
+
let items = Array.from(this.store.values());
|
|
361
|
+
if (filters?.contentId) {
|
|
362
|
+
items = items.filter((r) => r.contentId === filters.contentId);
|
|
363
|
+
}
|
|
364
|
+
return items;
|
|
365
|
+
}
|
|
366
|
+
async get(id) {
|
|
367
|
+
return this.store.get(id) ?? null;
|
|
368
|
+
}
|
|
369
|
+
async create(review) {
|
|
370
|
+
this.store.set(review.id, review);
|
|
371
|
+
return review;
|
|
372
|
+
}
|
|
373
|
+
async findByContent(contentId) {
|
|
374
|
+
return Array.from(this.store.values()).filter((r) => r.contentId === contentId);
|
|
375
|
+
}
|
|
376
|
+
async getLatestForContent(contentId) {
|
|
377
|
+
const reviews = await this.findByContent(contentId);
|
|
378
|
+
if (reviews.length === 0) {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
reviews.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
382
|
+
return reviews[0] ?? null;
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
function createInMemoryAdapter() {
|
|
386
|
+
return {
|
|
387
|
+
content: new InMemoryContentStorage(),
|
|
388
|
+
workflows: new InMemoryWorkflowStorage(),
|
|
389
|
+
contentTypes: new InMemoryContentTypeStorage(),
|
|
390
|
+
socialPosts: new InMemorySocialPostStorage(),
|
|
391
|
+
aiReviews: new InMemoryAIReviewStorage()
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// src/namespaces/content.ts
|
|
396
|
+
import * as crypto from "crypto";
|
|
397
|
+
var ContentNamespaceImpl = class {
|
|
398
|
+
constructor(emitter, storage, getContentTypes, getPublishers) {
|
|
399
|
+
this.emitter = emitter;
|
|
400
|
+
this.storage = storage;
|
|
401
|
+
this.getContentTypes = getContentTypes;
|
|
402
|
+
this.getPublishers = getPublishers;
|
|
403
|
+
}
|
|
404
|
+
async create(input) {
|
|
405
|
+
const contentTypes = this.getContentTypes();
|
|
406
|
+
const typeDef = contentTypes.get(input.type);
|
|
407
|
+
if (typeDef && typeDef.schema) {
|
|
408
|
+
const dataToValidate = {
|
|
409
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
410
|
+
...input.body !== void 0 ? { body: input.body } : {},
|
|
411
|
+
...input.data ?? {}
|
|
412
|
+
};
|
|
413
|
+
const result = contentTypes.validate(input.type, dataToValidate);
|
|
414
|
+
if (!result.valid) {
|
|
415
|
+
throw new ValidationError("Content type schema validation failed", {
|
|
416
|
+
details: { errors: result.errors }
|
|
417
|
+
});
|
|
443
418
|
}
|
|
419
|
+
}
|
|
420
|
+
const item = {
|
|
421
|
+
id: crypto.randomUUID(),
|
|
422
|
+
type: input.type,
|
|
423
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
424
|
+
status: "draft",
|
|
425
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
426
|
+
...input.body !== void 0 ? { body: input.body } : {},
|
|
427
|
+
...input.data !== void 0 ? { data: { ...input.data } } : {},
|
|
428
|
+
...input.channels !== void 0 ? { channels: input.channels } : {},
|
|
429
|
+
...input.metadata !== void 0 ? { metadata: input.metadata } : {}
|
|
444
430
|
};
|
|
431
|
+
const created = await this.storage.content.create(item);
|
|
432
|
+
this.emitter.emit("content:created", created);
|
|
433
|
+
return created;
|
|
445
434
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
}
|
|
435
|
+
async get(id) {
|
|
436
|
+
const item = await this.storage.content.get(id);
|
|
437
|
+
if (!item) {
|
|
438
|
+
throw new NotFoundError(`Content item not found: ${id}`);
|
|
439
|
+
}
|
|
440
|
+
return item;
|
|
441
|
+
}
|
|
442
|
+
async list(filters) {
|
|
443
|
+
return this.storage.content.list(filters);
|
|
444
|
+
}
|
|
445
|
+
async update(id, input) {
|
|
446
|
+
const existing = await this.storage.content.get(id);
|
|
447
|
+
if (!existing) {
|
|
448
|
+
throw new NotFoundError(`Content item not found: ${id}`);
|
|
449
|
+
}
|
|
450
|
+
const partial = {
|
|
451
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
452
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
453
|
+
...input.body !== void 0 ? { body: input.body } : {},
|
|
454
|
+
...input.status !== void 0 ? { status: input.status } : {},
|
|
455
|
+
...input.channels !== void 0 ? { channels: input.channels } : {},
|
|
456
|
+
...input.metadata !== void 0 ? { metadata: input.metadata } : {}
|
|
460
457
|
};
|
|
458
|
+
if (input.data !== void 0) {
|
|
459
|
+
partial.data = { ...existing.data ?? {}, ...input.data };
|
|
460
|
+
}
|
|
461
|
+
const updated = await this.storage.content.update(id, partial);
|
|
462
|
+
this.emitter.emit("content:updated", updated);
|
|
463
|
+
return updated;
|
|
461
464
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
}
|
|
467
|
-
const config = this.configManager?.getConfig() || {};
|
|
468
|
-
return ContentManagementConfigSchema.parse(config);
|
|
469
|
-
} catch (error) {
|
|
470
|
-
console.error("Failed to load configuration:", error);
|
|
471
|
-
return ContentManagementConfigSchema.parse({});
|
|
465
|
+
async delete(id) {
|
|
466
|
+
const existing = await this.storage.content.get(id);
|
|
467
|
+
if (!existing) {
|
|
468
|
+
throw new NotFoundError(`Content item not found: ${id}`);
|
|
472
469
|
}
|
|
470
|
+
await this.storage.content.delete(id);
|
|
471
|
+
this.emitter.emit("content:deleted", { id });
|
|
473
472
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
473
|
+
async publish(id, options) {
|
|
474
|
+
const existing = await this.storage.content.get(id);
|
|
475
|
+
if (!existing) {
|
|
476
|
+
throw new NotFoundError(`Content item not found: ${id}`);
|
|
477
477
|
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
478
|
+
if (existing.workflowId) {
|
|
479
|
+
const workflow = await this.storage.workflows.get(existing.workflowId);
|
|
480
|
+
if (workflow && workflow.stages && workflow.stages.length > 0) {
|
|
481
|
+
const currentStage = workflow.stages.find((s) => s.id === existing.workflowStage);
|
|
482
|
+
const publishStage = workflow.stages.find((s) => s.isPublishStage);
|
|
483
|
+
if (publishStage && (!currentStage || !currentStage.isPublishStage)) {
|
|
484
|
+
throw new ValidationError(
|
|
485
|
+
`Content is not at a publish-eligible stage. Current stage: "${existing.workflowStage ?? "none"}", required publish stage: "${publishStage.id}"`,
|
|
486
|
+
{
|
|
487
|
+
details: {
|
|
488
|
+
currentStage: existing.workflowStage ?? null,
|
|
489
|
+
requiredStage: publishStage.id
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
483
495
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
496
|
+
const publishers = this.getPublishers();
|
|
497
|
+
const allPublishers = publishers.list();
|
|
498
|
+
const channelIds = options?.channels ?? existing.channels;
|
|
499
|
+
let targetPublishers;
|
|
500
|
+
if (channelIds && channelIds.length > 0) {
|
|
501
|
+
targetPublishers = allPublishers.filter((p) => channelIds.includes(p.id));
|
|
502
|
+
} else {
|
|
503
|
+
targetPublishers = allPublishers.filter((p) => p.acceptedTypes.includes(existing.type));
|
|
504
|
+
}
|
|
505
|
+
const results = [];
|
|
506
|
+
for (const publisher of targetPublishers) {
|
|
507
|
+
if (!publisher.acceptedTypes.includes(existing.type)) {
|
|
508
|
+
results.push({
|
|
509
|
+
channelId: publisher.id,
|
|
510
|
+
success: false,
|
|
511
|
+
error: `Content type "${existing.type}" not accepted by publisher "${publisher.id}". Accepted: ${publisher.acceptedTypes.join(", ")}`
|
|
512
|
+
});
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
if (publisher.validate) {
|
|
516
|
+
const validationErrors = await publisher.validate(existing);
|
|
517
|
+
if (validationErrors.length > 0) {
|
|
518
|
+
results.push({
|
|
519
|
+
channelId: publisher.id,
|
|
520
|
+
success: false,
|
|
521
|
+
error: `Validation failed: ${validationErrors.map((e) => e.message).join(", ")}`
|
|
522
|
+
});
|
|
523
|
+
continue;
|
|
490
524
|
}
|
|
491
|
-
}
|
|
525
|
+
}
|
|
526
|
+
try {
|
|
527
|
+
const result = await publisher.publish(existing);
|
|
528
|
+
results.push(result);
|
|
529
|
+
} catch (error) {
|
|
530
|
+
results.push({
|
|
531
|
+
channelId: publisher.id,
|
|
532
|
+
success: false,
|
|
533
|
+
error: error instanceof Error ? error.message : String(error)
|
|
534
|
+
});
|
|
535
|
+
}
|
|
492
536
|
}
|
|
493
|
-
|
|
494
|
-
this.
|
|
495
|
-
|
|
496
|
-
|
|
537
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
538
|
+
const updated = await this.storage.content.update(id, {
|
|
539
|
+
status: "published",
|
|
540
|
+
publishedAt: now,
|
|
541
|
+
updatedAt: now
|
|
497
542
|
});
|
|
543
|
+
const publishResult = { item: updated, results };
|
|
544
|
+
this.emitter.emit("content:published", publishResult);
|
|
545
|
+
return publishResult;
|
|
498
546
|
}
|
|
499
|
-
|
|
500
|
-
this.
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
547
|
+
async unpublish(id) {
|
|
548
|
+
const existing = await this.storage.content.get(id);
|
|
549
|
+
if (!existing) {
|
|
550
|
+
throw new NotFoundError(`Content item not found: ${id}`);
|
|
551
|
+
}
|
|
552
|
+
const updated = await this.storage.content.update(id, {
|
|
553
|
+
status: "draft",
|
|
554
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
507
555
|
});
|
|
508
|
-
this.
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
router.delete("/content/:id", this.deleteContent.bind(this));
|
|
520
|
-
router.post("/content/:id/publish", this.publishContent.bind(this));
|
|
521
|
-
router.post("/content/:id/schedule", this.scheduleContent.bind(this));
|
|
522
|
-
router.post("/content/:id/unpublish", this.unpublishContent.bind(this));
|
|
523
|
-
router.get("/workflows", this.getWorkflows.bind(this));
|
|
524
|
-
router.get("/workflows/:id", this.getWorkflow.bind(this));
|
|
525
|
-
router.post("/workflows", this.createWorkflow.bind(this));
|
|
526
|
-
router.put("/workflows/:id", this.updateWorkflow.bind(this));
|
|
527
|
-
router.delete("/workflows/:id", this.deleteWorkflow.bind(this));
|
|
528
|
-
router.get("/content-types", this.getContentTypes.bind(this));
|
|
529
|
-
router.get("/content-types/:id", this.handleGetContentType.bind(this));
|
|
530
|
-
router.post("/content-types", this.createContentType.bind(this));
|
|
531
|
-
router.put("/content-types/:id", this.updateContentType.bind(this));
|
|
532
|
-
router.delete("/content-types/:id", this.deleteContentType.bind(this));
|
|
533
|
-
router.get("/config", this.handleGetConfig.bind(this));
|
|
534
|
-
router.put("/config", this.handleUpdateConfig.bind(this));
|
|
535
|
-
router.get("/users", this.getUsers.bind(this));
|
|
536
|
-
router.get("/users/:id", this.getUser.bind(this));
|
|
537
|
-
router.post("/users", this.createUser.bind(this));
|
|
538
|
-
router.put("/users/:id", this.updateUser.bind(this));
|
|
539
|
-
router.delete("/users/:id", this.deleteUser.bind(this));
|
|
540
|
-
return router;
|
|
541
|
-
}
|
|
542
|
-
createAdminRoutes() {
|
|
543
|
-
const router = express.Router();
|
|
544
|
-
router.get("/", (req, res) => {
|
|
545
|
-
res.json({
|
|
546
|
-
message: "Content Management Suite Admin",
|
|
547
|
-
version: "0.1.0",
|
|
548
|
-
endpoints: [
|
|
549
|
-
"/workflows",
|
|
550
|
-
"/content-types",
|
|
551
|
-
"/config",
|
|
552
|
-
"/users"
|
|
553
|
-
]
|
|
554
|
-
});
|
|
556
|
+
this.emitter.emit("content:unpublished", updated);
|
|
557
|
+
return updated;
|
|
558
|
+
}
|
|
559
|
+
async schedule(id, date) {
|
|
560
|
+
const existing = await this.storage.content.get(id);
|
|
561
|
+
if (!existing) {
|
|
562
|
+
throw new NotFoundError(`Content item not found: ${id}`);
|
|
563
|
+
}
|
|
564
|
+
const updated = await this.storage.content.update(id, {
|
|
565
|
+
scheduledFor: date,
|
|
566
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
555
567
|
});
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
message: error.message,
|
|
575
|
-
details: error.details
|
|
576
|
-
}
|
|
577
|
-
});
|
|
568
|
+
this.emitter.emit("content:scheduled", { item: updated, date });
|
|
569
|
+
return updated;
|
|
570
|
+
}
|
|
571
|
+
async search(query, options) {
|
|
572
|
+
return this.storage.content.search(query, options);
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
// src/namespaces/content-types.ts
|
|
577
|
+
import { z as z2 } from "zod";
|
|
578
|
+
var ContentTypesNamespaceImpl = class {
|
|
579
|
+
constructor() {
|
|
580
|
+
this.store = /* @__PURE__ */ new Map();
|
|
581
|
+
}
|
|
582
|
+
register(definition) {
|
|
583
|
+
if ("schema" in definition && definition.schema) {
|
|
584
|
+
const def = definition;
|
|
585
|
+
this.store.set(def.id, def);
|
|
578
586
|
} else {
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
587
|
+
const id = definition.id ?? definition.name;
|
|
588
|
+
if (!id) {
|
|
589
|
+
throw new Error("Content type must have an id or name");
|
|
590
|
+
}
|
|
591
|
+
const name = definition.name ?? definition.id ?? id;
|
|
592
|
+
this.store.set(id, {
|
|
593
|
+
id,
|
|
594
|
+
name,
|
|
595
|
+
schema: null
|
|
585
596
|
});
|
|
586
597
|
}
|
|
587
598
|
}
|
|
588
|
-
|
|
589
|
-
this.
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
this.
|
|
599
|
+
unregister(id) {
|
|
600
|
+
if (!this.store.has(id)) {
|
|
601
|
+
throw new NotFoundError(`Content type not found: ${id}`);
|
|
602
|
+
}
|
|
603
|
+
this.store.delete(id);
|
|
604
|
+
}
|
|
605
|
+
get(id) {
|
|
606
|
+
return this.store.get(id);
|
|
607
|
+
}
|
|
608
|
+
list() {
|
|
609
|
+
return Array.from(this.store.values());
|
|
593
610
|
}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
if (
|
|
597
|
-
|
|
611
|
+
validate(type, data) {
|
|
612
|
+
const def = this.store.get(type);
|
|
613
|
+
if (!def || !def.schema) {
|
|
614
|
+
return { valid: true };
|
|
598
615
|
}
|
|
599
616
|
try {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
const port = this.config.server.port;
|
|
604
|
-
const host = this.config.server.host;
|
|
605
|
-
return new Promise((resolve, reject) => {
|
|
606
|
-
this.httpServer = this.server.listen(port, host, () => {
|
|
607
|
-
console.log(`Content Management Suite started on http://${host}:${port}`);
|
|
608
|
-
this.isStarted = true;
|
|
609
|
-
this.emit("started");
|
|
610
|
-
resolve();
|
|
611
|
-
});
|
|
612
|
-
this.httpServer.on("error", (err) => {
|
|
613
|
-
this.emit("error", err);
|
|
614
|
-
reject(new ContentManagementSuiteInternalError("Failed to start server", err));
|
|
615
|
-
});
|
|
616
|
-
});
|
|
617
|
+
const schema = def.schema;
|
|
618
|
+
schema.parse(data);
|
|
619
|
+
return { valid: true };
|
|
617
620
|
} catch (error) {
|
|
618
|
-
|
|
619
|
-
|
|
621
|
+
if (error instanceof z2.ZodError) {
|
|
622
|
+
return {
|
|
623
|
+
valid: false,
|
|
624
|
+
errors: error.issues.map((issue) => ({
|
|
625
|
+
message: issue.message,
|
|
626
|
+
path: issue.path.map(String)
|
|
627
|
+
}))
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
return {
|
|
631
|
+
valid: false,
|
|
632
|
+
errors: [{ message: error instanceof Error ? error.message : String(error) }]
|
|
633
|
+
};
|
|
620
634
|
}
|
|
621
635
|
}
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
// src/namespaces/workflows.ts
|
|
639
|
+
import * as crypto2 from "crypto";
|
|
640
|
+
var WorkflowsNamespaceImpl = class {
|
|
641
|
+
constructor(storage) {
|
|
642
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
643
|
+
this.storage = storage;
|
|
644
|
+
}
|
|
645
|
+
/** Load workflows from storage into cache. Called during suite initialization. */
|
|
646
|
+
async loadFromStorage() {
|
|
647
|
+
if (!this.storage) {
|
|
648
|
+
return;
|
|
625
649
|
}
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
await this.editorService.stop();
|
|
630
|
-
if (this.httpServer) {
|
|
631
|
-
return new Promise((resolve) => {
|
|
632
|
-
this.httpServer.close(() => {
|
|
633
|
-
console.log("Content Management Suite stopped");
|
|
634
|
-
this.isStopped = true;
|
|
635
|
-
this.emit("stopped");
|
|
636
|
-
resolve();
|
|
637
|
-
});
|
|
638
|
-
});
|
|
639
|
-
}
|
|
640
|
-
this.isStopped = true;
|
|
641
|
-
} catch (error) {
|
|
642
|
-
this.emit("error", error);
|
|
643
|
-
throw new ContentManagementSuiteInternalError("Failed to stop suite", error);
|
|
650
|
+
const workflows = await this.storage.workflows.list();
|
|
651
|
+
for (const w of workflows) {
|
|
652
|
+
this.cache.set(w.id, w);
|
|
644
653
|
}
|
|
645
654
|
}
|
|
646
|
-
|
|
647
|
-
|
|
655
|
+
async create(input) {
|
|
656
|
+
const workflow = {
|
|
657
|
+
id: crypto2.randomUUID(),
|
|
658
|
+
name: input.name,
|
|
659
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
660
|
+
...input.stages !== void 0 ? { stages: input.stages } : {},
|
|
661
|
+
...input.transitions !== void 0 ? { transitions: input.transitions } : {}
|
|
662
|
+
};
|
|
663
|
+
if (this.storage) {
|
|
664
|
+
await this.storage.workflows.create(workflow);
|
|
665
|
+
}
|
|
666
|
+
this.cache.set(workflow.id, workflow);
|
|
667
|
+
return workflow;
|
|
648
668
|
}
|
|
649
|
-
async
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
...config
|
|
654
|
-
});
|
|
655
|
-
this.config = newConfig;
|
|
656
|
-
await this.configManager.updateConfig(newConfig);
|
|
657
|
-
this.emit("config:updated", newConfig);
|
|
658
|
-
} catch (error) {
|
|
659
|
-
throw new ContentManagementSuiteValidationError("Invalid configuration", error);
|
|
669
|
+
async get(id) {
|
|
670
|
+
const workflow = this.cache.get(id);
|
|
671
|
+
if (!workflow) {
|
|
672
|
+
throw new NotFoundError(`Workflow not found: ${id}`);
|
|
660
673
|
}
|
|
674
|
+
return workflow;
|
|
661
675
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
this.contentTypes.set(id, contentType);
|
|
665
|
-
this.emit("contentType:registered", contentType);
|
|
676
|
+
async list() {
|
|
677
|
+
return Array.from(this.cache.values());
|
|
666
678
|
}
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
679
|
+
async update(id, input) {
|
|
680
|
+
const existing = this.cache.get(id);
|
|
681
|
+
if (!existing) {
|
|
682
|
+
throw new NotFoundError(`Workflow not found: ${id}`);
|
|
670
683
|
}
|
|
671
|
-
|
|
672
|
-
|
|
684
|
+
const updated = {
|
|
685
|
+
...existing,
|
|
686
|
+
...input.name !== void 0 ? { name: input.name } : {},
|
|
687
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
688
|
+
...input.stages !== void 0 ? { stages: input.stages } : {},
|
|
689
|
+
...input.transitions !== void 0 ? { transitions: input.transitions } : {}
|
|
690
|
+
};
|
|
691
|
+
if (this.storage) {
|
|
692
|
+
await this.storage.workflows.update(id, updated);
|
|
693
|
+
}
|
|
694
|
+
this.cache.set(id, updated);
|
|
695
|
+
return updated;
|
|
673
696
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
697
|
+
async delete(id) {
|
|
698
|
+
if (!this.cache.has(id)) {
|
|
699
|
+
throw new NotFoundError(`Workflow not found: ${id}`);
|
|
700
|
+
}
|
|
701
|
+
if (this.storage) {
|
|
702
|
+
const contentResult = await this.storage.content.list();
|
|
703
|
+
const assignedContent = contentResult.items.filter((item) => item.workflowId === id);
|
|
704
|
+
if (assignedContent.length > 0) {
|
|
705
|
+
throw new ConflictError(
|
|
706
|
+
`Cannot delete workflow "${id}": ${assignedContent.length} content item(s) are assigned to it. Reassign or archive them first.`,
|
|
707
|
+
{ details: { assignedCount: assignedContent.length } }
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
await this.storage.workflows.delete(id);
|
|
678
711
|
}
|
|
679
|
-
|
|
712
|
+
this.cache.delete(id);
|
|
680
713
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
// src/namespaces/users.ts
|
|
717
|
+
import * as crypto3 from "crypto";
|
|
718
|
+
var UsersNamespaceImpl = class {
|
|
719
|
+
constructor(emitter) {
|
|
720
|
+
this.store = /* @__PURE__ */ new Map();
|
|
721
|
+
this.emitter = emitter;
|
|
687
722
|
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
723
|
+
async create(input) {
|
|
724
|
+
const user = {
|
|
725
|
+
id: crypto3.randomUUID(),
|
|
726
|
+
name: input.name,
|
|
727
|
+
...input.email !== void 0 ? { email: input.email } : {},
|
|
728
|
+
...input.role !== void 0 ? { role: input.role } : {},
|
|
729
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
730
|
+
};
|
|
731
|
+
this.store.set(user.id, user);
|
|
732
|
+
this.emitter.emit("user:created", user);
|
|
733
|
+
return user;
|
|
696
734
|
}
|
|
697
|
-
|
|
698
|
-
const
|
|
699
|
-
if (!
|
|
700
|
-
throw new
|
|
701
|
-
}
|
|
702
|
-
plugin.uninstall(this);
|
|
703
|
-
this.plugins.delete(pluginName);
|
|
704
|
-
this.emit("plugin:unregistered", pluginName);
|
|
705
|
-
}
|
|
706
|
-
// Middleware Management
|
|
707
|
-
addMiddleware(middleware) {
|
|
708
|
-
this.middleware.push(middleware);
|
|
709
|
-
this.emit("middleware:added", middleware);
|
|
710
|
-
}
|
|
711
|
-
removeMiddleware(middlewareName) {
|
|
712
|
-
const index = this.middleware.findIndex((m) => m.name === middlewareName);
|
|
713
|
-
if (index === -1) {
|
|
714
|
-
throw new ContentManagementSuiteNotFoundError("Middleware not found");
|
|
715
|
-
}
|
|
716
|
-
this.middleware.splice(index, 1);
|
|
717
|
-
this.emit("middleware:removed", middlewareName);
|
|
718
|
-
}
|
|
719
|
-
// Hook Management
|
|
720
|
-
addHook(hook) {
|
|
721
|
-
if (!this.hooks.has(hook.name)) {
|
|
722
|
-
this.hooks.set(hook.name, []);
|
|
723
|
-
}
|
|
724
|
-
this.hooks.get(hook.name).push(hook);
|
|
725
|
-
this.hooks.get(hook.name).sort((a, b) => (a.priority || 0) - (b.priority || 0));
|
|
726
|
-
this.emit("hook:added", hook);
|
|
727
|
-
}
|
|
728
|
-
removeHook(hookName, handler) {
|
|
729
|
-
const hooks = this.hooks.get(hookName);
|
|
730
|
-
if (!hooks) {
|
|
731
|
-
throw new ContentManagementSuiteNotFoundError("Hook not found");
|
|
732
|
-
}
|
|
733
|
-
if (handler) {
|
|
734
|
-
const index = hooks.findIndex((h) => h.handler === handler);
|
|
735
|
-
if (index === -1) {
|
|
736
|
-
throw new ContentManagementSuiteNotFoundError("Hook handler not found");
|
|
737
|
-
}
|
|
738
|
-
hooks.splice(index, 1);
|
|
739
|
-
} else {
|
|
740
|
-
this.hooks.delete(hookName);
|
|
735
|
+
async get(id) {
|
|
736
|
+
const user = this.store.get(id);
|
|
737
|
+
if (!user) {
|
|
738
|
+
throw new NotFoundError(`User not found: ${id}`);
|
|
741
739
|
}
|
|
742
|
-
|
|
740
|
+
return user;
|
|
743
741
|
}
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
throw new ContentManagementSuiteInternalError("Failed to get content list", error);
|
|
742
|
+
async list() {
|
|
743
|
+
const items = Array.from(this.store.values());
|
|
744
|
+
return { items, total: items.length };
|
|
745
|
+
}
|
|
746
|
+
async update(id, input) {
|
|
747
|
+
const existing = this.store.get(id);
|
|
748
|
+
if (!existing) {
|
|
749
|
+
throw new NotFoundError(`User not found: ${id}`);
|
|
753
750
|
}
|
|
751
|
+
const updated = {
|
|
752
|
+
...existing,
|
|
753
|
+
...input,
|
|
754
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
755
|
+
};
|
|
756
|
+
this.store.set(id, updated);
|
|
757
|
+
this.emitter.emit("user:updated", updated);
|
|
758
|
+
return updated;
|
|
754
759
|
}
|
|
755
|
-
async
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
const content = await this.editorService.getContent(id);
|
|
759
|
-
res.json(content);
|
|
760
|
-
} catch (error) {
|
|
761
|
-
throw new ContentManagementSuiteNotFoundError("Content not found", error);
|
|
760
|
+
async delete(id) {
|
|
761
|
+
if (!this.store.has(id)) {
|
|
762
|
+
throw new NotFoundError(`User not found: ${id}`);
|
|
762
763
|
}
|
|
764
|
+
this.store.delete(id);
|
|
765
|
+
this.emitter.emit("user:deleted", { id });
|
|
763
766
|
}
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
767
|
+
};
|
|
768
|
+
var PermissionsNamespaceImpl = class {
|
|
769
|
+
constructor() {
|
|
770
|
+
this.store = /* @__PURE__ */ new Map();
|
|
771
|
+
}
|
|
772
|
+
async check(userId, permission) {
|
|
773
|
+
const perms = this.store.get(userId);
|
|
774
|
+
return perms?.has(permission) ?? false;
|
|
775
|
+
}
|
|
776
|
+
async grant(userId, permission) {
|
|
777
|
+
let perms = this.store.get(userId);
|
|
778
|
+
if (!perms) {
|
|
779
|
+
perms = /* @__PURE__ */ new Set();
|
|
780
|
+
this.store.set(userId, perms);
|
|
772
781
|
}
|
|
782
|
+
perms.add(permission);
|
|
773
783
|
}
|
|
774
|
-
async
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
const content = await this.editorService.updateContent(id, data);
|
|
779
|
-
this.emit("content:updated", content);
|
|
780
|
-
res.json(content);
|
|
781
|
-
} catch (error) {
|
|
782
|
-
throw new ContentManagementSuiteInternalError("Failed to update content", error);
|
|
784
|
+
async revoke(userId, permission) {
|
|
785
|
+
const perms = this.store.get(userId);
|
|
786
|
+
if (perms) {
|
|
787
|
+
perms.delete(permission);
|
|
783
788
|
}
|
|
784
789
|
}
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
790
|
+
};
|
|
791
|
+
|
|
792
|
+
// src/namespaces/config.ts
|
|
793
|
+
function deepMerge(target, source) {
|
|
794
|
+
const result = { ...target };
|
|
795
|
+
for (const key of Object.keys(source)) {
|
|
796
|
+
const sourceVal = source[key];
|
|
797
|
+
const targetVal = result[key];
|
|
798
|
+
if (sourceVal !== null && sourceVal !== void 0 && typeof sourceVal === "object" && !Array.isArray(sourceVal) && targetVal !== null && targetVal !== void 0 && typeof targetVal === "object" && !Array.isArray(targetVal)) {
|
|
799
|
+
result[key] = deepMerge(
|
|
800
|
+
targetVal,
|
|
801
|
+
sourceVal
|
|
802
|
+
);
|
|
803
|
+
} else {
|
|
804
|
+
result[key] = sourceVal;
|
|
794
805
|
}
|
|
795
806
|
}
|
|
796
|
-
|
|
807
|
+
return result;
|
|
808
|
+
}
|
|
809
|
+
var ConfigNamespaceImpl = class {
|
|
810
|
+
constructor(initialConfig, emitter) {
|
|
811
|
+
this.config = initialConfig;
|
|
812
|
+
this.emitter = emitter;
|
|
813
|
+
}
|
|
814
|
+
get() {
|
|
815
|
+
return this.config;
|
|
816
|
+
}
|
|
817
|
+
update(partial) {
|
|
818
|
+
const merged = deepMerge(this.config, partial);
|
|
797
819
|
try {
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
820
|
+
this.config = ContentManagementConfigSchema.parse(merged);
|
|
821
|
+
} catch (err) {
|
|
822
|
+
throw new ValidationError(
|
|
823
|
+
"Invalid configuration",
|
|
824
|
+
err instanceof Error ? { cause: err } : void 0
|
|
825
|
+
);
|
|
804
826
|
}
|
|
827
|
+
this.emitter.emit("config:updated", this.config);
|
|
828
|
+
return this.config;
|
|
805
829
|
}
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
830
|
+
};
|
|
831
|
+
|
|
832
|
+
// src/namespaces/plugins.ts
|
|
833
|
+
var PluginsNamespaceImpl = class {
|
|
834
|
+
constructor(suite) {
|
|
835
|
+
this.store = /* @__PURE__ */ new Map();
|
|
836
|
+
this.suite = suite;
|
|
837
|
+
}
|
|
838
|
+
async register(plugin) {
|
|
839
|
+
if (this.store.has(plugin.name)) {
|
|
840
|
+
throw new ConflictError(`Plugin already registered: ${plugin.name}`);
|
|
815
841
|
}
|
|
842
|
+
await plugin.setup(this.suite);
|
|
843
|
+
this.store.set(plugin.name, plugin);
|
|
816
844
|
}
|
|
817
|
-
async
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
845
|
+
async unregister(name) {
|
|
846
|
+
const plugin = this.store.get(name);
|
|
847
|
+
if (!plugin) {
|
|
848
|
+
throw new NotFoundError(`Plugin not found: ${name}`);
|
|
849
|
+
}
|
|
850
|
+
if (plugin.teardown) {
|
|
851
|
+
await plugin.teardown(this.suite);
|
|
824
852
|
}
|
|
853
|
+
this.store.delete(name);
|
|
825
854
|
}
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
855
|
+
};
|
|
856
|
+
|
|
857
|
+
// src/namespaces/publishers.ts
|
|
858
|
+
var WebsitePublisher = class {
|
|
859
|
+
constructor() {
|
|
860
|
+
this.id = "website";
|
|
861
|
+
this.name = "Website";
|
|
862
|
+
this.acceptedTypes = ["blog-post", "article", "page", "generic", "text"];
|
|
863
|
+
}
|
|
864
|
+
async publish(content) {
|
|
865
|
+
const slug = content.title ? content.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") : content.id;
|
|
866
|
+
return {
|
|
867
|
+
channelId: this.id,
|
|
868
|
+
success: true,
|
|
869
|
+
url: `/content/${slug}`
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
};
|
|
873
|
+
var RSSPublisher = class {
|
|
874
|
+
constructor() {
|
|
875
|
+
this.id = "rss";
|
|
876
|
+
this.name = "RSS Feed";
|
|
877
|
+
this.acceptedTypes = ["blog-post", "article", "rss-article", "generic", "text"];
|
|
878
|
+
}
|
|
879
|
+
async publish(content) {
|
|
880
|
+
return {
|
|
881
|
+
channelId: this.id,
|
|
882
|
+
success: true,
|
|
883
|
+
url: `/feed/rss/${content.id}`
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
var PublishersNamespaceImpl = class {
|
|
888
|
+
constructor(emitter) {
|
|
889
|
+
this.store = /* @__PURE__ */ new Map();
|
|
890
|
+
this.emitter = emitter;
|
|
891
|
+
this.store.set("website", new WebsitePublisher());
|
|
892
|
+
this.store.set("rss", new RSSPublisher());
|
|
893
|
+
}
|
|
894
|
+
register(publisher) {
|
|
895
|
+
this.store.set(publisher.id, publisher);
|
|
896
|
+
this.emitter.emit("publisher:registered", { publisher });
|
|
897
|
+
}
|
|
898
|
+
unregister(id) {
|
|
899
|
+
if (!this.store.has(id)) {
|
|
900
|
+
throw new NotFoundError(`Publisher not found: ${id}`);
|
|
832
901
|
}
|
|
902
|
+
this.store.delete(id);
|
|
833
903
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
904
|
+
list() {
|
|
905
|
+
return Array.from(this.store.values());
|
|
906
|
+
}
|
|
907
|
+
get(id) {
|
|
908
|
+
return this.store.get(id);
|
|
909
|
+
}
|
|
910
|
+
};
|
|
911
|
+
|
|
912
|
+
// src/namespaces/sources.ts
|
|
913
|
+
import * as crypto4 from "crypto";
|
|
914
|
+
var SourcesNamespaceImpl = class {
|
|
915
|
+
constructor(emitter, storage, workflowDefaults) {
|
|
916
|
+
this.store = /* @__PURE__ */ new Map();
|
|
917
|
+
this.emitter = emitter;
|
|
918
|
+
this.storage = storage;
|
|
919
|
+
this.workflowDefaults = workflowDefaults;
|
|
920
|
+
}
|
|
921
|
+
register(source) {
|
|
922
|
+
this.store.set(source.id, source);
|
|
923
|
+
this.emitter.emit("source:registered", { source });
|
|
924
|
+
}
|
|
925
|
+
unregister(id) {
|
|
926
|
+
if (!this.store.has(id)) {
|
|
927
|
+
throw new NotFoundError(`Source not found: ${id}`);
|
|
841
928
|
}
|
|
929
|
+
this.store.delete(id);
|
|
842
930
|
}
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
931
|
+
list() {
|
|
932
|
+
return Array.from(this.store.values());
|
|
933
|
+
}
|
|
934
|
+
get(id) {
|
|
935
|
+
return this.store.get(id);
|
|
936
|
+
}
|
|
937
|
+
async ingest(sourceId, rawPayload) {
|
|
938
|
+
const source = this.store.get(sourceId);
|
|
939
|
+
if (!source) {
|
|
940
|
+
throw new NotFoundError(`Source not found: ${sourceId}`);
|
|
850
941
|
}
|
|
942
|
+
if (source.validate) {
|
|
943
|
+
const validationErrors = await source.validate(rawPayload);
|
|
944
|
+
if (validationErrors.length > 0) {
|
|
945
|
+
throw new ValidationError("Source validation failed", {
|
|
946
|
+
details: { errors: validationErrors }
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
const rawItem = await source.ingest(rawPayload);
|
|
951
|
+
if (rawItem.sourceId) {
|
|
952
|
+
const existing = await this.storage.content.findBySource(source.id, rawItem.sourceId);
|
|
953
|
+
if (existing) {
|
|
954
|
+
return { created: false, content: existing };
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
958
|
+
const workflowId = this.workflowDefaults[rawItem.type] ?? this.workflowDefaults["generic"];
|
|
959
|
+
const item = {
|
|
960
|
+
id: crypto4.randomUUID(),
|
|
961
|
+
type: rawItem.type,
|
|
962
|
+
createdAt: now,
|
|
963
|
+
status: "review",
|
|
964
|
+
sourceType: source.id,
|
|
965
|
+
sourceId: rawItem.sourceId ?? null,
|
|
966
|
+
...rawItem.title !== void 0 ? { title: rawItem.title } : {},
|
|
967
|
+
...rawItem.body !== void 0 ? { body: rawItem.body } : {},
|
|
968
|
+
...rawItem.data !== void 0 ? { data: rawItem.data } : {},
|
|
969
|
+
...rawItem.metadata !== void 0 ? { metadata: rawItem.metadata } : {},
|
|
970
|
+
...workflowId !== void 0 ? { workflowId } : {}
|
|
971
|
+
};
|
|
972
|
+
const created = await this.storage.content.create(item);
|
|
973
|
+
this.emitter.emit("content:ingested", { item: created, sourceType: source.id });
|
|
974
|
+
this.emitter.emit("content:created", created);
|
|
975
|
+
return { created: true, content: created };
|
|
851
976
|
}
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
977
|
+
};
|
|
978
|
+
|
|
979
|
+
// src/namespaces/ai.ts
|
|
980
|
+
import * as crypto5 from "crypto";
|
|
981
|
+
var AINamespaceImpl = class {
|
|
982
|
+
constructor(emitter, storage, options) {
|
|
983
|
+
this.emitter = emitter;
|
|
984
|
+
this.storage = storage;
|
|
985
|
+
this.reviewService = options.reviewService;
|
|
986
|
+
this.socialGeneratorService = options.socialGeneratorService;
|
|
987
|
+
this.enhanceService = options.enhanceService;
|
|
988
|
+
this.workflowDefaults = options.workflowDefaults ?? {};
|
|
989
|
+
}
|
|
990
|
+
async review(contentId) {
|
|
991
|
+
if (!this.reviewService) {
|
|
992
|
+
throw new InternalError("AI review service not configured");
|
|
993
|
+
}
|
|
994
|
+
const content = await this.storage.content.get(contentId);
|
|
995
|
+
if (!content) {
|
|
996
|
+
throw new NotFoundError(`Content item not found: ${contentId}`);
|
|
860
997
|
}
|
|
998
|
+
const textToReview = [content.title, content.body].filter(Boolean).join("\n\n") || JSON.stringify(content.data ?? {});
|
|
999
|
+
const result = await this.reviewService.review(textToReview);
|
|
1000
|
+
const suggestions = result.issues.map((issue) => ({
|
|
1001
|
+
category: issue.category,
|
|
1002
|
+
severity: issue.severity === "info" || issue.severity === "warning" || issue.severity === "error" ? issue.severity : "info",
|
|
1003
|
+
message: issue.message,
|
|
1004
|
+
...issue.originalText !== void 0 ? { originalText: issue.originalText } : {},
|
|
1005
|
+
...issue.suggestedFix !== void 0 ? { suggestedFix: issue.suggestedFix } : {}
|
|
1006
|
+
}));
|
|
1007
|
+
const review = {
|
|
1008
|
+
id: crypto5.randomUUID(),
|
|
1009
|
+
contentId,
|
|
1010
|
+
score: result.overallScore,
|
|
1011
|
+
suggestions,
|
|
1012
|
+
passesThreshold: result.passesThreshold,
|
|
1013
|
+
rawResult: result.raw,
|
|
1014
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1015
|
+
};
|
|
1016
|
+
await this.storage.aiReviews.create(review);
|
|
1017
|
+
this.emitter.emit("ai:review:completed", { contentId, review });
|
|
1018
|
+
return review;
|
|
861
1019
|
}
|
|
862
|
-
async
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
throw new
|
|
1020
|
+
async generateSocialPosts(contentId, platforms) {
|
|
1021
|
+
if (!this.socialGeneratorService) {
|
|
1022
|
+
throw new InternalError("AI social generator service not configured");
|
|
1023
|
+
}
|
|
1024
|
+
const content = await this.storage.content.get(contentId);
|
|
1025
|
+
if (!content) {
|
|
1026
|
+
throw new NotFoundError(`Content item not found: ${contentId}`);
|
|
1027
|
+
}
|
|
1028
|
+
const textContent = [content.title, content.body].filter(Boolean).join("\n\n") || JSON.stringify(content.data ?? {});
|
|
1029
|
+
const generated = await this.socialGeneratorService.generate(textContent, platforms);
|
|
1030
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1031
|
+
const workflowId = this.workflowDefaults["social-post"] ?? this.workflowDefaults["generic"];
|
|
1032
|
+
const posts = generated.map((g) => ({
|
|
1033
|
+
id: crypto5.randomUUID(),
|
|
1034
|
+
contentId,
|
|
1035
|
+
platform: g.platform,
|
|
1036
|
+
body: g.body,
|
|
1037
|
+
status: "draft",
|
|
1038
|
+
platformPostId: null,
|
|
1039
|
+
publishedAt: null,
|
|
1040
|
+
createdAt: now,
|
|
1041
|
+
...workflowId !== void 0 ? { workflowId } : {}
|
|
1042
|
+
}));
|
|
1043
|
+
for (const post of posts) {
|
|
1044
|
+
await this.storage.socialPosts.create(post);
|
|
869
1045
|
}
|
|
1046
|
+
this.emitter.emit("ai:social:generated", { contentId, posts });
|
|
1047
|
+
return posts;
|
|
870
1048
|
}
|
|
871
|
-
async
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
res.json(contentTypes);
|
|
875
|
-
} catch (error) {
|
|
876
|
-
throw new ContentManagementSuiteInternalError("Failed to get content types", error);
|
|
1049
|
+
async enhance(contentId, opts) {
|
|
1050
|
+
if (!this.enhanceService) {
|
|
1051
|
+
throw new InternalError("AI enhance service not configured");
|
|
877
1052
|
}
|
|
1053
|
+
const content = await this.storage.content.get(contentId);
|
|
1054
|
+
if (!content) {
|
|
1055
|
+
throw new NotFoundError(`Content item not found: ${contentId}`);
|
|
1056
|
+
}
|
|
1057
|
+
const textContent = [content.title, content.body].filter(Boolean).join("\n\n") || JSON.stringify(content.data ?? {});
|
|
1058
|
+
return this.enhanceService.enhance(textContent, opts);
|
|
878
1059
|
}
|
|
879
|
-
async
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
if (!id) {
|
|
883
|
-
throw new ContentManagementSuiteNotFoundError("Content type ID required");
|
|
884
|
-
}
|
|
885
|
-
const contentType = this.contentTypes.get(id);
|
|
886
|
-
if (!contentType) {
|
|
887
|
-
throw new ContentManagementSuiteNotFoundError("Content type not found");
|
|
888
|
-
}
|
|
889
|
-
res.json(contentType);
|
|
890
|
-
} catch (error) {
|
|
891
|
-
throw new ContentManagementSuiteNotFoundError("Content type not found", error);
|
|
1060
|
+
async suggest(contentId) {
|
|
1061
|
+
if (!this.enhanceService) {
|
|
1062
|
+
throw new InternalError("AI enhance service not configured");
|
|
892
1063
|
}
|
|
1064
|
+
const content = await this.storage.content.get(contentId);
|
|
1065
|
+
if (!content) {
|
|
1066
|
+
throw new NotFoundError(`Content item not found: ${contentId}`);
|
|
1067
|
+
}
|
|
1068
|
+
const textContent = [content.title, content.body].filter(Boolean).join("\n\n") || JSON.stringify(content.data ?? {});
|
|
1069
|
+
return this.enhanceService.suggest(textContent);
|
|
893
1070
|
}
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
1071
|
+
};
|
|
1072
|
+
|
|
1073
|
+
// src/namespaces/social.ts
|
|
1074
|
+
var SocialNamespaceImpl = class {
|
|
1075
|
+
constructor(emitter, storage) {
|
|
1076
|
+
this.store = /* @__PURE__ */ new Map();
|
|
1077
|
+
this.emitter = emitter;
|
|
1078
|
+
this.storage = storage;
|
|
1079
|
+
}
|
|
1080
|
+
registerPlatform(adapter) {
|
|
1081
|
+
this.store.set(adapter.id, adapter);
|
|
1082
|
+
}
|
|
1083
|
+
unregisterPlatform(id) {
|
|
1084
|
+
if (!this.store.has(id)) {
|
|
1085
|
+
throw new NotFoundError(`Social platform not found: ${id}`);
|
|
901
1086
|
}
|
|
1087
|
+
this.store.delete(id);
|
|
902
1088
|
}
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
1089
|
+
listPlatforms() {
|
|
1090
|
+
return Array.from(this.store.values()).map((adapter) => ({
|
|
1091
|
+
id: adapter.id,
|
|
1092
|
+
name: adapter.name,
|
|
1093
|
+
acceptedTypes: adapter.acceptedTypes,
|
|
1094
|
+
hasPreview: typeof adapter.preview === "function",
|
|
1095
|
+
hasMetrics: typeof adapter.getMetrics === "function"
|
|
1096
|
+
}));
|
|
1097
|
+
}
|
|
1098
|
+
async publish(contentId, platforms) {
|
|
1099
|
+
const missing = platforms.filter((p) => !this.store.has(p));
|
|
1100
|
+
if (missing.length > 0) {
|
|
1101
|
+
throw new ValidationError(
|
|
1102
|
+
`Unregistered social platform(s): ${missing.join(", ")}`,
|
|
1103
|
+
{ details: { missingPlatforms: missing } }
|
|
1104
|
+
);
|
|
1105
|
+
}
|
|
1106
|
+
const content = await this.storage.content.get(contentId);
|
|
1107
|
+
if (!content) {
|
|
1108
|
+
throw new NotFoundError(`Content item not found: ${contentId}`);
|
|
1109
|
+
}
|
|
1110
|
+
if (content.workflowId) {
|
|
1111
|
+
const workflow = await this.storage.workflows.get(content.workflowId);
|
|
1112
|
+
if (workflow && workflow.stages && workflow.stages.length > 0) {
|
|
1113
|
+
const currentStage = workflow.stages.find((s) => s.id === content.workflowStage);
|
|
1114
|
+
const publishStage = workflow.stages.find((s) => s.isPublishStage);
|
|
1115
|
+
if (publishStage && (!currentStage || !currentStage.isPublishStage)) {
|
|
1116
|
+
throw new ValidationError(
|
|
1117
|
+
`Content is not at a publish-eligible stage. Current stage: "${content.workflowStage ?? "none"}", required publish stage: "${publishStage.id}"`,
|
|
1118
|
+
{
|
|
1119
|
+
details: {
|
|
1120
|
+
currentStage: content.workflowStage ?? null,
|
|
1121
|
+
requiredStage: publishStage.id
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
);
|
|
1125
|
+
}
|
|
908
1126
|
}
|
|
909
|
-
const { data } = req.body;
|
|
910
|
-
this.unregisterContentType(id);
|
|
911
|
-
this.registerContentType(data);
|
|
912
|
-
res.json(data);
|
|
913
|
-
} catch (error) {
|
|
914
|
-
throw new ContentManagementSuiteInternalError("Failed to update content type", error);
|
|
915
1127
|
}
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
const
|
|
920
|
-
if (!
|
|
921
|
-
|
|
1128
|
+
const socialPosts = await this.storage.socialPosts.findByContent(contentId);
|
|
1129
|
+
const results = [];
|
|
1130
|
+
for (const platformId of platforms) {
|
|
1131
|
+
const adapter = this.store.get(platformId);
|
|
1132
|
+
if (!adapter) {
|
|
1133
|
+
continue;
|
|
1134
|
+
}
|
|
1135
|
+
if (!adapter.acceptedTypes.includes(content.type)) {
|
|
1136
|
+
results.push({
|
|
1137
|
+
platform: platformId,
|
|
1138
|
+
success: false,
|
|
1139
|
+
error: `Content type "${content.type}" not accepted by platform "${platformId}". Accepted: ${adapter.acceptedTypes.join(", ")}`
|
|
1140
|
+
});
|
|
1141
|
+
continue;
|
|
1142
|
+
}
|
|
1143
|
+
const post = socialPosts.find((p) => p.platform === platformId && p.status === "draft");
|
|
1144
|
+
if (!post) {
|
|
1145
|
+
results.push({
|
|
1146
|
+
platform: platformId,
|
|
1147
|
+
success: false,
|
|
1148
|
+
error: `No draft social post found for platform "${platformId}"`
|
|
1149
|
+
});
|
|
1150
|
+
continue;
|
|
1151
|
+
}
|
|
1152
|
+
try {
|
|
1153
|
+
const result = await adapter.publish(post);
|
|
1154
|
+
results.push(result);
|
|
1155
|
+
if (result.success) {
|
|
1156
|
+
await this.storage.socialPosts.update(post.id, {
|
|
1157
|
+
status: "published",
|
|
1158
|
+
publishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1159
|
+
...result.platformPostId !== void 0 ? { platformPostId: result.platformPostId } : {}
|
|
1160
|
+
});
|
|
1161
|
+
this.emitter.emit("social:published", { contentId, platform: platformId, result });
|
|
1162
|
+
} else {
|
|
1163
|
+
await this.storage.socialPosts.update(post.id, { status: "failed" });
|
|
1164
|
+
}
|
|
1165
|
+
} catch (error) {
|
|
1166
|
+
await this.storage.socialPosts.update(post.id, { status: "failed" });
|
|
1167
|
+
results.push({
|
|
1168
|
+
platform: platformId,
|
|
1169
|
+
success: false,
|
|
1170
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1171
|
+
});
|
|
922
1172
|
}
|
|
923
|
-
this.unregisterContentType(id);
|
|
924
|
-
res.status(204).send();
|
|
925
|
-
} catch (error) {
|
|
926
|
-
throw new ContentManagementSuiteInternalError("Failed to delete content type", error);
|
|
927
1173
|
}
|
|
1174
|
+
return results;
|
|
928
1175
|
}
|
|
929
|
-
async
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
throw new ContentManagementSuiteInternalError("Failed to get config", error);
|
|
1176
|
+
async preview(contentId, platform) {
|
|
1177
|
+
const adapter = this.store.get(platform);
|
|
1178
|
+
if (!adapter) {
|
|
1179
|
+
throw new NotFoundError(`Social platform not found: ${platform}`);
|
|
934
1180
|
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
try {
|
|
938
|
-
const { config } = req.body;
|
|
939
|
-
const newConfig = ContentManagementConfigSchema.parse({
|
|
940
|
-
...this.config,
|
|
941
|
-
...config
|
|
942
|
-
});
|
|
943
|
-
this.config = newConfig;
|
|
944
|
-
await this.configManager.updateConfig(newConfig);
|
|
945
|
-
this.emit("config:updated", newConfig);
|
|
946
|
-
res.json(this.config);
|
|
947
|
-
} catch (error) {
|
|
948
|
-
throw new ContentManagementSuiteValidationError("Invalid configuration", error);
|
|
1181
|
+
if (!adapter.preview) {
|
|
1182
|
+
throw new InternalError(`Platform "${platform}" does not support preview`);
|
|
949
1183
|
}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
} catch (error) {
|
|
955
|
-
throw new ContentManagementSuiteInternalError("Failed to get users", error);
|
|
1184
|
+
const posts = await this.storage.socialPosts.findByContent(contentId);
|
|
1185
|
+
const post = posts.find((p) => p.platform === platform);
|
|
1186
|
+
if (!post) {
|
|
1187
|
+
throw new NotFoundError(`No social post found for content "${contentId}" on platform "${platform}"`);
|
|
956
1188
|
}
|
|
1189
|
+
return adapter.preview(post);
|
|
957
1190
|
}
|
|
958
|
-
async
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
} catch (error) {
|
|
963
|
-
throw new ContentManagementSuiteNotFoundError("User not found", error);
|
|
1191
|
+
async getMetrics(contentId, platform) {
|
|
1192
|
+
const adapter = this.store.get(platform);
|
|
1193
|
+
if (!adapter) {
|
|
1194
|
+
throw new NotFoundError(`Social platform not found: ${platform}`);
|
|
964
1195
|
}
|
|
1196
|
+
if (!adapter.getMetrics) {
|
|
1197
|
+
throw new InternalError(`Platform "${platform}" does not support metrics`);
|
|
1198
|
+
}
|
|
1199
|
+
const posts = await this.storage.socialPosts.findByContent(contentId);
|
|
1200
|
+
const post = posts.find((p) => p.platform === platform && p.platformPostId);
|
|
1201
|
+
if (!post || !post.platformPostId) {
|
|
1202
|
+
throw new NotFoundError(`No published social post found for content "${contentId}" on platform "${platform}"`);
|
|
1203
|
+
}
|
|
1204
|
+
return adapter.getMetrics(post.platformPostId);
|
|
965
1205
|
}
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1206
|
+
};
|
|
1207
|
+
|
|
1208
|
+
// src/content-management-suite.ts
|
|
1209
|
+
var ContentManagementSuiteImpl = class extends EventEmitter {
|
|
1210
|
+
constructor(options) {
|
|
1211
|
+
super();
|
|
1212
|
+
this._initialized = false;
|
|
1213
|
+
this._options = options ?? {};
|
|
1214
|
+
this._logger = this._options.logger;
|
|
1215
|
+
this._storage = this._options.storage ?? createInMemoryAdapter();
|
|
1216
|
+
const parsedConfig = ContentManagementConfigSchema.parse(
|
|
1217
|
+
this._options.config ?? {}
|
|
1218
|
+
);
|
|
1219
|
+
const workflowDefaults = this._options.workflows?.defaults ?? {};
|
|
1220
|
+
this.contentTypes = new ContentTypesNamespaceImpl();
|
|
1221
|
+
this.publishers = new PublishersNamespaceImpl(this);
|
|
1222
|
+
this.content = new ContentNamespaceImpl(
|
|
1223
|
+
this,
|
|
1224
|
+
this._storage,
|
|
1225
|
+
() => this.contentTypes,
|
|
1226
|
+
() => this.publishers
|
|
1227
|
+
);
|
|
1228
|
+
this.workflows = new WorkflowsNamespaceImpl(this._storage);
|
|
1229
|
+
this.users = new UsersNamespaceImpl(this);
|
|
1230
|
+
this.permissions = new PermissionsNamespaceImpl();
|
|
1231
|
+
this.config = new ConfigNamespaceImpl(parsedConfig, this);
|
|
1232
|
+
this.plugins = new PluginsNamespaceImpl(this);
|
|
1233
|
+
this.sources = new SourcesNamespaceImpl(this, this._storage, workflowDefaults);
|
|
1234
|
+
const aiOpts = {};
|
|
1235
|
+
if (this._options.ai?.reviewService) {
|
|
1236
|
+
aiOpts.reviewService = this._options.ai.reviewService;
|
|
1237
|
+
}
|
|
1238
|
+
if (this._options.ai?.socialGeneratorService) {
|
|
1239
|
+
aiOpts.socialGeneratorService = this._options.ai.socialGeneratorService;
|
|
1240
|
+
}
|
|
1241
|
+
if (this._options.ai?.enhanceService) {
|
|
1242
|
+
aiOpts.enhanceService = this._options.ai.enhanceService;
|
|
972
1243
|
}
|
|
1244
|
+
if (Object.keys(workflowDefaults).length > 0) {
|
|
1245
|
+
aiOpts.workflowDefaults = workflowDefaults;
|
|
1246
|
+
}
|
|
1247
|
+
this.ai = new AINamespaceImpl(this, this._storage, aiOpts);
|
|
1248
|
+
this.social = new SocialNamespaceImpl(this, this._storage);
|
|
973
1249
|
}
|
|
974
|
-
async
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
const { data } = req.body;
|
|
978
|
-
res.json({ id, ...data });
|
|
979
|
-
} catch (error) {
|
|
980
|
-
throw new ContentManagementSuiteInternalError("Failed to update user", error);
|
|
1250
|
+
async initialize() {
|
|
1251
|
+
if (this._initialized) {
|
|
1252
|
+
throw new InternalError("Suite is already initialized");
|
|
981
1253
|
}
|
|
1254
|
+
this.contentTypes.register({ id: "text", name: "text" });
|
|
1255
|
+
this.contentTypes.register({ id: "image", name: "image" });
|
|
1256
|
+
this.contentTypes.register({ id: "audio", name: "audio" });
|
|
1257
|
+
this.contentTypes.register({ id: "video", name: "video" });
|
|
1258
|
+
const workflowsImpl = this.workflows;
|
|
1259
|
+
if (typeof workflowsImpl.loadFromStorage === "function") {
|
|
1260
|
+
await workflowsImpl.loadFromStorage();
|
|
1261
|
+
}
|
|
1262
|
+
if (this._options.plugins) {
|
|
1263
|
+
for (const plugin of this._options.plugins) {
|
|
1264
|
+
await this.plugins.register(plugin);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
this.on("ai:review:completed", async ({ contentId, review }) => {
|
|
1268
|
+
try {
|
|
1269
|
+
const content = await this._storage.content.get(contentId);
|
|
1270
|
+
if (!content || !content.workflowId || !content.workflowStage) {
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1273
|
+
const workflow = await this._storage.workflows.get(content.workflowId);
|
|
1274
|
+
if (!workflow || !workflow.stages) {
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
const currentStage = workflow.stages.find((s) => s.id === content.workflowStage);
|
|
1278
|
+
if (!currentStage || !currentStage.conditions) {
|
|
1279
|
+
return;
|
|
1280
|
+
}
|
|
1281
|
+
const { autoAdvance, minScore, requiresHumanReview } = currentStage.conditions;
|
|
1282
|
+
if (requiresHumanReview) {
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
if (autoAdvance && review.passesThreshold && (minScore === void 0 || review.score >= minScore)) {
|
|
1286
|
+
const sortedStages = [...workflow.stages].sort((a, b) => a.order - b.order);
|
|
1287
|
+
const currentIndex = sortedStages.findIndex((s) => s.id === currentStage.id);
|
|
1288
|
+
const nextStage = sortedStages[currentIndex + 1];
|
|
1289
|
+
if (nextStage) {
|
|
1290
|
+
const fromStage = content.workflowStage;
|
|
1291
|
+
await this._storage.content.update(contentId, {
|
|
1292
|
+
workflowStage: nextStage.id
|
|
1293
|
+
});
|
|
1294
|
+
this.emit("workflow:stage:changed", {
|
|
1295
|
+
contentId,
|
|
1296
|
+
workflowId: content.workflowId,
|
|
1297
|
+
from: fromStage,
|
|
1298
|
+
to: nextStage.id
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
} catch {
|
|
1303
|
+
}
|
|
1304
|
+
});
|
|
1305
|
+
this._initialized = true;
|
|
1306
|
+
this.emit("initialized", void 0);
|
|
982
1307
|
}
|
|
983
|
-
async
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
res.status(204).send();
|
|
987
|
-
} catch (error) {
|
|
988
|
-
throw new ContentManagementSuiteInternalError("Failed to delete user", error);
|
|
1308
|
+
async dispose() {
|
|
1309
|
+
if (!this._initialized) {
|
|
1310
|
+
throw new InternalError("Suite is not initialized");
|
|
989
1311
|
}
|
|
1312
|
+
this._initialized = false;
|
|
1313
|
+
this.emit("disposed", void 0);
|
|
1314
|
+
}
|
|
1315
|
+
on(event, listener) {
|
|
1316
|
+
return super.on(event, listener);
|
|
1317
|
+
}
|
|
1318
|
+
emit(event, ...args) {
|
|
1319
|
+
return super.emit(event, ...args);
|
|
990
1320
|
}
|
|
991
1321
|
};
|
|
992
|
-
function createContentManagementSuite(options
|
|
1322
|
+
function createContentManagementSuite(options) {
|
|
993
1323
|
return new ContentManagementSuiteImpl(options);
|
|
994
1324
|
}
|
|
995
1325
|
export {
|
|
996
|
-
|
|
997
|
-
AutosaveManager,
|
|
1326
|
+
ConflictError,
|
|
998
1327
|
ContentManagementConfigSchema,
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
ContentTypeRegistry2 as ContentTypeRegistry,
|
|
1008
|
-
EditorialWorkflowEngine,
|
|
1009
|
-
ImageContentType2 as ImageContentType,
|
|
1010
|
-
StageActionButtons2 as StageActionButtons,
|
|
1011
|
-
TextContentType2 as TextContentType,
|
|
1012
|
-
VideoContentType2 as VideoContentType,
|
|
1013
|
-
WorkflowAdminConfig2 as WorkflowAdminConfig,
|
|
1014
|
-
WorkflowBuilder,
|
|
1015
|
-
WorkflowFactory,
|
|
1016
|
-
WorkflowStepper2 as WorkflowStepper,
|
|
1017
|
-
WorkflowTemplates,
|
|
1018
|
-
WorkflowTimeline2 as WorkflowTimeline,
|
|
1019
|
-
createContentManagementSuite
|
|
1328
|
+
ContentManagementError,
|
|
1329
|
+
ForbiddenError,
|
|
1330
|
+
InternalError,
|
|
1331
|
+
NotFoundError,
|
|
1332
|
+
UnauthorizedError,
|
|
1333
|
+
ValidationError,
|
|
1334
|
+
createContentManagementSuite,
|
|
1335
|
+
createInMemoryAdapter
|
|
1020
1336
|
};
|
|
1021
1337
|
//# sourceMappingURL=index.mjs.map
|