@bernierllc/content-management-suite 0.4.2 → 0.6.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 +399 -804
- package/dist/index.d.ts +399 -804
- package/dist/index.js +436 -852
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +428 -851
- package/dist/index.mjs.map +1 -1
- 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 +35 -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,541 @@ 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
|
-
name: "editor",
|
|
201
|
-
permissions: ["content.edit", "content.publish", "content.schedule"],
|
|
202
|
-
description: "Content editing and publishing"
|
|
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
|
+
])
|
|
216
112
|
}).default({}),
|
|
217
|
-
// Logging Configuration
|
|
218
113
|
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({})
|
|
114
|
+
level: z.enum(["error", "warn", "info", "debug"]).default("info")
|
|
231
115
|
}).default({}),
|
|
232
|
-
// Performance Configuration
|
|
233
116
|
performance: z.object({
|
|
234
117
|
cache: z.object({
|
|
235
118
|
enabled: z.boolean().default(true),
|
|
236
119
|
ttl: z.number().default(300),
|
|
237
|
-
// 5 minutes
|
|
238
120
|
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
121
|
}).default({})
|
|
250
122
|
}).default({})
|
|
251
123
|
});
|
|
252
|
-
|
|
253
|
-
|
|
124
|
+
|
|
125
|
+
// src/errors.ts
|
|
126
|
+
var ContentManagementError = class extends Error {
|
|
127
|
+
constructor(message, options) {
|
|
254
128
|
super(message);
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
this.
|
|
129
|
+
if (options?.cause !== void 0) {
|
|
130
|
+
this.cause = options.cause;
|
|
131
|
+
}
|
|
132
|
+
this.name = this.constructor.name;
|
|
133
|
+
this.code = options?.code ?? "CONTENT_MANAGEMENT_ERROR";
|
|
134
|
+
if (options?.details !== void 0) {
|
|
135
|
+
this.details = options.details;
|
|
136
|
+
}
|
|
259
137
|
}
|
|
260
138
|
};
|
|
261
|
-
var
|
|
262
|
-
constructor(message,
|
|
263
|
-
super(message, "VALIDATION_ERROR"
|
|
264
|
-
this.name = "ContentManagementSuiteValidationError";
|
|
139
|
+
var ValidationError = class extends ContentManagementError {
|
|
140
|
+
constructor(message, options) {
|
|
141
|
+
super(message, { ...options, code: "VALIDATION_ERROR" });
|
|
265
142
|
}
|
|
266
143
|
};
|
|
267
|
-
var
|
|
268
|
-
constructor(message,
|
|
269
|
-
super(message, "NOT_FOUND"
|
|
270
|
-
this.name = "ContentManagementSuiteNotFoundError";
|
|
144
|
+
var NotFoundError = class extends ContentManagementError {
|
|
145
|
+
constructor(message, options) {
|
|
146
|
+
super(message, { ...options, code: "NOT_FOUND" });
|
|
271
147
|
}
|
|
272
148
|
};
|
|
273
|
-
var
|
|
274
|
-
constructor(message,
|
|
275
|
-
super(message, "UNAUTHORIZED"
|
|
276
|
-
this.name = "ContentManagementSuiteUnauthorizedError";
|
|
149
|
+
var UnauthorizedError = class extends ContentManagementError {
|
|
150
|
+
constructor(message, options) {
|
|
151
|
+
super(message, { ...options, code: "UNAUTHORIZED" });
|
|
277
152
|
}
|
|
278
153
|
};
|
|
279
|
-
var
|
|
280
|
-
constructor(message,
|
|
281
|
-
super(message, "FORBIDDEN"
|
|
282
|
-
this.name = "ContentManagementSuiteForbiddenError";
|
|
154
|
+
var ForbiddenError = class extends ContentManagementError {
|
|
155
|
+
constructor(message, options) {
|
|
156
|
+
super(message, { ...options, code: "FORBIDDEN" });
|
|
283
157
|
}
|
|
284
158
|
};
|
|
285
|
-
var
|
|
286
|
-
constructor(message,
|
|
287
|
-
super(message, "CONFLICT"
|
|
288
|
-
this.name = "ContentManagementSuiteConflictError";
|
|
159
|
+
var ConflictError = class extends ContentManagementError {
|
|
160
|
+
constructor(message, options) {
|
|
161
|
+
super(message, { ...options, code: "CONFLICT" });
|
|
289
162
|
}
|
|
290
163
|
};
|
|
291
|
-
var
|
|
292
|
-
constructor(message,
|
|
293
|
-
super(message, "INTERNAL_ERROR"
|
|
294
|
-
this.name = "ContentManagementSuiteInternalError";
|
|
164
|
+
var InternalError = class extends ContentManagementError {
|
|
165
|
+
constructor(message, options) {
|
|
166
|
+
super(message, { ...options, code: "INTERNAL_ERROR" });
|
|
295
167
|
}
|
|
296
168
|
};
|
|
297
169
|
|
|
298
|
-
// src/
|
|
299
|
-
import
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
|
170
|
+
// src/namespaces/content.ts
|
|
171
|
+
import * as crypto from "crypto";
|
|
172
|
+
var ContentNamespaceImpl = class {
|
|
173
|
+
constructor(emitter) {
|
|
174
|
+
this.store = /* @__PURE__ */ new Map();
|
|
175
|
+
this.emitter = emitter;
|
|
176
|
+
}
|
|
177
|
+
async create(input) {
|
|
178
|
+
const item = {
|
|
179
|
+
id: crypto.randomUUID(),
|
|
180
|
+
type: input.type,
|
|
181
|
+
data: { ...input.data },
|
|
182
|
+
status: "draft",
|
|
183
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
361
184
|
};
|
|
362
|
-
this.
|
|
363
|
-
this.
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
this.
|
|
368
|
-
if (
|
|
369
|
-
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
185
|
+
this.store.set(item.id, item);
|
|
186
|
+
this.emitter.emit("content:created", item);
|
|
187
|
+
return item;
|
|
188
|
+
}
|
|
189
|
+
async get(id) {
|
|
190
|
+
const item = this.store.get(id);
|
|
191
|
+
if (!item) {
|
|
192
|
+
throw new NotFoundError(`Content item not found: ${id}`);
|
|
193
|
+
}
|
|
194
|
+
return item;
|
|
195
|
+
}
|
|
196
|
+
async list(filters) {
|
|
197
|
+
let items = Array.from(this.store.values());
|
|
198
|
+
if (filters?.type) {
|
|
199
|
+
items = items.filter((item) => item.type === filters.type);
|
|
200
|
+
}
|
|
201
|
+
if (filters?.status) {
|
|
202
|
+
items = items.filter((item) => item.status === filters.status);
|
|
203
|
+
}
|
|
204
|
+
const total = items.length;
|
|
205
|
+
const page = filters?.page ?? 1;
|
|
206
|
+
const limit = filters?.limit ?? items.length;
|
|
207
|
+
const start = (page - 1) * limit;
|
|
208
|
+
const paged = items.slice(start, start + limit);
|
|
209
|
+
return { items: paged, total, page, limit };
|
|
210
|
+
}
|
|
211
|
+
async update(id, input) {
|
|
212
|
+
const existing = this.store.get(id);
|
|
213
|
+
if (!existing) {
|
|
214
|
+
throw new NotFoundError(`Content item not found: ${id}`);
|
|
215
|
+
}
|
|
216
|
+
const updated = {
|
|
217
|
+
...existing,
|
|
218
|
+
data: { ...existing.data, ...input.data },
|
|
219
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
383
220
|
};
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
return
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
221
|
+
this.store.set(id, updated);
|
|
222
|
+
this.emitter.emit("content:updated", updated);
|
|
223
|
+
return updated;
|
|
224
|
+
}
|
|
225
|
+
async delete(id) {
|
|
226
|
+
if (!this.store.has(id)) {
|
|
227
|
+
throw new NotFoundError(`Content item not found: ${id}`);
|
|
228
|
+
}
|
|
229
|
+
this.store.delete(id);
|
|
230
|
+
this.emitter.emit("content:deleted", { id });
|
|
231
|
+
}
|
|
232
|
+
async publish(id) {
|
|
233
|
+
const existing = this.store.get(id);
|
|
234
|
+
if (!existing) {
|
|
235
|
+
throw new NotFoundError(`Content item not found: ${id}`);
|
|
236
|
+
}
|
|
237
|
+
const published = {
|
|
238
|
+
...existing,
|
|
239
|
+
status: "published",
|
|
240
|
+
publishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
241
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
400
242
|
};
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
if (!existing)
|
|
415
|
-
throw new Error("Content not found");
|
|
416
|
-
const updated = { ...existing, ...data, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
417
|
-
contentStore.set(id, updated);
|
|
418
|
-
return updated;
|
|
419
|
-
},
|
|
420
|
-
deleteContent: async (id, _soft) => {
|
|
421
|
-
contentStore.delete(id);
|
|
422
|
-
},
|
|
423
|
-
start: async () => {
|
|
424
|
-
},
|
|
425
|
-
stop: async () => {
|
|
426
|
-
}
|
|
243
|
+
this.store.set(id, published);
|
|
244
|
+
this.emitter.emit("content:published", published);
|
|
245
|
+
return published;
|
|
246
|
+
}
|
|
247
|
+
async unpublish(id) {
|
|
248
|
+
const existing = this.store.get(id);
|
|
249
|
+
if (!existing) {
|
|
250
|
+
throw new NotFoundError(`Content item not found: ${id}`);
|
|
251
|
+
}
|
|
252
|
+
const unpublished = {
|
|
253
|
+
...existing,
|
|
254
|
+
status: "draft",
|
|
255
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
427
256
|
};
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
},
|
|
442
|
-
off: (_event, _callback) => {
|
|
443
|
-
}
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
// Stub for list interface (until @bernierllc/content-list-ui is published)
|
|
447
|
-
createListStub() {
|
|
448
|
-
return {
|
|
449
|
-
render: () => null,
|
|
450
|
-
getItems: () => [],
|
|
451
|
-
setItems: (_items) => {
|
|
452
|
-
},
|
|
453
|
-
getSelectedItems: () => [],
|
|
454
|
-
setSelectedItems: (_items) => {
|
|
455
|
-
},
|
|
456
|
-
on: (_event, _callback) => {
|
|
457
|
-
},
|
|
458
|
-
off: (_event, _callback) => {
|
|
459
|
-
}
|
|
257
|
+
this.store.set(id, unpublished);
|
|
258
|
+
this.emitter.emit("content:unpublished", unpublished);
|
|
259
|
+
return unpublished;
|
|
260
|
+
}
|
|
261
|
+
async schedule(id, date) {
|
|
262
|
+
const existing = this.store.get(id);
|
|
263
|
+
if (!existing) {
|
|
264
|
+
throw new NotFoundError(`Content item not found: ${id}`);
|
|
265
|
+
}
|
|
266
|
+
const scheduled = {
|
|
267
|
+
...existing,
|
|
268
|
+
scheduledFor: date,
|
|
269
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
460
270
|
};
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
return
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
return ContentManagementConfigSchema.parse({});
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
setupMiddleware() {
|
|
475
|
-
if (this.config.server.security.helmet) {
|
|
476
|
-
this.server.use(helmet());
|
|
477
|
-
}
|
|
478
|
-
this.server.use(cors(this.config.server.cors));
|
|
479
|
-
if (this.config.performance.compression.enabled) {
|
|
480
|
-
this.server.use(compression({
|
|
481
|
-
level: this.config.performance.compression.level
|
|
482
|
-
}));
|
|
483
|
-
}
|
|
484
|
-
if (this.options.logger) {
|
|
485
|
-
this.server.use(morgan("combined", {
|
|
486
|
-
stream: {
|
|
487
|
-
write: (message) => {
|
|
488
|
-
this.options.logger?.info(message.trim());
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}));
|
|
492
|
-
}
|
|
493
|
-
this.server.use(express.json({ limit: "10mb" }));
|
|
494
|
-
this.server.use(express.urlencoded({ extended: true, limit: "10mb" }));
|
|
495
|
-
this.middleware.sort((a, b) => (a.order || 0) - (b.order || 0)).forEach((middleware) => {
|
|
496
|
-
this.server.use(middleware.handler);
|
|
271
|
+
this.store.set(id, scheduled);
|
|
272
|
+
this.emitter.emit("content:scheduled", { item: scheduled, date });
|
|
273
|
+
return scheduled;
|
|
274
|
+
}
|
|
275
|
+
async search(query, options) {
|
|
276
|
+
const lowerQuery = query.toLowerCase();
|
|
277
|
+
let items = Array.from(this.store.values()).filter((item) => {
|
|
278
|
+
return Object.values(item.data).some(
|
|
279
|
+
(value) => typeof value === "string" && value.toLowerCase().includes(lowerQuery)
|
|
280
|
+
);
|
|
497
281
|
});
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
this.server.get("/health", (req, res) => {
|
|
501
|
-
res.json({
|
|
502
|
-
status: "healthy",
|
|
503
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
504
|
-
version: "0.1.0",
|
|
505
|
-
uptime: process.uptime()
|
|
506
|
-
});
|
|
507
|
-
});
|
|
508
|
-
this.server.use("/api", this.createAPIRoutes());
|
|
509
|
-
this.server.use("/admin", this.createAdminRoutes());
|
|
510
|
-
this.server.use("/static", express.static("public"));
|
|
511
|
-
this.server.use(this.errorHandler.bind(this));
|
|
512
|
-
}
|
|
513
|
-
createAPIRoutes() {
|
|
514
|
-
const router = express.Router();
|
|
515
|
-
router.get("/content", this.getContentList.bind(this));
|
|
516
|
-
router.get("/content/:id", this.getContent.bind(this));
|
|
517
|
-
router.post("/content", this.createContent.bind(this));
|
|
518
|
-
router.put("/content/:id", this.updateContent.bind(this));
|
|
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
|
-
});
|
|
555
|
-
});
|
|
556
|
-
router.get("/workflows", this.getWorkflows.bind(this));
|
|
557
|
-
router.post("/workflows", this.createWorkflow.bind(this));
|
|
558
|
-
router.put("/workflows/:id", this.updateWorkflow.bind(this));
|
|
559
|
-
router.delete("/workflows/:id", this.deleteWorkflow.bind(this));
|
|
560
|
-
router.get("/content-types", this.getContentTypes.bind(this));
|
|
561
|
-
router.post("/content-types", this.createContentType.bind(this));
|
|
562
|
-
router.put("/content-types/:id", this.updateContentType.bind(this));
|
|
563
|
-
router.delete("/content-types/:id", this.deleteContentType.bind(this));
|
|
564
|
-
router.get("/config", this.handleGetConfig.bind(this));
|
|
565
|
-
router.put("/config", this.handleUpdateConfig.bind(this));
|
|
566
|
-
return router;
|
|
567
|
-
}
|
|
568
|
-
errorHandler(error, _req, res, _next) {
|
|
569
|
-
this.emit("error", error);
|
|
570
|
-
if (error instanceof ContentManagementSuiteError) {
|
|
571
|
-
res.status(error.statusCode).json({
|
|
572
|
-
error: {
|
|
573
|
-
code: error.code,
|
|
574
|
-
message: error.message,
|
|
575
|
-
details: error.details
|
|
576
|
-
}
|
|
577
|
-
});
|
|
578
|
-
} else {
|
|
579
|
-
res.status(500).json({
|
|
580
|
-
error: {
|
|
581
|
-
code: "INTERNAL_ERROR",
|
|
582
|
-
message: "An unexpected error occurred",
|
|
583
|
-
details: process.env.NODE_ENV === "development" ? error.message : void 0
|
|
584
|
-
}
|
|
585
|
-
});
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
registerDefaultContentTypes() {
|
|
589
|
-
this.contentTypes.set("text", TextContentType);
|
|
590
|
-
this.contentTypes.set("image", ImageContentType);
|
|
591
|
-
this.contentTypes.set("audio", AudioContentTypeManager);
|
|
592
|
-
this.contentTypes.set("video", VideoContentType);
|
|
593
|
-
}
|
|
594
|
-
// Public API Methods
|
|
595
|
-
async start() {
|
|
596
|
-
if (this.isStarted) {
|
|
597
|
-
throw new ContentManagementSuiteError("Suite is already started", "ALREADY_STARTED");
|
|
598
|
-
}
|
|
599
|
-
try {
|
|
600
|
-
await this.configManager.start();
|
|
601
|
-
await this.workflowService.start();
|
|
602
|
-
await this.editorService.start();
|
|
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
|
-
} catch (error) {
|
|
618
|
-
this.emit("error", error);
|
|
619
|
-
throw new ContentManagementSuiteInternalError("Failed to start suite", error);
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
async stop() {
|
|
623
|
-
if (this.isStopped) {
|
|
624
|
-
throw new ContentManagementSuiteError("Suite is already stopped", "ALREADY_STOPPED");
|
|
282
|
+
if (options?.type) {
|
|
283
|
+
items = items.filter((item) => item.type === options.type);
|
|
625
284
|
}
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
await this.workflowService.stop();
|
|
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);
|
|
285
|
+
if (options?.status) {
|
|
286
|
+
items = items.filter((item) => item.status === options.status);
|
|
644
287
|
}
|
|
288
|
+
const total = items.length;
|
|
289
|
+
const page = options?.page ?? 1;
|
|
290
|
+
const limit = options?.limit ?? items.length;
|
|
291
|
+
const start = (page - 1) * limit;
|
|
292
|
+
const paged = items.slice(start, start + limit);
|
|
293
|
+
return { items: paged, total, page, limit };
|
|
645
294
|
}
|
|
646
|
-
|
|
647
|
-
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
// src/namespaces/content-types.ts
|
|
298
|
+
var ContentTypesNamespaceImpl = class {
|
|
299
|
+
constructor() {
|
|
300
|
+
this.store = /* @__PURE__ */ new Map();
|
|
648
301
|
}
|
|
649
|
-
|
|
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);
|
|
302
|
+
register(contentType) {
|
|
303
|
+
const id = contentType.id ?? contentType.name;
|
|
304
|
+
if (!id) {
|
|
305
|
+
throw new Error("Content type must have an id or name");
|
|
660
306
|
}
|
|
307
|
+
this.store.set(id, contentType);
|
|
661
308
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
this.emit("contentType:registered", contentType);
|
|
666
|
-
}
|
|
667
|
-
unregisterContentType(contentTypeId) {
|
|
668
|
-
if (!this.contentTypes.has(contentTypeId)) {
|
|
669
|
-
throw new ContentManagementSuiteNotFoundError("Content type not found");
|
|
309
|
+
unregister(id) {
|
|
310
|
+
if (!this.store.has(id)) {
|
|
311
|
+
throw new NotFoundError(`Content type not found: ${id}`);
|
|
670
312
|
}
|
|
671
|
-
this.
|
|
672
|
-
this.emit("contentType:unregistered", contentTypeId);
|
|
313
|
+
this.store.delete(id);
|
|
673
314
|
}
|
|
674
|
-
|
|
675
|
-
const contentType = this.
|
|
676
|
-
if (
|
|
677
|
-
throw new
|
|
315
|
+
get(id) {
|
|
316
|
+
const contentType = this.store.get(id);
|
|
317
|
+
if (contentType === void 0) {
|
|
318
|
+
throw new NotFoundError(`Content type not found: ${id}`);
|
|
678
319
|
}
|
|
679
|
-
return contentType;
|
|
320
|
+
return { id, contentType };
|
|
680
321
|
}
|
|
681
|
-
|
|
682
|
-
return Array.from(this.
|
|
322
|
+
list() {
|
|
323
|
+
return Array.from(this.store.entries()).map(([id, contentType]) => ({
|
|
683
324
|
id,
|
|
684
|
-
contentType
|
|
685
|
-
...contentType
|
|
325
|
+
contentType
|
|
686
326
|
}));
|
|
687
327
|
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
this.
|
|
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);
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
// src/namespaces/workflows.ts
|
|
331
|
+
import * as crypto2 from "crypto";
|
|
332
|
+
var WorkflowsNamespaceImpl = class {
|
|
333
|
+
constructor() {
|
|
334
|
+
this.store = /* @__PURE__ */ new Map();
|
|
335
|
+
}
|
|
336
|
+
async create(input) {
|
|
337
|
+
const workflow = {
|
|
338
|
+
id: crypto2.randomUUID(),
|
|
339
|
+
name: input.name,
|
|
340
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
341
|
+
...input.stages !== void 0 ? { stages: input.stages } : {},
|
|
342
|
+
...input.transitions !== void 0 ? { transitions: input.transitions } : {}
|
|
343
|
+
};
|
|
344
|
+
this.store.set(workflow.id, workflow);
|
|
345
|
+
return workflow;
|
|
718
346
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
if (!
|
|
722
|
-
|
|
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);
|
|
347
|
+
async get(id) {
|
|
348
|
+
const workflow = this.store.get(id);
|
|
349
|
+
if (!workflow) {
|
|
350
|
+
throw new NotFoundError(`Workflow not found: ${id}`);
|
|
741
351
|
}
|
|
742
|
-
|
|
352
|
+
return workflow;
|
|
743
353
|
}
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
try {
|
|
747
|
-
const { type, status, page = 1, limit = 20 } = req.query;
|
|
748
|
-
const filters = { type, status, page: parseInt(page), limit: parseInt(limit) };
|
|
749
|
-
const content = await this.editorService.listContent(filters);
|
|
750
|
-
res.json(content);
|
|
751
|
-
} catch (error) {
|
|
752
|
-
throw new ContentManagementSuiteInternalError("Failed to get content list", error);
|
|
753
|
-
}
|
|
354
|
+
async list() {
|
|
355
|
+
return Array.from(this.store.values());
|
|
754
356
|
}
|
|
755
|
-
async
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
res.json(content);
|
|
760
|
-
} catch (error) {
|
|
761
|
-
throw new ContentManagementSuiteNotFoundError("Content not found", error);
|
|
357
|
+
async update(id, input) {
|
|
358
|
+
const existing = this.store.get(id);
|
|
359
|
+
if (!existing) {
|
|
360
|
+
throw new NotFoundError(`Workflow not found: ${id}`);
|
|
762
361
|
}
|
|
362
|
+
const updated = { ...existing, ...input };
|
|
363
|
+
this.store.set(id, updated);
|
|
364
|
+
return updated;
|
|
763
365
|
}
|
|
764
|
-
async
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
const content = await this.editorService.createContent(type, data);
|
|
768
|
-
this.emit("content:created", content);
|
|
769
|
-
res.status(201).json(content);
|
|
770
|
-
} catch (error) {
|
|
771
|
-
throw new ContentManagementSuiteInternalError("Failed to create content", error);
|
|
366
|
+
async delete(id) {
|
|
367
|
+
if (!this.store.has(id)) {
|
|
368
|
+
throw new NotFoundError(`Workflow not found: ${id}`);
|
|
772
369
|
}
|
|
370
|
+
this.store.delete(id);
|
|
773
371
|
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
// src/namespaces/users.ts
|
|
375
|
+
import * as crypto3 from "crypto";
|
|
376
|
+
var UsersNamespaceImpl = class {
|
|
377
|
+
constructor(emitter) {
|
|
378
|
+
this.store = /* @__PURE__ */ new Map();
|
|
379
|
+
this.emitter = emitter;
|
|
380
|
+
}
|
|
381
|
+
async create(input) {
|
|
382
|
+
const user = {
|
|
383
|
+
id: crypto3.randomUUID(),
|
|
384
|
+
name: input.name,
|
|
385
|
+
...input.email !== void 0 ? { email: input.email } : {},
|
|
386
|
+
...input.role !== void 0 ? { role: input.role } : {},
|
|
387
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
388
|
+
};
|
|
389
|
+
this.store.set(user.id, user);
|
|
390
|
+
this.emitter.emit("user:created", user);
|
|
391
|
+
return user;
|
|
392
|
+
}
|
|
393
|
+
async get(id) {
|
|
394
|
+
const user = this.store.get(id);
|
|
395
|
+
if (!user) {
|
|
396
|
+
throw new NotFoundError(`User not found: ${id}`);
|
|
397
|
+
}
|
|
398
|
+
return user;
|
|
399
|
+
}
|
|
400
|
+
async list() {
|
|
401
|
+
const items = Array.from(this.store.values());
|
|
402
|
+
return { items, total: items.length };
|
|
403
|
+
}
|
|
404
|
+
async update(id, input) {
|
|
405
|
+
const existing = this.store.get(id);
|
|
406
|
+
if (!existing) {
|
|
407
|
+
throw new NotFoundError(`User not found: ${id}`);
|
|
408
|
+
}
|
|
409
|
+
const updated = {
|
|
410
|
+
...existing,
|
|
411
|
+
...input,
|
|
412
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
413
|
+
};
|
|
414
|
+
this.store.set(id, updated);
|
|
415
|
+
this.emitter.emit("user:updated", updated);
|
|
416
|
+
return updated;
|
|
784
417
|
}
|
|
785
|
-
async
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
const { soft = true } = req.query;
|
|
789
|
-
await this.editorService.deleteContent(id, soft === "true");
|
|
790
|
-
this.emit("content:deleted", { id });
|
|
791
|
-
res.status(204).send();
|
|
792
|
-
} catch (error) {
|
|
793
|
-
throw new ContentManagementSuiteInternalError("Failed to delete content", error);
|
|
418
|
+
async delete(id) {
|
|
419
|
+
if (!this.store.has(id)) {
|
|
420
|
+
throw new NotFoundError(`User not found: ${id}`);
|
|
794
421
|
}
|
|
422
|
+
this.store.delete(id);
|
|
423
|
+
this.emitter.emit("user:deleted", { id });
|
|
795
424
|
}
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
this.emit("content:published", content);
|
|
801
|
-
res.json(content);
|
|
802
|
-
} catch (error) {
|
|
803
|
-
throw new ContentManagementSuiteInternalError("Failed to publish content", error);
|
|
804
|
-
}
|
|
425
|
+
};
|
|
426
|
+
var PermissionsNamespaceImpl = class {
|
|
427
|
+
constructor() {
|
|
428
|
+
this.store = /* @__PURE__ */ new Map();
|
|
805
429
|
}
|
|
806
|
-
async
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
const { date } = req.body;
|
|
810
|
-
const content = await this.workflowService.scheduleContent(id, new Date(date));
|
|
811
|
-
this.emit("content:scheduled", content, new Date(date));
|
|
812
|
-
res.json(content);
|
|
813
|
-
} catch (error) {
|
|
814
|
-
throw new ContentManagementSuiteInternalError("Failed to schedule content", error);
|
|
815
|
-
}
|
|
430
|
+
async check(userId, permission) {
|
|
431
|
+
const perms = this.store.get(userId);
|
|
432
|
+
return perms?.has(permission) ?? false;
|
|
816
433
|
}
|
|
817
|
-
async
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
} catch (error) {
|
|
823
|
-
throw new ContentManagementSuiteInternalError("Failed to unpublish content", error);
|
|
434
|
+
async grant(userId, permission) {
|
|
435
|
+
let perms = this.store.get(userId);
|
|
436
|
+
if (!perms) {
|
|
437
|
+
perms = /* @__PURE__ */ new Set();
|
|
438
|
+
this.store.set(userId, perms);
|
|
824
439
|
}
|
|
440
|
+
perms.add(permission);
|
|
825
441
|
}
|
|
826
|
-
async
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
} catch (error) {
|
|
831
|
-
throw new ContentManagementSuiteInternalError("Failed to get workflows", error);
|
|
442
|
+
async revoke(userId, permission) {
|
|
443
|
+
const perms = this.store.get(userId);
|
|
444
|
+
if (perms) {
|
|
445
|
+
perms.delete(permission);
|
|
832
446
|
}
|
|
833
447
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
// src/namespaces/config.ts
|
|
451
|
+
function deepMerge(target, source) {
|
|
452
|
+
const result = { ...target };
|
|
453
|
+
for (const key of Object.keys(source)) {
|
|
454
|
+
const sourceVal = source[key];
|
|
455
|
+
const targetVal = result[key];
|
|
456
|
+
if (sourceVal !== null && sourceVal !== void 0 && typeof sourceVal === "object" && !Array.isArray(sourceVal) && targetVal !== null && targetVal !== void 0 && typeof targetVal === "object" && !Array.isArray(targetVal)) {
|
|
457
|
+
result[key] = deepMerge(
|
|
458
|
+
targetVal,
|
|
459
|
+
sourceVal
|
|
460
|
+
);
|
|
461
|
+
} else {
|
|
462
|
+
result[key] = sourceVal;
|
|
841
463
|
}
|
|
842
464
|
}
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
throw new ContentManagementSuiteInternalError("Failed to create workflow", error);
|
|
850
|
-
}
|
|
465
|
+
return result;
|
|
466
|
+
}
|
|
467
|
+
var ConfigNamespaceImpl = class {
|
|
468
|
+
constructor(initialConfig, emitter) {
|
|
469
|
+
this.config = initialConfig;
|
|
470
|
+
this.emitter = emitter;
|
|
851
471
|
}
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
const { id } = req.params;
|
|
855
|
-
const { data } = req.body;
|
|
856
|
-
const workflow = await this.workflowService.updateWorkflow(id, data);
|
|
857
|
-
res.json(workflow);
|
|
858
|
-
} catch (error) {
|
|
859
|
-
throw new ContentManagementSuiteInternalError("Failed to update workflow", error);
|
|
860
|
-
}
|
|
472
|
+
get() {
|
|
473
|
+
return this.config;
|
|
861
474
|
}
|
|
862
|
-
|
|
475
|
+
update(partial) {
|
|
476
|
+
const merged = deepMerge(this.config, partial);
|
|
863
477
|
try {
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
478
|
+
this.config = ContentManagementConfigSchema.parse(merged);
|
|
479
|
+
} catch (err) {
|
|
480
|
+
throw new ValidationError(
|
|
481
|
+
"Invalid configuration",
|
|
482
|
+
err instanceof Error ? { cause: err } : void 0
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
this.emitter.emit("config:updated", this.config);
|
|
486
|
+
return this.config;
|
|
870
487
|
}
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
// src/namespaces/plugins.ts
|
|
491
|
+
var PluginsNamespaceImpl = class {
|
|
492
|
+
constructor(suite) {
|
|
493
|
+
this.store = /* @__PURE__ */ new Map();
|
|
494
|
+
this.suite = suite;
|
|
878
495
|
}
|
|
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);
|
|
496
|
+
async register(plugin) {
|
|
497
|
+
if (this.store.has(plugin.name)) {
|
|
498
|
+
throw new ConflictError(`Plugin already registered: ${plugin.name}`);
|
|
892
499
|
}
|
|
500
|
+
await plugin.setup(this.suite);
|
|
501
|
+
this.store.set(plugin.name, plugin);
|
|
893
502
|
}
|
|
894
|
-
async
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
res.status(201).json(data);
|
|
899
|
-
} catch (error) {
|
|
900
|
-
throw new ContentManagementSuiteInternalError("Failed to create content type", error);
|
|
503
|
+
async unregister(name) {
|
|
504
|
+
const plugin = this.store.get(name);
|
|
505
|
+
if (!plugin) {
|
|
506
|
+
throw new NotFoundError(`Plugin not found: ${name}`);
|
|
901
507
|
}
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
try {
|
|
905
|
-
const id = req.params.id;
|
|
906
|
-
if (!id) {
|
|
907
|
-
throw new ContentManagementSuiteNotFoundError("Content type ID required");
|
|
908
|
-
}
|
|
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);
|
|
508
|
+
if (plugin.teardown) {
|
|
509
|
+
await plugin.teardown(this.suite);
|
|
915
510
|
}
|
|
511
|
+
this.store.delete(name);
|
|
916
512
|
}
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
// src/content-management-suite.ts
|
|
516
|
+
var ContentManagementSuiteImpl = class extends EventEmitter {
|
|
517
|
+
constructor(options) {
|
|
518
|
+
super();
|
|
519
|
+
this._initialized = false;
|
|
520
|
+
this._options = options ?? {};
|
|
521
|
+
this._logger = this._options.logger;
|
|
522
|
+
const parsedConfig = ContentManagementConfigSchema.parse(
|
|
523
|
+
this._options.config ?? {}
|
|
524
|
+
);
|
|
525
|
+
this.content = new ContentNamespaceImpl(this);
|
|
526
|
+
this.contentTypes = new ContentTypesNamespaceImpl();
|
|
527
|
+
this.workflows = new WorkflowsNamespaceImpl();
|
|
528
|
+
this.users = new UsersNamespaceImpl(this);
|
|
529
|
+
this.permissions = new PermissionsNamespaceImpl();
|
|
530
|
+
this.config = new ConfigNamespaceImpl(parsedConfig, this);
|
|
531
|
+
this.plugins = new PluginsNamespaceImpl(this);
|
|
532
|
+
}
|
|
533
|
+
async initialize() {
|
|
534
|
+
if (this._initialized) {
|
|
535
|
+
throw new InternalError("Suite is already initialized");
|
|
536
|
+
}
|
|
537
|
+
this.contentTypes.register({ id: "text", name: "text" });
|
|
538
|
+
this.contentTypes.register({ id: "image", name: "image" });
|
|
539
|
+
this.contentTypes.register({ id: "audio", name: "audio" });
|
|
540
|
+
this.contentTypes.register({ id: "video", name: "video" });
|
|
541
|
+
if (this._options.plugins) {
|
|
542
|
+
for (const plugin of this._options.plugins) {
|
|
543
|
+
await this.plugins.register(plugin);
|
|
922
544
|
}
|
|
923
|
-
this.unregisterContentType(id);
|
|
924
|
-
res.status(204).send();
|
|
925
|
-
} catch (error) {
|
|
926
|
-
throw new ContentManagementSuiteInternalError("Failed to delete content type", error);
|
|
927
545
|
}
|
|
546
|
+
this._initialized = true;
|
|
547
|
+
this.emit("initialized", void 0);
|
|
928
548
|
}
|
|
929
|
-
async
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
} catch (error) {
|
|
933
|
-
throw new ContentManagementSuiteInternalError("Failed to get config", error);
|
|
549
|
+
async dispose() {
|
|
550
|
+
if (!this._initialized) {
|
|
551
|
+
throw new InternalError("Suite is not initialized");
|
|
934
552
|
}
|
|
553
|
+
this._initialized = false;
|
|
554
|
+
this.emit("disposed", void 0);
|
|
935
555
|
}
|
|
936
|
-
|
|
937
|
-
|
|
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);
|
|
949
|
-
}
|
|
556
|
+
on(event, listener) {
|
|
557
|
+
return super.on(event, listener);
|
|
950
558
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
res.json([]);
|
|
954
|
-
} catch (error) {
|
|
955
|
-
throw new ContentManagementSuiteInternalError("Failed to get users", error);
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
async getUser(req, res) {
|
|
959
|
-
try {
|
|
960
|
-
const { id } = req.params;
|
|
961
|
-
res.json({ id, name: "User" });
|
|
962
|
-
} catch (error) {
|
|
963
|
-
throw new ContentManagementSuiteNotFoundError("User not found", error);
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
async createUser(req, res) {
|
|
967
|
-
try {
|
|
968
|
-
const { data } = req.body;
|
|
969
|
-
res.status(201).json({ id: uuidv4(), ...data });
|
|
970
|
-
} catch (error) {
|
|
971
|
-
throw new ContentManagementSuiteInternalError("Failed to create user", error);
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
async updateUser(req, res) {
|
|
975
|
-
try {
|
|
976
|
-
const { id } = req.params;
|
|
977
|
-
const { data } = req.body;
|
|
978
|
-
res.json({ id, ...data });
|
|
979
|
-
} catch (error) {
|
|
980
|
-
throw new ContentManagementSuiteInternalError("Failed to update user", error);
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
async deleteUser(req, res) {
|
|
984
|
-
try {
|
|
985
|
-
const { id: _id } = req.params;
|
|
986
|
-
res.status(204).send();
|
|
987
|
-
} catch (error) {
|
|
988
|
-
throw new ContentManagementSuiteInternalError("Failed to delete user", error);
|
|
989
|
-
}
|
|
559
|
+
emit(event, ...args) {
|
|
560
|
+
return super.emit(event, ...args);
|
|
990
561
|
}
|
|
991
562
|
};
|
|
992
|
-
function createContentManagementSuite(options
|
|
563
|
+
function createContentManagementSuite(options) {
|
|
993
564
|
return new ContentManagementSuiteImpl(options);
|
|
994
565
|
}
|
|
566
|
+
|
|
567
|
+
// src/index.ts
|
|
568
|
+
import { ContentTypeRegistry } from "@bernierllc/content-type-registry";
|
|
569
|
+
import { EditorialWorkflowEngine, WorkflowBuilder, WorkflowTemplates, WorkflowFactory } from "@bernierllc/content-editorial-workflow";
|
|
570
|
+
import { AutosaveManager } from "@bernierllc/content-autosave-manager";
|
|
571
|
+
import { ContentSoftDelete } from "@bernierllc/content-soft-delete";
|
|
572
|
+
import { TextContentType } from "@bernierllc/content-type-text";
|
|
573
|
+
import { ImageContentType } from "@bernierllc/content-type-image";
|
|
574
|
+
import { AudioContentTypeManager } from "@bernierllc/content-type-audio";
|
|
575
|
+
import { VideoContentType } from "@bernierllc/content-type-video";
|
|
995
576
|
export {
|
|
996
|
-
|
|
577
|
+
AudioContentTypeManager,
|
|
997
578
|
AutosaveManager,
|
|
579
|
+
ConflictError,
|
|
998
580
|
ContentManagementConfigSchema,
|
|
999
|
-
|
|
1000
|
-
ContentManagementSuiteError,
|
|
1001
|
-
ContentManagementSuiteForbiddenError,
|
|
1002
|
-
ContentManagementSuiteInternalError,
|
|
1003
|
-
ContentManagementSuiteNotFoundError,
|
|
1004
|
-
ContentManagementSuiteUnauthorizedError,
|
|
1005
|
-
ContentManagementSuiteValidationError,
|
|
581
|
+
ContentManagementError,
|
|
1006
582
|
ContentSoftDelete,
|
|
1007
|
-
|
|
583
|
+
ContentTypeRegistry,
|
|
1008
584
|
EditorialWorkflowEngine,
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
585
|
+
ForbiddenError,
|
|
586
|
+
ImageContentType,
|
|
587
|
+
InternalError,
|
|
588
|
+
NotFoundError,
|
|
589
|
+
TextContentType,
|
|
590
|
+
UnauthorizedError,
|
|
591
|
+
ValidationError,
|
|
592
|
+
VideoContentType,
|
|
1014
593
|
WorkflowBuilder,
|
|
1015
594
|
WorkflowFactory,
|
|
1016
|
-
WorkflowStepper2 as WorkflowStepper,
|
|
1017
595
|
WorkflowTemplates,
|
|
1018
|
-
WorkflowTimeline2 as WorkflowTimeline,
|
|
1019
596
|
createContentManagementSuite
|
|
1020
597
|
};
|
|
1021
598
|
//# sourceMappingURL=index.mjs.map
|