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