@bernierllc/content-management-suite 0.2.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.
@@ -0,0 +1,1089 @@
1
+ #!/usr/bin/env node
2
+ "use client";
3
+
4
+ // src/types.ts
5
+ import { z } from "zod";
6
+ var ContentManagementConfigSchema = z.object({
7
+ // Server Configuration
8
+ server: z.object({
9
+ port: z.number().default(3e3),
10
+ host: z.string().default("localhost"),
11
+ cors: z.object({
12
+ origin: z.union([z.string(), z.array(z.string())]).default("*"),
13
+ credentials: z.boolean().default(true)
14
+ }).default({}),
15
+ security: z.object({
16
+ helmet: z.boolean().default(true),
17
+ rateLimit: z.object({
18
+ enabled: z.boolean().default(true),
19
+ windowMs: z.number().default(15 * 60 * 1e3),
20
+ // 15 minutes
21
+ max: z.number().default(100)
22
+ // limit each IP to 100 requests per windowMs
23
+ }).default({})
24
+ }).default({})
25
+ }).default({}),
26
+ // Database Configuration
27
+ database: z.object({
28
+ type: z.enum(["sqlite", "postgresql", "mysql", "mongodb"]).default("sqlite"),
29
+ url: z.string().optional(),
30
+ host: z.string().default("localhost"),
31
+ port: z.number().optional(),
32
+ name: z.string().default("content_management"),
33
+ username: z.string().optional(),
34
+ password: z.string().optional(),
35
+ ssl: z.boolean().default(false),
36
+ pool: z.object({
37
+ min: z.number().default(2),
38
+ max: z.number().default(10)
39
+ }).default({})
40
+ }).default({}),
41
+ // Content Configuration
42
+ content: z.object({
43
+ // Default editorial workflow
44
+ defaultWorkflow: z.object({
45
+ id: z.string().default("standard"),
46
+ name: z.string().default("Standard Workflow"),
47
+ description: z.string().default("Standard editorial workflow"),
48
+ stages: z.array(z.object({
49
+ id: z.string(),
50
+ name: z.string(),
51
+ order: z.number(),
52
+ isPublishStage: z.boolean().default(false),
53
+ allowsScheduling: z.boolean().default(false),
54
+ description: z.string().optional(),
55
+ permissions: z.array(z.string()).default([])
56
+ })).default([
57
+ {
58
+ id: "write",
59
+ name: "Write",
60
+ order: 1,
61
+ isPublishStage: false,
62
+ allowsScheduling: false,
63
+ description: "Write content",
64
+ permissions: ["content.edit"]
65
+ },
66
+ {
67
+ id: "publish",
68
+ name: "Publish",
69
+ order: 2,
70
+ isPublishStage: true,
71
+ allowsScheduling: true,
72
+ description: "Publish content",
73
+ permissions: ["content.publish"]
74
+ }
75
+ ]),
76
+ transitions: z.array(z.object({
77
+ id: z.string(),
78
+ from: z.string(),
79
+ to: z.string(),
80
+ description: z.string().optional(),
81
+ permissions: z.array(z.string()).default([])
82
+ })).default([
83
+ {
84
+ id: "write-to-publish",
85
+ from: "write",
86
+ to: "publish",
87
+ description: "Move from write to publish",
88
+ permissions: ["content.publish"]
89
+ }
90
+ ])
91
+ }).default({}),
92
+ // Content types
93
+ contentTypes: z.array(z.string()).default(["text", "image", "audio", "video"]),
94
+ // Auto-save configuration
95
+ autoSave: z.object({
96
+ enabled: z.boolean().default(true),
97
+ debounceMs: z.number().default(1e3),
98
+ maxRetries: z.number().default(3),
99
+ backoffMs: z.number().default(1e3)
100
+ }).default({}),
101
+ // Soft delete configuration
102
+ softDelete: z.object({
103
+ enabled: z.boolean().default(true),
104
+ showDeletedToUsers: z.boolean().default(false),
105
+ retentionDays: z.number().default(30)
106
+ }).default({}),
107
+ // File upload configuration
108
+ upload: z.object({
109
+ maxFileSize: z.number().default(104857600),
110
+ // 100MB
111
+ allowedTypes: z.array(z.string()).default([
112
+ "image/jpeg",
113
+ "image/png",
114
+ "image/webp",
115
+ "image/gif",
116
+ "audio/mpeg",
117
+ "audio/wav",
118
+ "audio/ogg",
119
+ "video/mp4",
120
+ "video/webm",
121
+ "video/ogg"
122
+ ]),
123
+ storage: z.object({
124
+ type: z.enum(["local", "s3", "gcs", "azure"]).default("local"),
125
+ path: z.string().default("./uploads"),
126
+ bucket: z.string().optional(),
127
+ region: z.string().optional(),
128
+ accessKey: z.string().optional(),
129
+ secretKey: z.string().optional()
130
+ }).default({})
131
+ }).default({})
132
+ }).default({}),
133
+ // UI Configuration
134
+ ui: z.object({
135
+ theme: z.object({
136
+ mode: z.enum(["light", "dark", "auto"]).default("auto"),
137
+ primaryColor: z.string().default("#007bff"),
138
+ secondaryColor: z.string().default("#6c757d"),
139
+ accentColor: z.string().default("#17a2b8")
140
+ }).default({}),
141
+ editor: z.object({
142
+ showToolbar: z.boolean().default(true),
143
+ showStatusBar: z.boolean().default(true),
144
+ showWordCount: z.boolean().default(true),
145
+ showCharacterCount: z.boolean().default(true),
146
+ autoSave: z.boolean().default(true),
147
+ placeholder: z.string().default("Start writing your content...")
148
+ }).default({}),
149
+ list: z.object({
150
+ defaultView: z.enum(["table", "list", "grid", "kanban"]).default("table"),
151
+ pageSize: z.number().default(20),
152
+ showSearch: z.boolean().default(true),
153
+ showFilters: z.boolean().default(true),
154
+ showSorting: z.boolean().default(true),
155
+ showPagination: z.boolean().default(true)
156
+ }).default({})
157
+ }).default({}),
158
+ // Integration Configuration
159
+ integrations: z.object({
160
+ neverAdmin: z.object({
161
+ enabled: z.boolean().default(false),
162
+ url: z.string().optional(),
163
+ apiKey: z.string().optional(),
164
+ syncInterval: z.number().default(3e5)
165
+ // 5 minutes
166
+ }).default({}),
167
+ neverHub: z.object({
168
+ enabled: z.boolean().default(false),
169
+ url: z.string().optional(),
170
+ apiKey: z.string().optional(),
171
+ packageDiscovery: z.boolean().default(true)
172
+ }).default({}),
173
+ analytics: z.object({
174
+ enabled: z.boolean().default(false),
175
+ provider: z.enum(["google", "mixpanel", "amplitude", "custom"]).optional(),
176
+ trackingId: z.string().optional(),
177
+ config: z.record(z.any()).default({})
178
+ }).default({})
179
+ }).default({}),
180
+ // Security Configuration
181
+ security: z.object({
182
+ jwt: z.object({
183
+ secret: z.string().default("your-secret-key"),
184
+ expiresIn: z.string().default("24h"),
185
+ issuer: z.string().default("content-management-suite")
186
+ }).default({}),
187
+ permissions: z.object({
188
+ enabled: z.boolean().default(true),
189
+ defaultRole: z.string().default("user"),
190
+ roles: z.array(z.object({
191
+ name: z.string(),
192
+ permissions: z.array(z.string()),
193
+ description: z.string().optional()
194
+ })).default([
195
+ {
196
+ name: "admin",
197
+ permissions: ["*"],
198
+ description: "Full administrative access"
199
+ },
200
+ {
201
+ name: "editor",
202
+ permissions: ["content.edit", "content.publish", "content.schedule"],
203
+ description: "Content editing and publishing"
204
+ },
205
+ {
206
+ name: "author",
207
+ permissions: ["content.edit"],
208
+ description: "Content creation and editing"
209
+ },
210
+ {
211
+ name: "user",
212
+ permissions: ["content.view"],
213
+ description: "Content viewing only"
214
+ }
215
+ ])
216
+ }).default({})
217
+ }).default({}),
218
+ // Logging Configuration
219
+ logging: z.object({
220
+ level: z.enum(["error", "warn", "info", "debug"]).default("info"),
221
+ format: z.enum(["json", "text"]).default("json"),
222
+ file: z.object({
223
+ enabled: z.boolean().default(true),
224
+ path: z.string().default("./logs"),
225
+ maxSize: z.string().default("10MB"),
226
+ maxFiles: z.number().default(5)
227
+ }).default({}),
228
+ console: z.object({
229
+ enabled: z.boolean().default(true),
230
+ colorize: z.boolean().default(true)
231
+ }).default({})
232
+ }).default({}),
233
+ // Performance Configuration
234
+ performance: z.object({
235
+ cache: z.object({
236
+ enabled: z.boolean().default(true),
237
+ ttl: z.number().default(300),
238
+ // 5 minutes
239
+ maxSize: z.number().default(1e3)
240
+ }).default({}),
241
+ compression: z.object({
242
+ enabled: z.boolean().default(true),
243
+ level: z.number().min(1).max(9).default(6)
244
+ }).default({}),
245
+ rateLimit: z.object({
246
+ enabled: z.boolean().default(true),
247
+ windowMs: z.number().default(15 * 60 * 1e3),
248
+ // 15 minutes
249
+ max: z.number().default(100)
250
+ }).default({})
251
+ }).default({})
252
+ });
253
+ var ContentManagementSuiteError = class extends Error {
254
+ constructor(message, code, statusCode = 500, details) {
255
+ super(message);
256
+ this.name = "ContentManagementSuiteError";
257
+ this.code = code;
258
+ this.statusCode = statusCode;
259
+ this.details = details;
260
+ }
261
+ };
262
+ var ContentManagementSuiteValidationError = class extends ContentManagementSuiteError {
263
+ constructor(message, details) {
264
+ super(message, "VALIDATION_ERROR", 400, details);
265
+ this.name = "ContentManagementSuiteValidationError";
266
+ }
267
+ };
268
+ var ContentManagementSuiteNotFoundError = class extends ContentManagementSuiteError {
269
+ constructor(message, details) {
270
+ super(message, "NOT_FOUND", 404, details);
271
+ this.name = "ContentManagementSuiteNotFoundError";
272
+ }
273
+ };
274
+ var ContentManagementSuiteConflictError = class extends ContentManagementSuiteError {
275
+ constructor(message, details) {
276
+ super(message, "CONFLICT", 409, details);
277
+ this.name = "ContentManagementSuiteConflictError";
278
+ }
279
+ };
280
+ var ContentManagementSuiteInternalError = class extends ContentManagementSuiteError {
281
+ constructor(message, details) {
282
+ super(message, "INTERNAL_ERROR", 500, details);
283
+ this.name = "ContentManagementSuiteInternalError";
284
+ }
285
+ };
286
+
287
+ // src/index.ts
288
+ import {
289
+ WorkflowStepper as WorkflowStepper2,
290
+ StageActionButtons as StageActionButtons2,
291
+ WorkflowTimeline as WorkflowTimeline2,
292
+ WorkflowAdminConfig as WorkflowAdminConfig2
293
+ } from "@bernierllc/content-workflow-ui";
294
+ import { TextContentType as TextContentType2 } from "@bernierllc/content-type-text";
295
+ import { ImageContentType as ImageContentType2 } from "@bernierllc/content-type-image";
296
+ import { AudioContentTypeManager as AudioContentTypeManager2 } from "@bernierllc/content-type-audio";
297
+ import { VideoContentType as VideoContentType2 } from "@bernierllc/content-type-video";
298
+ import { ContentTypeRegistry as ContentTypeRegistry2 } from "@bernierllc/content-type-registry";
299
+ import { EditorialWorkflowEngine, WorkflowBuilder, WorkflowTemplates, WorkflowFactory } from "@bernierllc/content-editorial-workflow";
300
+ import { AutosaveManager } from "@bernierllc/content-autosave-manager";
301
+ import { ContentSoftDelete } from "@bernierllc/content-soft-delete";
302
+
303
+ // src/content-management-suite.ts
304
+ import { EventEmitter } from "events";
305
+ import express from "express";
306
+ import cors from "cors";
307
+ import helmet from "helmet";
308
+ import morgan from "morgan";
309
+ import compression from "compression";
310
+ import { v4 as uuidv4 } from "uuid";
311
+ import { ContentTypeRegistry } from "@bernierllc/content-type-registry";
312
+ import { TextContentType } from "@bernierllc/content-type-text";
313
+ import { ImageContentType } from "@bernierllc/content-type-image";
314
+ import { AudioContentTypeManager } from "@bernierllc/content-type-audio";
315
+ import { VideoContentType } from "@bernierllc/content-type-video";
316
+ import {
317
+ WorkflowStepper,
318
+ StageActionButtons,
319
+ WorkflowTimeline,
320
+ WorkflowAdminConfig
321
+ } from "@bernierllc/content-workflow-ui";
322
+ var ContentManagementSuiteImpl = class extends EventEmitter {
323
+ constructor(options = {}) {
324
+ super();
325
+ // Stub until @bernierllc/content-editor-service is published
326
+ this.autosaveManager = null;
327
+ this.softDelete = null;
328
+ this.workflowEngine = null;
329
+ // Content Types
330
+ this.contentTypes = /* @__PURE__ */ new Map();
331
+ // Plugins and Middleware
332
+ this.plugins = /* @__PURE__ */ new Map();
333
+ this.middleware = [];
334
+ this.hooks = /* @__PURE__ */ new Map();
335
+ // State
336
+ this.isStarted = false;
337
+ this.isStopped = false;
338
+ this.httpServer = null;
339
+ this.options = options;
340
+ this.config = this.loadConfiguration();
341
+ this.contentTypeRegistry = new ContentTypeRegistry();
342
+ this.configManager = this.createConfigManagerStub();
343
+ this.workflowService = this.createWorkflowServiceStub();
344
+ this.editorService = this.createEditorServiceStub();
345
+ this.workflow = {
346
+ stepper: WorkflowStepper,
347
+ actions: StageActionButtons,
348
+ timeline: WorkflowTimeline,
349
+ admin: WorkflowAdminConfig
350
+ };
351
+ this.server = express();
352
+ this.setupMiddleware();
353
+ this.setupRoutes();
354
+ this.editor = this.createEditorStub();
355
+ this.list = this.createListStub();
356
+ this.registerDefaultContentTypes();
357
+ if (options.plugins) {
358
+ options.plugins.forEach((plugin) => this.registerPlugin(plugin));
359
+ }
360
+ }
361
+ // Stub creators for services not yet published
362
+ createConfigManagerStub() {
363
+ return {
364
+ getConfig: () => this.config,
365
+ updateConfig: async (config2) => {
366
+ this.config = { ...this.config, ...config2 };
367
+ },
368
+ start: async () => {
369
+ },
370
+ stop: async () => {
371
+ }
372
+ };
373
+ }
374
+ createWorkflowServiceStub() {
375
+ return {
376
+ listWorkflows: async () => [],
377
+ getWorkflow: async (id) => ({ id, name: "Default Workflow" }),
378
+ createWorkflow: async (data) => ({ id: uuidv4(), ...data }),
379
+ updateWorkflow: async (id, data) => ({ id, ...data }),
380
+ deleteWorkflow: async (_id) => {
381
+ },
382
+ publishContent: async (id) => ({ id, status: "published" }),
383
+ scheduleContent: async (id, date) => ({ id, scheduledFor: date }),
384
+ unpublishContent: async (id) => ({ id, status: "draft" }),
385
+ start: async () => {
386
+ },
387
+ stop: async () => {
388
+ }
389
+ };
390
+ }
391
+ createEditorServiceStub() {
392
+ const contentStore = /* @__PURE__ */ new Map();
393
+ return {
394
+ listContent: async (_filters) => ({ items: Array.from(contentStore.values()), total: contentStore.size }),
395
+ getContent: async (id) => contentStore.get(id) || null,
396
+ createContent: async (type, data) => {
397
+ const content = { id: uuidv4(), type, ...data, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
398
+ contentStore.set(content.id, content);
399
+ return content;
400
+ },
401
+ updateContent: async (id, data) => {
402
+ const existing = contentStore.get(id);
403
+ if (!existing)
404
+ throw new Error("Content not found");
405
+ const updated = { ...existing, ...data, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
406
+ contentStore.set(id, updated);
407
+ return updated;
408
+ },
409
+ deleteContent: async (id, _soft) => {
410
+ contentStore.delete(id);
411
+ },
412
+ start: async () => {
413
+ },
414
+ stop: async () => {
415
+ }
416
+ };
417
+ }
418
+ // Stub for editor interface (until @bernierllc/content-editor-ui is published)
419
+ createEditorStub() {
420
+ return {
421
+ render: () => null,
422
+ getValue: () => "",
423
+ setValue: (_value) => {
424
+ },
425
+ focus: () => {
426
+ },
427
+ blur: () => {
428
+ },
429
+ on: (_event, _callback) => {
430
+ },
431
+ off: (_event, _callback) => {
432
+ }
433
+ };
434
+ }
435
+ // Stub for list interface (until @bernierllc/content-list-ui is published)
436
+ createListStub() {
437
+ return {
438
+ render: () => null,
439
+ getItems: () => [],
440
+ setItems: (_items) => {
441
+ },
442
+ getSelectedItems: () => [],
443
+ setSelectedItems: (_items) => {
444
+ },
445
+ on: (_event, _callback) => {
446
+ },
447
+ off: (_event, _callback) => {
448
+ }
449
+ };
450
+ }
451
+ loadConfiguration() {
452
+ try {
453
+ if (this.options.config) {
454
+ return ContentManagementConfigSchema.parse(this.options.config);
455
+ }
456
+ const config2 = this.configManager?.getConfig() || {};
457
+ return ContentManagementConfigSchema.parse(config2);
458
+ } catch (error) {
459
+ console.error("Failed to load configuration:", error);
460
+ return ContentManagementConfigSchema.parse({});
461
+ }
462
+ }
463
+ setupMiddleware() {
464
+ if (this.config.server.security.helmet) {
465
+ this.server.use(helmet());
466
+ }
467
+ this.server.use(cors(this.config.server.cors));
468
+ if (this.config.performance.compression.enabled) {
469
+ this.server.use(compression({
470
+ level: this.config.performance.compression.level
471
+ }));
472
+ }
473
+ if (this.options.logger) {
474
+ this.server.use(morgan("combined", {
475
+ stream: {
476
+ write: (message) => {
477
+ this.options.logger?.info(message.trim());
478
+ }
479
+ }
480
+ }));
481
+ }
482
+ this.server.use(express.json({ limit: "10mb" }));
483
+ this.server.use(express.urlencoded({ extended: true, limit: "10mb" }));
484
+ this.middleware.sort((a, b) => (a.order || 0) - (b.order || 0)).forEach((middleware) => {
485
+ this.server.use(middleware.handler);
486
+ });
487
+ }
488
+ setupRoutes() {
489
+ this.server.get("/health", (req, res) => {
490
+ res.json({
491
+ status: "healthy",
492
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
493
+ version: "0.1.0",
494
+ uptime: process.uptime()
495
+ });
496
+ });
497
+ this.server.use("/api", this.createAPIRoutes());
498
+ this.server.use("/admin", this.createAdminRoutes());
499
+ this.server.use("/static", express.static("public"));
500
+ this.server.use(this.errorHandler.bind(this));
501
+ }
502
+ createAPIRoutes() {
503
+ const router = express.Router();
504
+ router.get("/content", this.getContentList.bind(this));
505
+ router.get("/content/:id", this.getContent.bind(this));
506
+ router.post("/content", this.createContent.bind(this));
507
+ router.put("/content/:id", this.updateContent.bind(this));
508
+ router.delete("/content/:id", this.deleteContent.bind(this));
509
+ router.post("/content/:id/publish", this.publishContent.bind(this));
510
+ router.post("/content/:id/schedule", this.scheduleContent.bind(this));
511
+ router.post("/content/:id/unpublish", this.unpublishContent.bind(this));
512
+ router.get("/workflows", this.getWorkflows.bind(this));
513
+ router.get("/workflows/:id", this.getWorkflow.bind(this));
514
+ router.post("/workflows", this.createWorkflow.bind(this));
515
+ router.put("/workflows/:id", this.updateWorkflow.bind(this));
516
+ router.delete("/workflows/:id", this.deleteWorkflow.bind(this));
517
+ router.get("/content-types", this.getContentTypes.bind(this));
518
+ router.get("/content-types/:id", this.handleGetContentType.bind(this));
519
+ router.post("/content-types", this.createContentType.bind(this));
520
+ router.put("/content-types/:id", this.updateContentType.bind(this));
521
+ router.delete("/content-types/:id", this.deleteContentType.bind(this));
522
+ router.get("/config", this.handleGetConfig.bind(this));
523
+ router.put("/config", this.handleUpdateConfig.bind(this));
524
+ router.get("/users", this.getUsers.bind(this));
525
+ router.get("/users/:id", this.getUser.bind(this));
526
+ router.post("/users", this.createUser.bind(this));
527
+ router.put("/users/:id", this.updateUser.bind(this));
528
+ router.delete("/users/:id", this.deleteUser.bind(this));
529
+ return router;
530
+ }
531
+ createAdminRoutes() {
532
+ const router = express.Router();
533
+ router.get("/", (req, res) => {
534
+ res.json({
535
+ message: "Content Management Suite Admin",
536
+ version: "0.1.0",
537
+ endpoints: [
538
+ "/workflows",
539
+ "/content-types",
540
+ "/config",
541
+ "/users"
542
+ ]
543
+ });
544
+ });
545
+ router.get("/workflows", this.getWorkflows.bind(this));
546
+ router.post("/workflows", this.createWorkflow.bind(this));
547
+ router.put("/workflows/:id", this.updateWorkflow.bind(this));
548
+ router.delete("/workflows/:id", this.deleteWorkflow.bind(this));
549
+ router.get("/content-types", this.getContentTypes.bind(this));
550
+ router.post("/content-types", this.createContentType.bind(this));
551
+ router.put("/content-types/:id", this.updateContentType.bind(this));
552
+ router.delete("/content-types/:id", this.deleteContentType.bind(this));
553
+ router.get("/config", this.handleGetConfig.bind(this));
554
+ router.put("/config", this.handleUpdateConfig.bind(this));
555
+ return router;
556
+ }
557
+ errorHandler(error, _req, res, _next) {
558
+ this.emit("error", error);
559
+ if (error instanceof ContentManagementSuiteError) {
560
+ res.status(error.statusCode).json({
561
+ error: {
562
+ code: error.code,
563
+ message: error.message,
564
+ details: error.details
565
+ }
566
+ });
567
+ } else {
568
+ res.status(500).json({
569
+ error: {
570
+ code: "INTERNAL_ERROR",
571
+ message: "An unexpected error occurred",
572
+ details: process.env.NODE_ENV === "development" ? error.message : void 0
573
+ }
574
+ });
575
+ }
576
+ }
577
+ registerDefaultContentTypes() {
578
+ this.contentTypes.set("text", TextContentType);
579
+ this.contentTypes.set("image", ImageContentType);
580
+ this.contentTypes.set("audio", AudioContentTypeManager);
581
+ this.contentTypes.set("video", VideoContentType);
582
+ }
583
+ // Public API Methods
584
+ async start() {
585
+ if (this.isStarted) {
586
+ throw new ContentManagementSuiteError("Suite is already started", "ALREADY_STARTED");
587
+ }
588
+ try {
589
+ await this.configManager.start();
590
+ await this.workflowService.start();
591
+ await this.editorService.start();
592
+ const port = this.config.server.port;
593
+ const host = this.config.server.host;
594
+ return new Promise((resolve, reject) => {
595
+ this.httpServer = this.server.listen(port, host, () => {
596
+ console.log(`Content Management Suite started on http://${host}:${port}`);
597
+ this.isStarted = true;
598
+ this.emit("started");
599
+ resolve();
600
+ });
601
+ this.httpServer.on("error", (err) => {
602
+ this.emit("error", err);
603
+ reject(new ContentManagementSuiteInternalError("Failed to start server", err));
604
+ });
605
+ });
606
+ } catch (error) {
607
+ this.emit("error", error);
608
+ throw new ContentManagementSuiteInternalError("Failed to start suite", error);
609
+ }
610
+ }
611
+ async stop() {
612
+ if (this.isStopped) {
613
+ throw new ContentManagementSuiteError("Suite is already stopped", "ALREADY_STOPPED");
614
+ }
615
+ try {
616
+ await this.configManager.stop();
617
+ await this.workflowService.stop();
618
+ await this.editorService.stop();
619
+ if (this.httpServer) {
620
+ return new Promise((resolve) => {
621
+ this.httpServer.close(() => {
622
+ console.log("Content Management Suite stopped");
623
+ this.isStopped = true;
624
+ this.emit("stopped");
625
+ resolve();
626
+ });
627
+ });
628
+ }
629
+ this.isStopped = true;
630
+ } catch (error) {
631
+ this.emit("error", error);
632
+ throw new ContentManagementSuiteInternalError("Failed to stop suite", error);
633
+ }
634
+ }
635
+ getConfig() {
636
+ return this.config;
637
+ }
638
+ async updateConfig(config2) {
639
+ try {
640
+ const newConfig = ContentManagementConfigSchema.parse({
641
+ ...this.config,
642
+ ...config2
643
+ });
644
+ this.config = newConfig;
645
+ await this.configManager.updateConfig(newConfig);
646
+ this.emit("config:updated", newConfig);
647
+ } catch (error) {
648
+ throw new ContentManagementSuiteValidationError("Invalid configuration", error);
649
+ }
650
+ }
651
+ registerContentType(contentType) {
652
+ const id = contentType?.id || contentType?.name || "unknown";
653
+ this.contentTypes.set(id, contentType);
654
+ this.emit("contentType:registered", contentType);
655
+ }
656
+ unregisterContentType(contentTypeId) {
657
+ if (!this.contentTypes.has(contentTypeId)) {
658
+ throw new ContentManagementSuiteNotFoundError("Content type not found");
659
+ }
660
+ this.contentTypes.delete(contentTypeId);
661
+ this.emit("contentType:unregistered", contentTypeId);
662
+ }
663
+ getContentType(contentTypeId) {
664
+ const contentType = this.contentTypes.get(contentTypeId);
665
+ if (!contentType) {
666
+ throw new ContentManagementSuiteNotFoundError("Content type not found");
667
+ }
668
+ return contentType;
669
+ }
670
+ listContentTypes() {
671
+ return Array.from(this.contentTypes.entries()).map(([id, contentType]) => ({
672
+ id,
673
+ contentType,
674
+ ...contentType
675
+ }));
676
+ }
677
+ // Plugin Management
678
+ registerPlugin(plugin) {
679
+ if (this.plugins.has(plugin.name)) {
680
+ throw new ContentManagementSuiteConflictError("Plugin already registered");
681
+ }
682
+ this.plugins.set(plugin.name, plugin);
683
+ plugin.install(this);
684
+ this.emit("plugin:registered", plugin);
685
+ }
686
+ unregisterPlugin(pluginName) {
687
+ const plugin = this.plugins.get(pluginName);
688
+ if (!plugin) {
689
+ throw new ContentManagementSuiteNotFoundError("Plugin not found");
690
+ }
691
+ plugin.uninstall(this);
692
+ this.plugins.delete(pluginName);
693
+ this.emit("plugin:unregistered", pluginName);
694
+ }
695
+ // Middleware Management
696
+ addMiddleware(middleware) {
697
+ this.middleware.push(middleware);
698
+ this.emit("middleware:added", middleware);
699
+ }
700
+ removeMiddleware(middlewareName) {
701
+ const index = this.middleware.findIndex((m) => m.name === middlewareName);
702
+ if (index === -1) {
703
+ throw new ContentManagementSuiteNotFoundError("Middleware not found");
704
+ }
705
+ this.middleware.splice(index, 1);
706
+ this.emit("middleware:removed", middlewareName);
707
+ }
708
+ // Hook Management
709
+ addHook(hook) {
710
+ if (!this.hooks.has(hook.name)) {
711
+ this.hooks.set(hook.name, []);
712
+ }
713
+ this.hooks.get(hook.name).push(hook);
714
+ this.hooks.get(hook.name).sort((a, b) => (a.priority || 0) - (b.priority || 0));
715
+ this.emit("hook:added", hook);
716
+ }
717
+ removeHook(hookName, handler) {
718
+ const hooks = this.hooks.get(hookName);
719
+ if (!hooks) {
720
+ throw new ContentManagementSuiteNotFoundError("Hook not found");
721
+ }
722
+ if (handler) {
723
+ const index = hooks.findIndex((h) => h.handler === handler);
724
+ if (index === -1) {
725
+ throw new ContentManagementSuiteNotFoundError("Hook handler not found");
726
+ }
727
+ hooks.splice(index, 1);
728
+ } else {
729
+ this.hooks.delete(hookName);
730
+ }
731
+ this.emit("hook:removed", hookName);
732
+ }
733
+ // API Route Handlers
734
+ async getContentList(req, res) {
735
+ try {
736
+ const { type, status, page = 1, limit = 20 } = req.query;
737
+ const filters = { type, status, page: parseInt(page), limit: parseInt(limit) };
738
+ const content = await this.editorService.listContent(filters);
739
+ res.json(content);
740
+ } catch (error) {
741
+ throw new ContentManagementSuiteInternalError("Failed to get content list", error);
742
+ }
743
+ }
744
+ async getContent(req, res) {
745
+ try {
746
+ const { id } = req.params;
747
+ const content = await this.editorService.getContent(id);
748
+ res.json(content);
749
+ } catch (error) {
750
+ throw new ContentManagementSuiteNotFoundError("Content not found", error);
751
+ }
752
+ }
753
+ async createContent(req, res) {
754
+ try {
755
+ const { type, data } = req.body;
756
+ const content = await this.editorService.createContent(type, data);
757
+ this.emit("content:created", content);
758
+ res.status(201).json(content);
759
+ } catch (error) {
760
+ throw new ContentManagementSuiteInternalError("Failed to create content", error);
761
+ }
762
+ }
763
+ async updateContent(req, res) {
764
+ try {
765
+ const { id } = req.params;
766
+ const { data } = req.body;
767
+ const content = await this.editorService.updateContent(id, data);
768
+ this.emit("content:updated", content);
769
+ res.json(content);
770
+ } catch (error) {
771
+ throw new ContentManagementSuiteInternalError("Failed to update content", error);
772
+ }
773
+ }
774
+ async deleteContent(req, res) {
775
+ try {
776
+ const { id } = req.params;
777
+ const { soft = true } = req.query;
778
+ await this.editorService.deleteContent(id, soft === "true");
779
+ this.emit("content:deleted", { id });
780
+ res.status(204).send();
781
+ } catch (error) {
782
+ throw new ContentManagementSuiteInternalError("Failed to delete content", error);
783
+ }
784
+ }
785
+ async publishContent(req, res) {
786
+ try {
787
+ const { id } = req.params;
788
+ const content = await this.workflowService.publishContent(id);
789
+ this.emit("content:published", content);
790
+ res.json(content);
791
+ } catch (error) {
792
+ throw new ContentManagementSuiteInternalError("Failed to publish content", error);
793
+ }
794
+ }
795
+ async scheduleContent(req, res) {
796
+ try {
797
+ const { id } = req.params;
798
+ const { date } = req.body;
799
+ const content = await this.workflowService.scheduleContent(id, new Date(date));
800
+ this.emit("content:scheduled", content, new Date(date));
801
+ res.json(content);
802
+ } catch (error) {
803
+ throw new ContentManagementSuiteInternalError("Failed to schedule content", error);
804
+ }
805
+ }
806
+ async unpublishContent(req, res) {
807
+ try {
808
+ const { id } = req.params;
809
+ const content = await this.workflowService.unpublishContent(id);
810
+ res.json(content);
811
+ } catch (error) {
812
+ throw new ContentManagementSuiteInternalError("Failed to unpublish content", error);
813
+ }
814
+ }
815
+ async getWorkflows(req, res) {
816
+ try {
817
+ const workflows = await this.workflowService.listWorkflows();
818
+ res.json(workflows);
819
+ } catch (error) {
820
+ throw new ContentManagementSuiteInternalError("Failed to get workflows", error);
821
+ }
822
+ }
823
+ async getWorkflow(req, res) {
824
+ try {
825
+ const { id } = req.params;
826
+ const workflow = await this.workflowService.getWorkflow(id);
827
+ res.json(workflow);
828
+ } catch (error) {
829
+ throw new ContentManagementSuiteNotFoundError("Workflow not found", error);
830
+ }
831
+ }
832
+ async createWorkflow(req, res) {
833
+ try {
834
+ const { data } = req.body;
835
+ const workflow = await this.workflowService.createWorkflow(data);
836
+ res.status(201).json(workflow);
837
+ } catch (error) {
838
+ throw new ContentManagementSuiteInternalError("Failed to create workflow", error);
839
+ }
840
+ }
841
+ async updateWorkflow(req, res) {
842
+ try {
843
+ const { id } = req.params;
844
+ const { data } = req.body;
845
+ const workflow = await this.workflowService.updateWorkflow(id, data);
846
+ res.json(workflow);
847
+ } catch (error) {
848
+ throw new ContentManagementSuiteInternalError("Failed to update workflow", error);
849
+ }
850
+ }
851
+ async deleteWorkflow(req, res) {
852
+ try {
853
+ const { id } = req.params;
854
+ await this.workflowService.deleteWorkflow(id);
855
+ res.status(204).send();
856
+ } catch (error) {
857
+ throw new ContentManagementSuiteInternalError("Failed to delete workflow", error);
858
+ }
859
+ }
860
+ async getContentTypes(req, res) {
861
+ try {
862
+ const contentTypes = this.listContentTypes();
863
+ res.json(contentTypes);
864
+ } catch (error) {
865
+ throw new ContentManagementSuiteInternalError("Failed to get content types", error);
866
+ }
867
+ }
868
+ async handleGetContentType(req, res) {
869
+ try {
870
+ const id = req.params.id;
871
+ if (!id) {
872
+ throw new ContentManagementSuiteNotFoundError("Content type ID required");
873
+ }
874
+ const contentType = this.contentTypes.get(id);
875
+ if (!contentType) {
876
+ throw new ContentManagementSuiteNotFoundError("Content type not found");
877
+ }
878
+ res.json(contentType);
879
+ } catch (error) {
880
+ throw new ContentManagementSuiteNotFoundError("Content type not found", error);
881
+ }
882
+ }
883
+ async createContentType(req, res) {
884
+ try {
885
+ const { data } = req.body;
886
+ this.registerContentType(data);
887
+ res.status(201).json(data);
888
+ } catch (error) {
889
+ throw new ContentManagementSuiteInternalError("Failed to create content type", error);
890
+ }
891
+ }
892
+ async updateContentType(req, res) {
893
+ try {
894
+ const id = req.params.id;
895
+ if (!id) {
896
+ throw new ContentManagementSuiteNotFoundError("Content type ID required");
897
+ }
898
+ const { data } = req.body;
899
+ this.unregisterContentType(id);
900
+ this.registerContentType(data);
901
+ res.json(data);
902
+ } catch (error) {
903
+ throw new ContentManagementSuiteInternalError("Failed to update content type", error);
904
+ }
905
+ }
906
+ async deleteContentType(req, res) {
907
+ try {
908
+ const id = req.params.id;
909
+ if (!id) {
910
+ throw new ContentManagementSuiteNotFoundError("Content type ID required");
911
+ }
912
+ this.unregisterContentType(id);
913
+ res.status(204).send();
914
+ } catch (error) {
915
+ throw new ContentManagementSuiteInternalError("Failed to delete content type", error);
916
+ }
917
+ }
918
+ async handleGetConfig(req, res) {
919
+ try {
920
+ res.json(this.config);
921
+ } catch (error) {
922
+ throw new ContentManagementSuiteInternalError("Failed to get config", error);
923
+ }
924
+ }
925
+ async handleUpdateConfig(req, res) {
926
+ try {
927
+ const { config: config2 } = req.body;
928
+ const newConfig = ContentManagementConfigSchema.parse({
929
+ ...this.config,
930
+ ...config2
931
+ });
932
+ this.config = newConfig;
933
+ await this.configManager.updateConfig(newConfig);
934
+ this.emit("config:updated", newConfig);
935
+ res.json(this.config);
936
+ } catch (error) {
937
+ throw new ContentManagementSuiteValidationError("Invalid configuration", error);
938
+ }
939
+ }
940
+ async getUsers(req, res) {
941
+ try {
942
+ res.json([]);
943
+ } catch (error) {
944
+ throw new ContentManagementSuiteInternalError("Failed to get users", error);
945
+ }
946
+ }
947
+ async getUser(req, res) {
948
+ try {
949
+ const { id } = req.params;
950
+ res.json({ id, name: "User" });
951
+ } catch (error) {
952
+ throw new ContentManagementSuiteNotFoundError("User not found", error);
953
+ }
954
+ }
955
+ async createUser(req, res) {
956
+ try {
957
+ const { data } = req.body;
958
+ res.status(201).json({ id: uuidv4(), ...data });
959
+ } catch (error) {
960
+ throw new ContentManagementSuiteInternalError("Failed to create user", error);
961
+ }
962
+ }
963
+ async updateUser(req, res) {
964
+ try {
965
+ const { id } = req.params;
966
+ const { data } = req.body;
967
+ res.json({ id, ...data });
968
+ } catch (error) {
969
+ throw new ContentManagementSuiteInternalError("Failed to update user", error);
970
+ }
971
+ }
972
+ async deleteUser(req, res) {
973
+ try {
974
+ const { id: _id } = req.params;
975
+ res.status(204).send();
976
+ } catch (error) {
977
+ throw new ContentManagementSuiteInternalError("Failed to delete user", error);
978
+ }
979
+ }
980
+ };
981
+ function createContentManagementSuite(options = {}) {
982
+ return new ContentManagementSuiteImpl(options);
983
+ }
984
+
985
+ // src/server.ts
986
+ import { config } from "dotenv";
987
+ config();
988
+ async function main() {
989
+ try {
990
+ console.log("Starting Content Management Suite...");
991
+ const suite = createContentManagementSuite({
992
+ config: {
993
+ server: {
994
+ port: parseInt(process.env.CONTENT_MANAGEMENT_PORT || "3000"),
995
+ host: process.env.CONTENT_MANAGEMENT_HOST || "localhost",
996
+ cors: {
997
+ origin: process.env.CONTENT_MANAGEMENT_CORS_ORIGIN || "*",
998
+ credentials: true
999
+ },
1000
+ security: {
1001
+ helmet: true,
1002
+ rateLimit: {
1003
+ enabled: true,
1004
+ windowMs: 15 * 60 * 1e3,
1005
+ max: 100
1006
+ }
1007
+ }
1008
+ },
1009
+ database: {
1010
+ type: process.env.CONTENT_MANAGEMENT_DATABASE_TYPE || "sqlite",
1011
+ url: process.env.CONTENT_MANAGEMENT_DATABASE_URL,
1012
+ host: process.env.CONTENT_MANAGEMENT_DATABASE_HOST || "localhost",
1013
+ name: process.env.CONTENT_MANAGEMENT_DATABASE_NAME || "content_management",
1014
+ ssl: process.env.CONTENT_MANAGEMENT_DATABASE_SSL === "true",
1015
+ pool: {
1016
+ min: 2,
1017
+ max: 10
1018
+ }
1019
+ },
1020
+ security: {
1021
+ jwt: {
1022
+ secret: process.env.CONTENT_MANAGEMENT_JWT_SECRET || "your-secret-key",
1023
+ expiresIn: process.env.CONTENT_MANAGEMENT_JWT_EXPIRES_IN || "24h",
1024
+ issuer: "content-management-suite"
1025
+ },
1026
+ permissions: {
1027
+ enabled: true,
1028
+ defaultRole: "user",
1029
+ roles: [
1030
+ { name: "admin", permissions: ["*"], description: "Full administrative access" },
1031
+ { name: "editor", permissions: ["content.edit", "content.publish", "content.schedule"], description: "Content editing and publishing" },
1032
+ { name: "author", permissions: ["content.edit"], description: "Content creation and editing" },
1033
+ { name: "user", permissions: ["content.view"], description: "Content viewing only" }
1034
+ ]
1035
+ }
1036
+ },
1037
+ integrations: {
1038
+ neverAdmin: {
1039
+ enabled: process.env.CONTENT_MANAGEMENT_NEVERADMIN_ENABLED === "true",
1040
+ url: process.env.CONTENT_MANAGEMENT_NEVERADMIN_URL,
1041
+ apiKey: process.env.CONTENT_MANAGEMENT_NEVERADMIN_API_KEY,
1042
+ syncInterval: 3e5
1043
+ },
1044
+ neverHub: {
1045
+ enabled: process.env.CONTENT_MANAGEMENT_NEVERHUB_ENABLED === "true",
1046
+ url: process.env.CONTENT_MANAGEMENT_NEVERHUB_URL,
1047
+ apiKey: process.env.CONTENT_MANAGEMENT_NEVERHUB_API_KEY,
1048
+ packageDiscovery: true
1049
+ },
1050
+ analytics: {
1051
+ enabled: false,
1052
+ config: {}
1053
+ }
1054
+ }
1055
+ }
1056
+ });
1057
+ process.on("SIGINT", async () => {
1058
+ console.log("\nReceived SIGINT, shutting down gracefully...");
1059
+ try {
1060
+ await suite.stop();
1061
+ console.log("Suite stopped successfully");
1062
+ process.exit(0);
1063
+ } catch (error) {
1064
+ console.error("Error during shutdown:", error);
1065
+ process.exit(1);
1066
+ }
1067
+ });
1068
+ process.on("SIGTERM", async () => {
1069
+ console.log("\nReceived SIGTERM, shutting down gracefully...");
1070
+ try {
1071
+ await suite.stop();
1072
+ console.log("Suite stopped successfully");
1073
+ process.exit(0);
1074
+ } catch (error) {
1075
+ console.error("Error during shutdown:", error);
1076
+ process.exit(1);
1077
+ }
1078
+ });
1079
+ await suite.start();
1080
+ console.log("Content Management Suite is running");
1081
+ console.log(`Server: http://${process.env.CONTENT_MANAGEMENT_HOST || "localhost"}:${process.env.CONTENT_MANAGEMENT_PORT || "3000"}`);
1082
+ console.log("Press Ctrl+C to stop");
1083
+ } catch (error) {
1084
+ console.error("Failed to start Content Management Suite:", error);
1085
+ process.exit(1);
1086
+ }
1087
+ }
1088
+ main();
1089
+ //# sourceMappingURL=server.mjs.map