@sonicjs-cms/core 1.0.0-alpha.4 → 2.0.0-alpha.10

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.
Files changed (90) hide show
  1. package/README.md +268 -166
  2. package/dist/chunk-3MNMOLSA.js +133 -0
  3. package/dist/chunk-3MNMOLSA.js.map +1 -0
  4. package/dist/chunk-4USDL3UP.cjs +15565 -0
  5. package/dist/chunk-4USDL3UP.cjs.map +1 -0
  6. package/dist/{chunk-CXZDAR6S.js → chunk-7N3HK7ZK.js} +3 -3
  7. package/dist/{chunk-CXZDAR6S.js.map → chunk-7N3HK7ZK.js.map} +1 -1
  8. package/dist/chunk-AGOE25LF.cjs +137 -0
  9. package/dist/chunk-AGOE25LF.cjs.map +1 -0
  10. package/dist/{chunk-NRSL6BQI.js → chunk-BITQ4MFX.js} +3 -3
  11. package/dist/{chunk-NRSL6BQI.js.map → chunk-BITQ4MFX.js.map} +1 -1
  12. package/dist/{chunk-24PWAFUT.cjs → chunk-BUKT6HP5.cjs} +13 -13
  13. package/dist/{chunk-24PWAFUT.cjs.map → chunk-BUKT6HP5.cjs.map} +1 -1
  14. package/dist/{chunk-L3NXO7Y4.cjs → chunk-FVMV5DKA.cjs} +49 -49
  15. package/dist/{chunk-L3NXO7Y4.cjs.map → chunk-FVMV5DKA.cjs.map} +1 -1
  16. package/dist/chunk-GGBHOIDD.js +15552 -0
  17. package/dist/chunk-GGBHOIDD.js.map +1 -0
  18. package/dist/{chunk-EMMSS5I5.cjs → chunk-IGJUBJBW.cjs} +8 -2
  19. package/dist/{chunk-ALTMI5Y2.cjs.map → chunk-IGJUBJBW.cjs.map} +1 -1
  20. package/dist/{chunk-WJ7QYVR2.cjs → chunk-RNR4HA23.cjs} +4 -4
  21. package/dist/{chunk-WJ7QYVR2.cjs.map → chunk-RNR4HA23.cjs.map} +1 -1
  22. package/dist/{chunk-G3PMV62Z.js → chunk-V4OQ3NZ2.js} +7 -3
  23. package/dist/{chunk-G3PMV62Z.js.map → chunk-V4OQ3NZ2.js.map} +1 -1
  24. package/dist/{chunk-PTQZ5FEI.js → chunk-WESS2U3K.js} +3 -3
  25. package/dist/{chunk-PTQZ5FEI.js.map → chunk-WESS2U3K.js.map} +1 -1
  26. package/dist/index.cjs +105 -92
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.js +22 -8
  29. package/dist/index.js.map +1 -1
  30. package/dist/middleware.cjs +23 -23
  31. package/dist/middleware.js +3 -3
  32. package/dist/plugins.cjs +8 -8
  33. package/dist/plugins.js +2 -2
  34. package/dist/routes.cjs +51 -3
  35. package/dist/routes.js +6 -2
  36. package/dist/services.cjs +31 -19
  37. package/dist/services.js +3 -3
  38. package/dist/templates.cjs +1 -1
  39. package/dist/templates.js +1 -1
  40. package/dist/types.cjs +1 -1
  41. package/dist/types.js +1 -1
  42. package/dist/utils.cjs +1 -1
  43. package/dist/utils.js +1 -1
  44. package/migrations/001_initial_schema.sql +198 -0
  45. package/migrations/002_faq_plugin.sql +86 -0
  46. package/migrations/003_stage5_enhancements.sql +121 -0
  47. package/migrations/004_stage6_user_management.sql +183 -0
  48. package/migrations/005_stage7_workflow_automation.sql +294 -0
  49. package/migrations/006_plugin_system.sql +155 -0
  50. package/migrations/007_demo_login_plugin.sql +23 -0
  51. package/migrations/008_fix_slug_validation.sql +22 -0
  52. package/migrations/009_system_logging.sql +57 -0
  53. package/migrations/011_config_managed_collections.sql +14 -0
  54. package/migrations/012_testimonials_plugin.sql +80 -0
  55. package/migrations/013_code_examples_plugin.sql +177 -0
  56. package/migrations/014_fix_plugin_registry.sql +88 -0
  57. package/migrations/015_add_remaining_plugins.sql +89 -0
  58. package/migrations/016_remove_duplicate_cache_plugin.sql +17 -0
  59. package/migrations/017_auth_configurable_fields.sql +49 -0
  60. package/package.json +24 -24
  61. package/dist/chunk-4URGXJP7.js +0 -3
  62. package/dist/chunk-4URGXJP7.js.map +0 -1
  63. package/dist/chunk-ALTMI5Y2.cjs +0 -4
  64. package/dist/chunk-BOLQHE4J.cjs +0 -11
  65. package/dist/chunk-BOLQHE4J.cjs.map +0 -1
  66. package/dist/chunk-EMMSS5I5.cjs.map +0 -1
  67. package/dist/chunk-HD7R6T6I.js +0 -9
  68. package/dist/chunk-HD7R6T6I.js.map +0 -1
  69. package/dist/collection-config-FLlGtsh9.d.cts +0 -107
  70. package/dist/collection-config-FLlGtsh9.d.ts +0 -107
  71. package/dist/index-BlsY5XNH.d.ts +0 -8333
  72. package/dist/index-D45jaIlr.d.cts +0 -8333
  73. package/dist/index.d.cts +0 -136
  74. package/dist/index.d.ts +0 -136
  75. package/dist/middleware.d.cts +0 -206
  76. package/dist/middleware.d.ts +0 -206
  77. package/dist/plugin-UzmDImQc.d.cts +0 -357
  78. package/dist/plugin-UzmDImQc.d.ts +0 -357
  79. package/dist/plugins.d.cts +0 -330
  80. package/dist/plugins.d.ts +0 -330
  81. package/dist/routes.d.cts +0 -17
  82. package/dist/routes.d.ts +0 -17
  83. package/dist/services.d.cts +0 -5
  84. package/dist/services.d.ts +0 -5
  85. package/dist/templates.d.cts +0 -140
  86. package/dist/templates.d.ts +0 -140
  87. package/dist/types.d.cts +0 -41
  88. package/dist/types.d.ts +0 -41
  89. package/dist/utils.d.cts +0 -184
  90. package/dist/utils.d.ts +0 -184
@@ -0,0 +1,198 @@
1
+ -- Initial schema for SonicJS AI
2
+ -- Create users table for authentication
3
+ CREATE TABLE IF NOT EXISTS users (
4
+ id TEXT PRIMARY KEY,
5
+ email TEXT NOT NULL UNIQUE,
6
+ username TEXT NOT NULL UNIQUE,
7
+ first_name TEXT NOT NULL,
8
+ last_name TEXT NOT NULL,
9
+ password_hash TEXT,
10
+ role TEXT NOT NULL DEFAULT 'viewer',
11
+ avatar TEXT,
12
+ is_active INTEGER NOT NULL DEFAULT 1,
13
+ last_login_at INTEGER,
14
+ created_at INTEGER NOT NULL,
15
+ updated_at INTEGER NOT NULL
16
+ );
17
+
18
+ -- Create collections table for content schema definitions
19
+ CREATE TABLE IF NOT EXISTS collections (
20
+ id TEXT PRIMARY KEY,
21
+ name TEXT NOT NULL UNIQUE,
22
+ display_name TEXT NOT NULL,
23
+ description TEXT,
24
+ schema TEXT NOT NULL, -- JSON schema definition
25
+ is_active INTEGER NOT NULL DEFAULT 1,
26
+ created_at INTEGER NOT NULL,
27
+ updated_at INTEGER NOT NULL
28
+ );
29
+
30
+ -- Create content table for actual content data
31
+ CREATE TABLE IF NOT EXISTS content (
32
+ id TEXT PRIMARY KEY,
33
+ collection_id TEXT NOT NULL REFERENCES collections(id),
34
+ slug TEXT NOT NULL,
35
+ title TEXT NOT NULL,
36
+ data TEXT NOT NULL, -- JSON content data
37
+ status TEXT NOT NULL DEFAULT 'draft',
38
+ published_at INTEGER,
39
+ author_id TEXT NOT NULL REFERENCES users(id),
40
+ created_at INTEGER NOT NULL,
41
+ updated_at INTEGER NOT NULL
42
+ );
43
+
44
+ -- Create content_versions table for versioning
45
+ CREATE TABLE IF NOT EXISTS content_versions (
46
+ id TEXT PRIMARY KEY,
47
+ content_id TEXT NOT NULL REFERENCES content(id),
48
+ version INTEGER NOT NULL,
49
+ data TEXT NOT NULL, -- JSON data
50
+ author_id TEXT NOT NULL REFERENCES users(id),
51
+ created_at INTEGER NOT NULL
52
+ );
53
+
54
+ -- Create media/files table with comprehensive R2 integration
55
+ CREATE TABLE IF NOT EXISTS media (
56
+ id TEXT PRIMARY KEY,
57
+ filename TEXT NOT NULL,
58
+ original_name TEXT NOT NULL,
59
+ mime_type TEXT NOT NULL,
60
+ size INTEGER NOT NULL,
61
+ width INTEGER,
62
+ height INTEGER,
63
+ folder TEXT NOT NULL DEFAULT 'uploads',
64
+ r2_key TEXT NOT NULL, -- R2 storage key
65
+ public_url TEXT NOT NULL, -- CDN URL
66
+ thumbnail_url TEXT, -- Cloudflare Images URL
67
+ alt TEXT,
68
+ caption TEXT,
69
+ tags TEXT, -- JSON array of tags
70
+ uploaded_by TEXT NOT NULL REFERENCES users(id),
71
+ uploaded_at INTEGER NOT NULL,
72
+ updated_at INTEGER,
73
+ published_at INTEGER,
74
+ scheduled_at INTEGER,
75
+ archived_at INTEGER,
76
+ deleted_at INTEGER
77
+ );
78
+
79
+ -- Create API tokens table for programmatic access
80
+ CREATE TABLE IF NOT EXISTS api_tokens (
81
+ id TEXT PRIMARY KEY,
82
+ name TEXT NOT NULL,
83
+ token TEXT NOT NULL UNIQUE,
84
+ user_id TEXT NOT NULL REFERENCES users(id),
85
+ permissions TEXT NOT NULL, -- JSON array of permissions
86
+ expires_at INTEGER,
87
+ last_used_at INTEGER,
88
+ created_at INTEGER NOT NULL
89
+ );
90
+
91
+ -- Create workflow history table for content workflow tracking
92
+ CREATE TABLE IF NOT EXISTS workflow_history (
93
+ id TEXT PRIMARY KEY,
94
+ content_id TEXT NOT NULL REFERENCES content(id),
95
+ action TEXT NOT NULL,
96
+ from_status TEXT NOT NULL,
97
+ to_status TEXT NOT NULL,
98
+ user_id TEXT NOT NULL REFERENCES users(id),
99
+ comment TEXT,
100
+ created_at INTEGER NOT NULL
101
+ );
102
+
103
+ -- Create indexes for better performance
104
+ CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
105
+ CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
106
+ CREATE INDEX IF NOT EXISTS idx_users_role ON users(role);
107
+
108
+ CREATE INDEX IF NOT EXISTS idx_collections_name ON collections(name);
109
+ CREATE INDEX IF NOT EXISTS idx_collections_active ON collections(is_active);
110
+
111
+ CREATE INDEX IF NOT EXISTS idx_content_collection ON content(collection_id);
112
+ CREATE INDEX IF NOT EXISTS idx_content_author ON content(author_id);
113
+ CREATE INDEX IF NOT EXISTS idx_content_status ON content(status);
114
+ CREATE INDEX IF NOT EXISTS idx_content_published ON content(published_at);
115
+ CREATE INDEX IF NOT EXISTS idx_content_slug ON content(slug);
116
+
117
+ CREATE INDEX IF NOT EXISTS idx_content_versions_content ON content_versions(content_id);
118
+ CREATE INDEX IF NOT EXISTS idx_content_versions_version ON content_versions(version);
119
+
120
+ CREATE INDEX IF NOT EXISTS idx_media_folder ON media(folder);
121
+ CREATE INDEX IF NOT EXISTS idx_media_type ON media(mime_type);
122
+ CREATE INDEX IF NOT EXISTS idx_media_uploaded_by ON media(uploaded_by);
123
+ CREATE INDEX IF NOT EXISTS idx_media_uploaded_at ON media(uploaded_at);
124
+ CREATE INDEX IF NOT EXISTS idx_media_deleted ON media(deleted_at);
125
+
126
+ CREATE INDEX IF NOT EXISTS idx_api_tokens_user ON api_tokens(user_id);
127
+ CREATE INDEX IF NOT EXISTS idx_api_tokens_token ON api_tokens(token);
128
+
129
+ CREATE INDEX IF NOT EXISTS idx_workflow_history_content ON workflow_history(content_id);
130
+ CREATE INDEX IF NOT EXISTS idx_workflow_history_user ON workflow_history(user_id);
131
+
132
+ -- Insert default admin user (password: admin123)
133
+ INSERT OR IGNORE INTO users (
134
+ id, email, username, first_name, last_name, password_hash,
135
+ role, is_active, created_at, updated_at
136
+ ) VALUES (
137
+ 'admin-user-id',
138
+ 'admin@sonicjs.com',
139
+ 'admin',
140
+ 'Admin',
141
+ 'User',
142
+ 'd1c379e871838f44e21d5a55841349e50636f06df139bfef11870eec74c381db', -- SHA-256 hash of 'admin123'
143
+ 'admin',
144
+ 1,
145
+ strftime('%s', 'now') * 1000,
146
+ strftime('%s', 'now') * 1000
147
+ );
148
+
149
+ -- Insert sample collections
150
+ INSERT OR IGNORE INTO collections (
151
+ id, name, display_name, description, schema,
152
+ is_active, created_at, updated_at
153
+ ) VALUES (
154
+ 'blog-posts-collection',
155
+ 'blog_posts',
156
+ 'Blog Posts',
157
+ 'Blog post content collection',
158
+ '{"type":"object","properties":{"title":{"type":"string","title":"Title","required":true},"content":{"type":"string","title":"Content","format":"richtext"},"excerpt":{"type":"string","title":"Excerpt"},"featured_image":{"type":"string","title":"Featured Image","format":"media"},"tags":{"type":"array","title":"Tags","items":{"type":"string"}},"status":{"type":"string","title":"Status","enum":["draft","published","archived"],"default":"draft"}},"required":["title"]}',
159
+ 1,
160
+ strftime('%s', 'now') * 1000,
161
+ strftime('%s', 'now') * 1000
162
+ ),
163
+ (
164
+ 'pages-collection',
165
+ 'pages',
166
+ 'Pages',
167
+ 'Static page content collection',
168
+ '{"type":"object","properties":{"title":{"type":"string","title":"Title","required":true},"content":{"type":"string","title":"Content","format":"richtext"},"slug":{"type":"string","title":"Slug"},"meta_description":{"type":"string","title":"Meta Description"},"featured_image":{"type":"string","title":"Featured Image","format":"media"}},"required":["title"]}',
169
+ 1,
170
+ strftime('%s', 'now') * 1000,
171
+ strftime('%s', 'now') * 1000
172
+ ),
173
+ (
174
+ 'news-collection',
175
+ 'news',
176
+ 'News',
177
+ 'News article content collection',
178
+ '{"type":"object","properties":{"title":{"type":"string","title":"Title","required":true},"content":{"type":"string","title":"Content","format":"richtext"},"publish_date":{"type":"string","title":"Publish Date","format":"date"},"author":{"type":"string","title":"Author"},"category":{"type":"string","title":"Category","enum":["technology","business","general"]}},"required":["title"]}',
179
+ 1,
180
+ strftime('%s', 'now') * 1000,
181
+ strftime('%s', 'now') * 1000
182
+ );
183
+
184
+ -- Insert sample content
185
+ INSERT OR IGNORE INTO content (
186
+ id, collection_id, slug, title, data, status,
187
+ author_id, created_at, updated_at
188
+ ) VALUES (
189
+ 'welcome-blog-post',
190
+ 'blog-posts-collection',
191
+ 'welcome-to-sonicjs-ai',
192
+ 'Welcome to SonicJS AI',
193
+ '{"title":"Welcome to SonicJS AI","content":"<h1>Welcome to SonicJS AI</h1><p>This is your first blog post created with SonicJS AI, a modern headless CMS built on Cloudflare Workers.</p><h2>Features</h2><ul><li>Cloudflare-native architecture</li><li>TypeScript-first development</li><li>Hono.js framework</li><li>D1 database</li><li>R2 media storage</li><li>Edge computing</li></ul><p>Get started by exploring the admin interface and creating your own content!</p>","excerpt":"Welcome to SonicJS AI, a modern headless CMS built on Cloudflare Workers with TypeScript and Hono.js.","status":"published","tags":["welcome","cms","cloudflare"]}',
194
+ 'published',
195
+ 'admin-user-id',
196
+ strftime('%s', 'now') * 1000,
197
+ strftime('%s', 'now') * 1000
198
+ );
@@ -0,0 +1,86 @@
1
+ -- FAQ Plugin Migration (DEPRECATED - Now managed by third-party plugin)
2
+ -- Creates FAQ table for the FAQ plugin
3
+ -- NOTE: This migration is kept for historical purposes.
4
+ -- The FAQ functionality is now provided by the faq-plugin third-party plugin.
5
+
6
+ CREATE TABLE IF NOT EXISTS faqs (
7
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
8
+ question TEXT NOT NULL,
9
+ answer TEXT NOT NULL,
10
+ category TEXT,
11
+ tags TEXT,
12
+ isPublished INTEGER NOT NULL DEFAULT 1,
13
+ sortOrder INTEGER NOT NULL DEFAULT 0,
14
+ created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
15
+ updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
16
+ );
17
+
18
+ -- Create indexes for better performance
19
+ CREATE INDEX IF NOT EXISTS idx_faqs_category ON faqs(category);
20
+ CREATE INDEX IF NOT EXISTS idx_faqs_published ON faqs(isPublished);
21
+ CREATE INDEX IF NOT EXISTS idx_faqs_sort_order ON faqs(sortOrder);
22
+
23
+ -- Create trigger to update updated_at timestamp
24
+ CREATE TRIGGER IF NOT EXISTS faqs_updated_at
25
+ AFTER UPDATE ON faqs
26
+ BEGIN
27
+ UPDATE faqs SET updated_at = strftime('%s', 'now') WHERE id = NEW.id;
28
+ END;
29
+
30
+ -- Insert sample FAQ data
31
+ INSERT OR IGNORE INTO faqs (question, answer, category, tags, isPublished, sortOrder) VALUES
32
+ ('What is SonicJS AI?',
33
+ 'SonicJS AI is a modern, TypeScript-first headless CMS built for Cloudflare''s edge platform. It provides a complete content management system with admin interface, API endpoints, and plugin architecture.',
34
+ 'general',
35
+ 'sonicjs, cms, cloudflare',
36
+ 1,
37
+ 1),
38
+
39
+ ('How do I get started with SonicJS AI?',
40
+ 'To get started: 1) Clone the repository, 2) Install dependencies with npm install, 3) Set up your Cloudflare account and services, 4) Run the development server with npm run dev, 5) Access the admin interface at /admin.',
41
+ 'general',
42
+ 'getting-started, setup',
43
+ 1,
44
+ 2),
45
+
46
+ ('What technologies does SonicJS AI use?',
47
+ 'SonicJS AI is built with: TypeScript for type safety, Hono.js as the web framework, Cloudflare D1 for the database, Cloudflare R2 for media storage, Cloudflare Workers for serverless execution, and Tailwind CSS for styling.',
48
+ 'technical',
49
+ 'technology-stack, typescript, cloudflare',
50
+ 1,
51
+ 3),
52
+
53
+ ('How do I create content in SonicJS AI?',
54
+ 'Content creation is done through the admin interface. Navigate to /admin, log in with your credentials, go to Content section, select a collection, and click "New Content" to create articles, pages, or other content types.',
55
+ 'general',
56
+ 'content-creation, admin',
57
+ 1,
58
+ 4),
59
+
60
+ ('Is SonicJS AI free to use?',
61
+ 'SonicJS AI is open source and free to use. You only pay for the Cloudflare services you consume (D1 database, R2 storage, Workers execution time). Cloudflare offers generous free tiers for development and small projects.',
62
+ 'billing',
63
+ 'pricing, open-source, cloudflare',
64
+ 1,
65
+ 5),
66
+
67
+ ('How do I add custom functionality?',
68
+ 'SonicJS AI features a plugin system that allows you to extend functionality. You can create plugins using the PluginBuilder API, add custom routes, models, admin pages, and integrate with external services.',
69
+ 'technical',
70
+ 'plugins, customization, development',
71
+ 1,
72
+ 6),
73
+
74
+ ('Can I customize the admin interface?',
75
+ 'Yes! The admin interface is built with TypeScript templates and can be customized. You can modify existing templates, create new components, add custom pages, and integrate your own styling while maintaining the dark theme.',
76
+ 'technical',
77
+ 'admin-interface, customization, templates',
78
+ 1,
79
+ 7),
80
+
81
+ ('How does authentication work?',
82
+ 'SonicJS AI includes a built-in authentication system with JWT tokens, role-based access control (admin, editor, viewer), secure password hashing, and session management. Users can be managed through the admin interface.',
83
+ 'technical',
84
+ 'authentication, security, users',
85
+ 1,
86
+ 8);
@@ -0,0 +1,121 @@
1
+ -- Stage 5: Advanced Content Management enhancements
2
+ -- Add scheduling and workflow features to content table
3
+
4
+ -- Add content scheduling columns
5
+ ALTER TABLE content ADD COLUMN scheduled_publish_at INTEGER;
6
+ ALTER TABLE content ADD COLUMN scheduled_unpublish_at INTEGER;
7
+
8
+ -- Add workflow and review columns
9
+ ALTER TABLE content ADD COLUMN review_status TEXT DEFAULT 'none'; -- none, pending, approved, rejected
10
+ ALTER TABLE content ADD COLUMN reviewer_id TEXT REFERENCES users(id);
11
+ ALTER TABLE content ADD COLUMN reviewed_at INTEGER;
12
+ ALTER TABLE content ADD COLUMN review_notes TEXT;
13
+
14
+ -- Add content metadata
15
+ ALTER TABLE content ADD COLUMN meta_title TEXT;
16
+ ALTER TABLE content ADD COLUMN meta_description TEXT;
17
+ ALTER TABLE content ADD COLUMN featured_image_id TEXT REFERENCES media(id);
18
+ ALTER TABLE content ADD COLUMN content_type TEXT DEFAULT 'standard'; -- standard, template, component
19
+
20
+ -- Create content_fields table for dynamic field definitions
21
+ CREATE TABLE IF NOT EXISTS content_fields (
22
+ id TEXT PRIMARY KEY,
23
+ collection_id TEXT NOT NULL REFERENCES collections(id),
24
+ field_name TEXT NOT NULL,
25
+ field_type TEXT NOT NULL, -- text, richtext, number, boolean, date, select, media, relationship
26
+ field_label TEXT NOT NULL,
27
+ field_options TEXT, -- JSON for select options, validation rules, etc.
28
+ field_order INTEGER NOT NULL DEFAULT 0,
29
+ is_required INTEGER NOT NULL DEFAULT 0,
30
+ is_searchable INTEGER NOT NULL DEFAULT 0,
31
+ created_at INTEGER NOT NULL,
32
+ updated_at INTEGER NOT NULL,
33
+ UNIQUE(collection_id, field_name)
34
+ );
35
+
36
+ -- Create content_relationships table for content relationships
37
+ CREATE TABLE IF NOT EXISTS content_relationships (
38
+ id TEXT PRIMARY KEY,
39
+ source_content_id TEXT NOT NULL REFERENCES content(id),
40
+ target_content_id TEXT NOT NULL REFERENCES content(id),
41
+ relationship_type TEXT NOT NULL, -- references, tags, categories
42
+ created_at INTEGER NOT NULL,
43
+ UNIQUE(source_content_id, target_content_id, relationship_type)
44
+ );
45
+
46
+ -- Create workflow_templates table for reusable workflows
47
+ CREATE TABLE IF NOT EXISTS workflow_templates (
48
+ id TEXT PRIMARY KEY,
49
+ name TEXT NOT NULL,
50
+ description TEXT,
51
+ collection_id TEXT REFERENCES collections(id), -- null means applies to all collections
52
+ workflow_steps TEXT NOT NULL, -- JSON array of workflow steps
53
+ is_active INTEGER NOT NULL DEFAULT 1,
54
+ created_at INTEGER NOT NULL,
55
+ updated_at INTEGER NOT NULL
56
+ );
57
+
58
+ -- Add indexes for new columns
59
+ CREATE INDEX IF NOT EXISTS idx_content_scheduled_publish ON content(scheduled_publish_at);
60
+ CREATE INDEX IF NOT EXISTS idx_content_scheduled_unpublish ON content(scheduled_unpublish_at);
61
+ CREATE INDEX IF NOT EXISTS idx_content_review_status ON content(review_status);
62
+ CREATE INDEX IF NOT EXISTS idx_content_reviewer ON content(reviewer_id);
63
+ CREATE INDEX IF NOT EXISTS idx_content_content_type ON content(content_type);
64
+
65
+ CREATE INDEX IF NOT EXISTS idx_content_fields_collection ON content_fields(collection_id);
66
+ CREATE INDEX IF NOT EXISTS idx_content_fields_name ON content_fields(field_name);
67
+ CREATE INDEX IF NOT EXISTS idx_content_fields_type ON content_fields(field_type);
68
+ CREATE INDEX IF NOT EXISTS idx_content_fields_order ON content_fields(field_order);
69
+
70
+ CREATE INDEX IF NOT EXISTS idx_content_relationships_source ON content_relationships(source_content_id);
71
+ CREATE INDEX IF NOT EXISTS idx_content_relationships_target ON content_relationships(target_content_id);
72
+ CREATE INDEX IF NOT EXISTS idx_content_relationships_type ON content_relationships(relationship_type);
73
+
74
+ CREATE INDEX IF NOT EXISTS idx_workflow_templates_collection ON workflow_templates(collection_id);
75
+ CREATE INDEX IF NOT EXISTS idx_workflow_templates_active ON workflow_templates(is_active);
76
+
77
+ -- Insert default workflow template
78
+ INSERT OR IGNORE INTO workflow_templates (
79
+ id, name, description, workflow_steps, is_active, created_at, updated_at
80
+ ) VALUES (
81
+ 'default-content-workflow',
82
+ 'Default Content Workflow',
83
+ 'Standard content workflow: Draft → Review → Published',
84
+ '[
85
+ {"step": "draft", "name": "Draft", "description": "Content is being created", "permissions": ["author", "editor", "admin"]},
86
+ {"step": "review", "name": "Under Review", "description": "Content is pending review", "permissions": ["editor", "admin"]},
87
+ {"step": "published", "name": "Published", "description": "Content is live", "permissions": ["editor", "admin"]},
88
+ {"step": "archived", "name": "Archived", "description": "Content is archived", "permissions": ["admin"]}
89
+ ]',
90
+ 1,
91
+ strftime('%s', 'now') * 1000,
92
+ strftime('%s', 'now') * 1000
93
+ );
94
+
95
+ -- Insert enhanced field definitions for existing collections
96
+ INSERT OR IGNORE INTO content_fields (
97
+ id, collection_id, field_name, field_type, field_label, field_options, field_order, is_required, is_searchable, created_at, updated_at
98
+ ) VALUES
99
+ -- Blog Posts fields
100
+ ('blog-title-field', 'blog-posts-collection', 'title', 'text', 'Title', '{"maxLength": 200, "placeholder": "Enter blog post title"}', 1, 1, 1, strftime('%s', 'now') * 1000, strftime('%s', 'now') * 1000),
101
+ ('blog-content-field', 'blog-posts-collection', 'content', 'richtext', 'Content', '{"toolbar": "full", "height": 400}', 2, 1, 1, strftime('%s', 'now') * 1000, strftime('%s', 'now') * 1000),
102
+ ('blog-excerpt-field', 'blog-posts-collection', 'excerpt', 'text', 'Excerpt', '{"maxLength": 500, "rows": 3, "placeholder": "Brief description of the post"}', 3, 0, 1, strftime('%s', 'now') * 1000, strftime('%s', 'now') * 1000),
103
+ ('blog-tags-field', 'blog-posts-collection', 'tags', 'select', 'Tags', '{"multiple": true, "options": ["technology", "business", "tutorial", "news", "update"], "allowCustom": true}', 4, 0, 1, strftime('%s', 'now') * 1000, strftime('%s', 'now') * 1000),
104
+ ('blog-featured-image-field', 'blog-posts-collection', 'featured_image', 'media', 'Featured Image', '{"accept": "image/*", "maxSize": "5MB"}', 5, 0, 0, strftime('%s', 'now') * 1000, strftime('%s', 'now') * 1000),
105
+ ('blog-publish-date-field', 'blog-posts-collection', 'publish_date', 'date', 'Publish Date', '{"format": "YYYY-MM-DD", "defaultToday": true}', 6, 0, 0, strftime('%s', 'now') * 1000, strftime('%s', 'now') * 1000),
106
+ ('blog-featured-field', 'blog-posts-collection', 'is_featured', 'boolean', 'Featured Post', '{"default": false}', 7, 0, 0, strftime('%s', 'now') * 1000, strftime('%s', 'now') * 1000),
107
+
108
+ -- Pages fields
109
+ ('pages-title-field', 'pages-collection', 'title', 'text', 'Title', '{"maxLength": 200, "placeholder": "Enter page title"}', 1, 1, 1, strftime('%s', 'now') * 1000, strftime('%s', 'now') * 1000),
110
+ ('pages-content-field', 'pages-collection', 'content', 'richtext', 'Content', '{"toolbar": "full", "height": 500}', 2, 1, 1, strftime('%s', 'now') * 1000, strftime('%s', 'now') * 1000),
111
+ ('pages-slug-field', 'pages-collection', 'slug', 'text', 'URL Slug', '{"pattern": "^[a-z0-9-]+$", "placeholder": "url-friendly-slug"}', 3, 1, 0, strftime('%s', 'now') * 1000, strftime('%s', 'now') * 1000),
112
+ ('pages-meta-desc-field', 'pages-collection', 'meta_description', 'text', 'Meta Description', '{"maxLength": 160, "rows": 2, "placeholder": "SEO description for search engines"}', 4, 0, 0, strftime('%s', 'now') * 1000, strftime('%s', 'now') * 1000),
113
+ ('pages-template-field', 'pages-collection', 'template', 'select', 'Page Template', '{"options": ["default", "landing", "contact", "about"], "default": "default"}', 5, 0, 0, strftime('%s', 'now') * 1000, strftime('%s', 'now') * 1000),
114
+
115
+ -- News fields
116
+ ('news-title-field', 'news-collection', 'title', 'text', 'Title', '{"maxLength": 200, "placeholder": "Enter news title"}', 1, 1, 1, strftime('%s', 'now') * 1000, strftime('%s', 'now') * 1000),
117
+ ('news-content-field', 'news-collection', 'content', 'richtext', 'Content', '{"toolbar": "news", "height": 400}', 2, 1, 1, strftime('%s', 'now') * 1000, strftime('%s', 'now') * 1000),
118
+ ('news-category-field', 'news-collection', 'category', 'select', 'Category', '{"options": ["technology", "business", "politics", "sports", "entertainment", "health"], "required": true}', 3, 1, 1, strftime('%s', 'now') * 1000, strftime('%s', 'now') * 1000),
119
+ ('news-author-field', 'news-collection', 'author', 'text', 'Author', '{"placeholder": "Author name"}', 4, 1, 1, strftime('%s', 'now') * 1000, strftime('%s', 'now') * 1000),
120
+ ('news-source-field', 'news-collection', 'source', 'text', 'Source', '{"placeholder": "News source"}', 5, 0, 1, strftime('%s', 'now') * 1000, strftime('%s', 'now') * 1000),
121
+ ('news-priority-field', 'news-collection', 'priority', 'select', 'Priority', '{"options": ["low", "normal", "high", "breaking"], "default": "normal"}', 6, 0, 0, strftime('%s', 'now') * 1000, strftime('%s', 'now') * 1000);
@@ -0,0 +1,183 @@
1
+ -- Stage 6: User Management & Permissions enhancements
2
+ -- Enhanced user system with profiles, teams, permissions, and activity logging
3
+
4
+ -- Add user profile and preferences columns
5
+ ALTER TABLE users ADD COLUMN phone TEXT;
6
+ ALTER TABLE users ADD COLUMN bio TEXT;
7
+ ALTER TABLE users ADD COLUMN avatar_url TEXT;
8
+ ALTER TABLE users ADD COLUMN timezone TEXT DEFAULT 'UTC';
9
+ ALTER TABLE users ADD COLUMN language TEXT DEFAULT 'en';
10
+ ALTER TABLE users ADD COLUMN email_notifications INTEGER DEFAULT 1;
11
+ ALTER TABLE users ADD COLUMN theme TEXT DEFAULT 'dark';
12
+ ALTER TABLE users ADD COLUMN two_factor_enabled INTEGER DEFAULT 0;
13
+ ALTER TABLE users ADD COLUMN two_factor_secret TEXT;
14
+ ALTER TABLE users ADD COLUMN password_reset_token TEXT;
15
+ ALTER TABLE users ADD COLUMN password_reset_expires INTEGER;
16
+ ALTER TABLE users ADD COLUMN email_verified INTEGER DEFAULT 0;
17
+ ALTER TABLE users ADD COLUMN email_verification_token TEXT;
18
+ ALTER TABLE users ADD COLUMN invitation_token TEXT;
19
+ ALTER TABLE users ADD COLUMN invited_by TEXT REFERENCES users(id);
20
+ ALTER TABLE users ADD COLUMN invited_at INTEGER;
21
+ ALTER TABLE users ADD COLUMN accepted_invitation_at INTEGER;
22
+
23
+ -- Create teams table for team-based collaboration
24
+ CREATE TABLE IF NOT EXISTS teams (
25
+ id TEXT PRIMARY KEY,
26
+ name TEXT NOT NULL,
27
+ description TEXT,
28
+ slug TEXT NOT NULL UNIQUE,
29
+ owner_id TEXT NOT NULL REFERENCES users(id),
30
+ settings TEXT, -- JSON for team settings
31
+ is_active INTEGER NOT NULL DEFAULT 1,
32
+ created_at INTEGER NOT NULL,
33
+ updated_at INTEGER NOT NULL
34
+ );
35
+
36
+ -- Create team memberships table
37
+ CREATE TABLE IF NOT EXISTS team_memberships (
38
+ id TEXT PRIMARY KEY,
39
+ team_id TEXT NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
40
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
41
+ role TEXT NOT NULL DEFAULT 'member', -- owner, admin, editor, member, viewer
42
+ permissions TEXT, -- JSON for specific permissions
43
+ joined_at INTEGER NOT NULL,
44
+ updated_at INTEGER NOT NULL,
45
+ UNIQUE(team_id, user_id)
46
+ );
47
+
48
+ -- Create permissions table for granular access control
49
+ CREATE TABLE IF NOT EXISTS permissions (
50
+ id TEXT PRIMARY KEY,
51
+ name TEXT NOT NULL UNIQUE,
52
+ description TEXT,
53
+ category TEXT NOT NULL, -- content, users, collections, media, settings
54
+ created_at INTEGER NOT NULL
55
+ );
56
+
57
+ -- Create role permissions mapping
58
+ CREATE TABLE IF NOT EXISTS role_permissions (
59
+ id TEXT PRIMARY KEY,
60
+ role TEXT NOT NULL,
61
+ permission_id TEXT NOT NULL REFERENCES permissions(id),
62
+ created_at INTEGER NOT NULL,
63
+ UNIQUE(role, permission_id)
64
+ );
65
+
66
+ -- Create user sessions table for better session management
67
+ CREATE TABLE IF NOT EXISTS user_sessions (
68
+ id TEXT PRIMARY KEY,
69
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
70
+ token_hash TEXT NOT NULL,
71
+ ip_address TEXT,
72
+ user_agent TEXT,
73
+ is_active INTEGER NOT NULL DEFAULT 1,
74
+ expires_at INTEGER NOT NULL,
75
+ created_at INTEGER NOT NULL,
76
+ last_used_at INTEGER
77
+ );
78
+
79
+ -- Create activity log table for audit trails
80
+ CREATE TABLE IF NOT EXISTS activity_logs (
81
+ id TEXT PRIMARY KEY,
82
+ user_id TEXT REFERENCES users(id),
83
+ action TEXT NOT NULL,
84
+ resource_type TEXT, -- users, content, collections, media, etc.
85
+ resource_id TEXT,
86
+ details TEXT, -- JSON with additional details
87
+ ip_address TEXT,
88
+ user_agent TEXT,
89
+ created_at INTEGER NOT NULL
90
+ );
91
+
92
+ -- Create password history table for security
93
+ CREATE TABLE IF NOT EXISTS password_history (
94
+ id TEXT PRIMARY KEY,
95
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
96
+ password_hash TEXT NOT NULL,
97
+ created_at INTEGER NOT NULL
98
+ );
99
+
100
+ -- Insert default permissions
101
+ INSERT OR IGNORE INTO permissions (id, name, description, category, created_at) VALUES
102
+ ('perm_content_create', 'content.create', 'Create new content', 'content', strftime('%s', 'now') * 1000),
103
+ ('perm_content_read', 'content.read', 'View content', 'content', strftime('%s', 'now') * 1000),
104
+ ('perm_content_update', 'content.update', 'Edit existing content', 'content', strftime('%s', 'now') * 1000),
105
+ ('perm_content_delete', 'content.delete', 'Delete content', 'content', strftime('%s', 'now') * 1000),
106
+ ('perm_content_publish', 'content.publish', 'Publish/unpublish content', 'content', strftime('%s', 'now') * 1000),
107
+
108
+ ('perm_collections_create', 'collections.create', 'Create new collections', 'collections', strftime('%s', 'now') * 1000),
109
+ ('perm_collections_read', 'collections.read', 'View collections', 'collections', strftime('%s', 'now') * 1000),
110
+ ('perm_collections_update', 'collections.update', 'Edit collections', 'collections', strftime('%s', 'now') * 1000),
111
+ ('perm_collections_delete', 'collections.delete', 'Delete collections', 'collections', strftime('%s', 'now') * 1000),
112
+ ('perm_collections_fields', 'collections.fields', 'Manage collection fields', 'collections', strftime('%s', 'now') * 1000),
113
+
114
+ ('perm_media_upload', 'media.upload', 'Upload media files', 'media', strftime('%s', 'now') * 1000),
115
+ ('perm_media_read', 'media.read', 'View media files', 'media', strftime('%s', 'now') * 1000),
116
+ ('perm_media_update', 'media.update', 'Edit media metadata', 'media', strftime('%s', 'now') * 1000),
117
+ ('perm_media_delete', 'media.delete', 'Delete media files', 'media', strftime('%s', 'now') * 1000),
118
+
119
+ ('perm_users_create', 'users.create', 'Invite new users', 'users', strftime('%s', 'now') * 1000),
120
+ ('perm_users_read', 'users.read', 'View user profiles', 'users', strftime('%s', 'now') * 1000),
121
+ ('perm_users_update', 'users.update', 'Edit user profiles', 'users', strftime('%s', 'now') * 1000),
122
+ ('perm_users_delete', 'users.delete', 'Deactivate users', 'users', strftime('%s', 'now') * 1000),
123
+ ('perm_users_roles', 'users.roles', 'Manage user roles', 'users', strftime('%s', 'now') * 1000),
124
+
125
+ ('perm_settings_read', 'settings.read', 'View system settings', 'settings', strftime('%s', 'now') * 1000),
126
+ ('perm_settings_update', 'settings.update', 'Modify system settings', 'settings', strftime('%s', 'now') * 1000),
127
+ ('perm_activity_read', 'activity.read', 'View activity logs', 'settings', strftime('%s', 'now') * 1000);
128
+
129
+ -- Assign permissions to default roles
130
+ INSERT OR IGNORE INTO role_permissions (id, role, permission_id, created_at) VALUES
131
+ -- Admin has all permissions
132
+ ('rp_admin_content_create', 'admin', 'perm_content_create', strftime('%s', 'now') * 1000),
133
+ ('rp_admin_content_read', 'admin', 'perm_content_read', strftime('%s', 'now') * 1000),
134
+ ('rp_admin_content_update', 'admin', 'perm_content_update', strftime('%s', 'now') * 1000),
135
+ ('rp_admin_content_delete', 'admin', 'perm_content_delete', strftime('%s', 'now') * 1000),
136
+ ('rp_admin_content_publish', 'admin', 'perm_content_publish', strftime('%s', 'now') * 1000),
137
+ ('rp_admin_collections_create', 'admin', 'perm_collections_create', strftime('%s', 'now') * 1000),
138
+ ('rp_admin_collections_read', 'admin', 'perm_collections_read', strftime('%s', 'now') * 1000),
139
+ ('rp_admin_collections_update', 'admin', 'perm_collections_update', strftime('%s', 'now') * 1000),
140
+ ('rp_admin_collections_delete', 'admin', 'perm_collections_delete', strftime('%s', 'now') * 1000),
141
+ ('rp_admin_collections_fields', 'admin', 'perm_collections_fields', strftime('%s', 'now') * 1000),
142
+ ('rp_admin_media_upload', 'admin', 'perm_media_upload', strftime('%s', 'now') * 1000),
143
+ ('rp_admin_media_read', 'admin', 'perm_media_read', strftime('%s', 'now') * 1000),
144
+ ('rp_admin_media_update', 'admin', 'perm_media_update', strftime('%s', 'now') * 1000),
145
+ ('rp_admin_media_delete', 'admin', 'perm_media_delete', strftime('%s', 'now') * 1000),
146
+ ('rp_admin_users_create', 'admin', 'perm_users_create', strftime('%s', 'now') * 1000),
147
+ ('rp_admin_users_read', 'admin', 'perm_users_read', strftime('%s', 'now') * 1000),
148
+ ('rp_admin_users_update', 'admin', 'perm_users_update', strftime('%s', 'now') * 1000),
149
+ ('rp_admin_users_delete', 'admin', 'perm_users_delete', strftime('%s', 'now') * 1000),
150
+ ('rp_admin_users_roles', 'admin', 'perm_users_roles', strftime('%s', 'now') * 1000),
151
+ ('rp_admin_settings_read', 'admin', 'perm_settings_read', strftime('%s', 'now') * 1000),
152
+ ('rp_admin_settings_update', 'admin', 'perm_settings_update', strftime('%s', 'now') * 1000),
153
+ ('rp_admin_activity_read', 'admin', 'perm_activity_read', strftime('%s', 'now') * 1000),
154
+
155
+ -- Editor permissions
156
+ ('rp_editor_content_create', 'editor', 'perm_content_create', strftime('%s', 'now') * 1000),
157
+ ('rp_editor_content_read', 'editor', 'perm_content_read', strftime('%s', 'now') * 1000),
158
+ ('rp_editor_content_update', 'editor', 'perm_content_update', strftime('%s', 'now') * 1000),
159
+ ('rp_editor_content_publish', 'editor', 'perm_content_publish', strftime('%s', 'now') * 1000),
160
+ ('rp_editor_collections_read', 'editor', 'perm_collections_read', strftime('%s', 'now') * 1000),
161
+ ('rp_editor_media_upload', 'editor', 'perm_media_upload', strftime('%s', 'now') * 1000),
162
+ ('rp_editor_media_read', 'editor', 'perm_media_read', strftime('%s', 'now') * 1000),
163
+ ('rp_editor_media_update', 'editor', 'perm_media_update', strftime('%s', 'now') * 1000),
164
+ ('rp_editor_users_read', 'editor', 'perm_users_read', strftime('%s', 'now') * 1000),
165
+
166
+ -- Viewer permissions
167
+ ('rp_viewer_content_read', 'viewer', 'perm_content_read', strftime('%s', 'now') * 1000),
168
+ ('rp_viewer_collections_read', 'viewer', 'perm_collections_read', strftime('%s', 'now') * 1000),
169
+ ('rp_viewer_media_read', 'viewer', 'perm_media_read', strftime('%s', 'now') * 1000),
170
+ ('rp_viewer_users_read', 'viewer', 'perm_users_read', strftime('%s', 'now') * 1000);
171
+
172
+ -- Create indexes for performance
173
+ CREATE INDEX IF NOT EXISTS idx_team_memberships_team_id ON team_memberships(team_id);
174
+ CREATE INDEX IF NOT EXISTS idx_team_memberships_user_id ON team_memberships(user_id);
175
+ CREATE INDEX IF NOT EXISTS idx_user_sessions_user_id ON user_sessions(user_id);
176
+ CREATE INDEX IF NOT EXISTS idx_user_sessions_token_hash ON user_sessions(token_hash);
177
+ CREATE INDEX IF NOT EXISTS idx_activity_logs_user_id ON activity_logs(user_id);
178
+ CREATE INDEX IF NOT EXISTS idx_activity_logs_created_at ON activity_logs(created_at);
179
+ CREATE INDEX IF NOT EXISTS idx_activity_logs_resource ON activity_logs(resource_type, resource_id);
180
+ CREATE INDEX IF NOT EXISTS idx_password_history_user_id ON password_history(user_id);
181
+ CREATE INDEX IF NOT EXISTS idx_users_email_verification_token ON users(email_verification_token);
182
+ CREATE INDEX IF NOT EXISTS idx_users_password_reset_token ON users(password_reset_token);
183
+ CREATE INDEX IF NOT EXISTS idx_users_invitation_token ON users(invitation_token);