domma-cms 0.1.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.
Files changed (135) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +469 -0
  3. package/admin/css/admin.css +1123 -0
  4. package/admin/index.html +72 -0
  5. package/admin/js/api.js +210 -0
  6. package/admin/js/app.js +270 -0
  7. package/admin/js/config/sidebar-config.js +107 -0
  8. package/admin/js/lib/card.js +63 -0
  9. package/admin/js/lib/image-editor.js +869 -0
  10. package/admin/js/lib/markdown-toolbar.js +421 -0
  11. package/admin/js/templates/dashboard.html +50 -0
  12. package/admin/js/templates/documentation.html +237 -0
  13. package/admin/js/templates/layouts.html +11 -0
  14. package/admin/js/templates/login.html +58 -0
  15. package/admin/js/templates/media.html +16 -0
  16. package/admin/js/templates/navigation.html +50 -0
  17. package/admin/js/templates/page-editor.html +126 -0
  18. package/admin/js/templates/pages.html +18 -0
  19. package/admin/js/templates/plugins.html +12 -0
  20. package/admin/js/templates/settings.html +190 -0
  21. package/admin/js/templates/tutorials.html +233 -0
  22. package/admin/js/templates/user-editor.html +12 -0
  23. package/admin/js/templates/users.html +10 -0
  24. package/admin/js/views/dashboard.js +48 -0
  25. package/admin/js/views/documentation.js +12 -0
  26. package/admin/js/views/index.js +33 -0
  27. package/admin/js/views/layouts.js +49 -0
  28. package/admin/js/views/login.js +254 -0
  29. package/admin/js/views/media.js +240 -0
  30. package/admin/js/views/navigation.js +152 -0
  31. package/admin/js/views/page-editor.js +479 -0
  32. package/admin/js/views/pages.js +64 -0
  33. package/admin/js/views/plugins.js +100 -0
  34. package/admin/js/views/settings.js +64 -0
  35. package/admin/js/views/tutorials.js +12 -0
  36. package/admin/js/views/user-editor.js +88 -0
  37. package/admin/js/views/users.js +73 -0
  38. package/bin/cli.js +334 -0
  39. package/config/auth.json +20 -0
  40. package/config/content.json +10 -0
  41. package/config/navigation.json +63 -0
  42. package/config/plugins.json +47 -0
  43. package/config/presets.json +34 -0
  44. package/config/server.json +6 -0
  45. package/config/site.json +33 -0
  46. package/package.json +67 -0
  47. package/plugins/back-to-top/admin/templates/back-to-top-settings.html +55 -0
  48. package/plugins/back-to-top/admin/views/back-to-top-settings.js +44 -0
  49. package/plugins/back-to-top/config.js +10 -0
  50. package/plugins/back-to-top/plugin.js +24 -0
  51. package/plugins/back-to-top/plugin.json +36 -0
  52. package/plugins/back-to-top/public/inject-body.html +105 -0
  53. package/plugins/cookie-consent/admin/templates/cookie-consent-settings.html +113 -0
  54. package/plugins/cookie-consent/admin/views/cookie-consent-settings.js +73 -0
  55. package/plugins/cookie-consent/config.js +30 -0
  56. package/plugins/cookie-consent/plugin.js +24 -0
  57. package/plugins/cookie-consent/plugin.json +36 -0
  58. package/plugins/cookie-consent/public/inject-body.html +69 -0
  59. package/plugins/custom-css/admin/templates/custom-css.html +17 -0
  60. package/plugins/custom-css/admin/views/custom-css.js +35 -0
  61. package/plugins/custom-css/config.js +1 -0
  62. package/plugins/custom-css/data/custom.css +0 -0
  63. package/plugins/custom-css/plugin.js +63 -0
  64. package/plugins/custom-css/plugin.json +32 -0
  65. package/plugins/custom-css/public/inject-head.html +1 -0
  66. package/plugins/domma-effects/admin/templates/domma-effects.html +488 -0
  67. package/plugins/domma-effects/admin/views/domma-effects.js +56 -0
  68. package/plugins/domma-effects/config.js +9 -0
  69. package/plugins/domma-effects/plugin.js +22 -0
  70. package/plugins/domma-effects/plugin.json +36 -0
  71. package/plugins/domma-effects/public/celebrations/core/canvas.js +111 -0
  72. package/plugins/domma-effects/public/celebrations/core/particles.js +144 -0
  73. package/plugins/domma-effects/public/celebrations/core/physics.js +166 -0
  74. package/plugins/domma-effects/public/celebrations/index.js +535 -0
  75. package/plugins/domma-effects/public/celebrations/themes/christmas.js +1805 -0
  76. package/plugins/domma-effects/public/celebrations/themes/guy-fawkes.js +1477 -0
  77. package/plugins/domma-effects/public/celebrations/themes/halloween.js +1837 -0
  78. package/plugins/domma-effects/public/celebrations/themes/st-andrews.js +1175 -0
  79. package/plugins/domma-effects/public/celebrations/themes/st-davids.js +1258 -0
  80. package/plugins/domma-effects/public/celebrations/themes/st-georges.js +1754 -0
  81. package/plugins/domma-effects/public/celebrations/themes/st-patricks.js +1290 -0
  82. package/plugins/domma-effects/public/celebrations/themes/valentines.js +1361 -0
  83. package/plugins/domma-effects/public/inject-body.html +268 -0
  84. package/plugins/example-analytics/admin/templates/analytics.html +10 -0
  85. package/plugins/example-analytics/admin/views/analytics.js +51 -0
  86. package/plugins/example-analytics/config.js +6 -0
  87. package/plugins/example-analytics/plugin.js +58 -0
  88. package/plugins/example-analytics/plugin.json +27 -0
  89. package/plugins/example-analytics/public/inject-body.html +13 -0
  90. package/plugins/example-analytics/public/inject-head.html +1 -0
  91. package/plugins/example-analytics/stats.json +1 -0
  92. package/plugins/form-builder/admin/templates/form-editor.html +158 -0
  93. package/plugins/form-builder/admin/templates/form-settings.html +29 -0
  94. package/plugins/form-builder/admin/templates/form-submissions.html +30 -0
  95. package/plugins/form-builder/admin/templates/forms-list.html +17 -0
  96. package/plugins/form-builder/admin/views/form-editor.js +817 -0
  97. package/plugins/form-builder/admin/views/form-settings.js +38 -0
  98. package/plugins/form-builder/admin/views/form-submissions.js +295 -0
  99. package/plugins/form-builder/admin/views/forms-list.js +164 -0
  100. package/plugins/form-builder/config.js +9 -0
  101. package/plugins/form-builder/data/forms/contact-details.json +63 -0
  102. package/plugins/form-builder/data/forms/contact.json +52 -0
  103. package/plugins/form-builder/data/submissions/contact-details.json +1 -0
  104. package/plugins/form-builder/data/submissions/contact.json +14 -0
  105. package/plugins/form-builder/email.js +103 -0
  106. package/plugins/form-builder/plugin.js +454 -0
  107. package/plugins/form-builder/plugin.json +56 -0
  108. package/plugins/form-builder/public/inject-body.html +270 -0
  109. package/plugins/form-builder/public/inject-head.html +42 -0
  110. package/public/css/site.css +189 -0
  111. package/public/js/site.js +109 -0
  112. package/scripts/copy-domma.js +48 -0
  113. package/scripts/fresh.js +41 -0
  114. package/scripts/reset.js +124 -0
  115. package/scripts/seed.js +666 -0
  116. package/scripts/setup.js +263 -0
  117. package/server/config.js +56 -0
  118. package/server/middleware/auth.js +97 -0
  119. package/server/routes/api/auth.js +116 -0
  120. package/server/routes/api/layouts.js +25 -0
  121. package/server/routes/api/media.js +93 -0
  122. package/server/routes/api/navigation.js +37 -0
  123. package/server/routes/api/pages.js +118 -0
  124. package/server/routes/api/plugins.js +46 -0
  125. package/server/routes/api/settings.js +25 -0
  126. package/server/routes/api/users.js +110 -0
  127. package/server/routes/public.js +108 -0
  128. package/server/server.js +169 -0
  129. package/server/services/content.js +298 -0
  130. package/server/services/images.js +334 -0
  131. package/server/services/markdown.js +297 -0
  132. package/server/services/plugins.js +246 -0
  133. package/server/services/renderer.js +80 -0
  134. package/server/services/users.js +212 -0
  135. package/server/templates/page.html +78 -0
@@ -0,0 +1,212 @@
1
+ /**
2
+ * User Storage Service
3
+ * File-based CRUD — one JSON file per user in content/users/{id}.json.
4
+ * Mirrors the page-per-file pattern from the content service.
5
+ */
6
+ import fs from 'fs/promises';
7
+ import path from 'path';
8
+ import bcrypt from 'bcryptjs';
9
+ import { v4 as uuidv4 } from 'uuid';
10
+ import { config } from '../config.js';
11
+
12
+ const USERS_DIR = path.resolve(config.content.usersDir);
13
+
14
+ async function ensureDir() {
15
+ await fs.mkdir(USERS_DIR, { recursive: true });
16
+ }
17
+
18
+ /**
19
+ * Strip the password field from a user object before returning it to callers.
20
+ *
21
+ * @param {object} user
22
+ * @returns {object}
23
+ */
24
+ function stripPassword(user) {
25
+ const { password, ...safe } = user;
26
+ return safe;
27
+ }
28
+
29
+ /**
30
+ * Read a user file by ID.
31
+ *
32
+ * @param {string} id
33
+ * @returns {Promise<object>} Full user object including password hash
34
+ */
35
+ async function readUserFile(id) {
36
+ const filePath = path.join(USERS_DIR, `${id}.json`);
37
+ const raw = await fs.readFile(filePath, 'utf8');
38
+ return JSON.parse(raw);
39
+ }
40
+
41
+ /**
42
+ * Write a user file.
43
+ *
44
+ * @param {object} user
45
+ * @returns {Promise<void>}
46
+ */
47
+ async function writeUserFile(user) {
48
+ await ensureDir();
49
+ const filePath = path.join(USERS_DIR, `${user.id}.json`);
50
+ await fs.writeFile(filePath, JSON.stringify(user, null, 2) + '\n', 'utf8');
51
+ }
52
+
53
+ /**
54
+ * List all users (password stripped).
55
+ *
56
+ * @returns {Promise<object[]>}
57
+ */
58
+ export async function listUsers() {
59
+ await ensureDir();
60
+ let files;
61
+ try {
62
+ files = await fs.readdir(USERS_DIR);
63
+ } catch {
64
+ return [];
65
+ }
66
+ const jsonFiles = files.filter(f => f.endsWith('.json'));
67
+ const users = await Promise.all(jsonFiles.map(async (file) => {
68
+ const raw = await fs.readFile(path.join(USERS_DIR, file), 'utf8');
69
+ return stripPassword(JSON.parse(raw));
70
+ }));
71
+ return users.sort((a, b) => a.name.localeCompare(b.name));
72
+ }
73
+
74
+ /**
75
+ * Get a single user by ID (password stripped).
76
+ *
77
+ * @param {string} id
78
+ * @returns {Promise<object|null>}
79
+ */
80
+ export async function getUserById(id) {
81
+ try {
82
+ return stripPassword(await readUserFile(id));
83
+ } catch {
84
+ return null;
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Get a user by email address — includes password hash (needed for login).
90
+ *
91
+ * @param {string} email
92
+ * @returns {Promise<object|null>}
93
+ */
94
+ export async function getUserByEmail(email) {
95
+ await ensureDir();
96
+ let files;
97
+ try {
98
+ files = await fs.readdir(USERS_DIR);
99
+ } catch {
100
+ return null;
101
+ }
102
+ for (const file of files.filter(f => f.endsWith('.json'))) {
103
+ const raw = await fs.readFile(path.join(USERS_DIR, file), 'utf8');
104
+ const user = JSON.parse(raw);
105
+ if (user.email.toLowerCase() === email.toLowerCase()) return user;
106
+ }
107
+ return null;
108
+ }
109
+
110
+ /**
111
+ * Create a new user.
112
+ *
113
+ * @param {object} data - { name, email, password, role }
114
+ * @returns {Promise<object>} Created user (password stripped)
115
+ */
116
+ export async function createUser({ name, email, password, role = 'editor' }) {
117
+ const existing = await getUserByEmail(email);
118
+ if (existing) throw new Error('A user with that email already exists');
119
+
120
+ const hash = await bcrypt.hash(password, config.auth.bcryptRounds);
121
+ const now = new Date().toISOString();
122
+ const user = {
123
+ id: uuidv4(),
124
+ email: email.toLowerCase().trim(),
125
+ name: name.trim(),
126
+ password: hash,
127
+ role,
128
+ isActive: true,
129
+ createdAt: now,
130
+ updatedAt: now,
131
+ lastLogin: null
132
+ };
133
+
134
+ await writeUserFile(user);
135
+ return stripPassword(user);
136
+ }
137
+
138
+ /**
139
+ * Update an existing user.
140
+ * Pass a new `password` field to re-hash; omit to keep the existing hash.
141
+ *
142
+ * @param {string} id
143
+ * @param {object} updates
144
+ * @returns {Promise<object>} Updated user (password stripped)
145
+ */
146
+ export async function updateUser(id, updates) {
147
+ const user = await readUserFile(id);
148
+ if (!user) throw new Error('User not found');
149
+
150
+ if (updates.password) {
151
+ updates.password = await bcrypt.hash(updates.password, config.auth.bcryptRounds);
152
+ }
153
+
154
+ const updated = {
155
+ ...user,
156
+ ...updates,
157
+ id: user.id,
158
+ updatedAt: new Date().toISOString()
159
+ };
160
+
161
+ await writeUserFile(updated);
162
+ return stripPassword(updated);
163
+ }
164
+
165
+ /**
166
+ * Delete a user file.
167
+ *
168
+ * @param {string} id
169
+ * @returns {Promise<void>}
170
+ */
171
+ export async function deleteUser(id) {
172
+ const filePath = path.join(USERS_DIR, `${id}.json`);
173
+ await fs.unlink(filePath);
174
+ }
175
+
176
+ /**
177
+ * Count total users.
178
+ *
179
+ * @returns {Promise<number>}
180
+ */
181
+ export async function countUsers() {
182
+ await ensureDir();
183
+ try {
184
+ const files = await fs.readdir(USERS_DIR);
185
+ return files.filter(f => f.endsWith('.json')).length;
186
+ } catch {
187
+ return 0;
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Validate a plain-text password against a bcrypt hash.
193
+ *
194
+ * @param {string} plain
195
+ * @param {string} hash
196
+ * @returns {Promise<boolean>}
197
+ */
198
+ export async function validatePassword(plain, hash) {
199
+ return bcrypt.compare(plain, hash);
200
+ }
201
+
202
+ /**
203
+ * Record the current timestamp as the user's last login.
204
+ *
205
+ * @param {string} id
206
+ * @returns {Promise<void>}
207
+ */
208
+ export async function touchLastLogin(id) {
209
+ const user = await readUserFile(id);
210
+ user.lastLogin = new Date().toISOString();
211
+ await writeUserFile(user);
212
+ }
@@ -0,0 +1,78 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en-GB">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>{{seoTitle}}</title>
7
+ <meta name="description" content="{{seoDescription}}">
8
+ {{#if ogImage}}<meta property="og:image" content="{{ogImage}}">{{/if}}
9
+
10
+ <!-- Fonts -->
11
+ <link rel="preconnect" href="https://fonts.googleapis.com">
12
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
13
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,300;0,400;0,500;0,700;1,400&display=swap">
14
+
15
+ <!-- DommaJS CSS -->
16
+ <link rel="stylesheet" href="/dist/domma/domma.css">
17
+ <link rel="stylesheet" href="/dist/domma/grid.css">
18
+ <link rel="stylesheet" href="/dist/domma/elements.css">
19
+ <link rel="stylesheet" href="/dist/domma/themes/domma-themes.css">
20
+
21
+ <!-- Site CSS -->
22
+ <link rel="stylesheet" href="/public/css/site.css">
23
+
24
+ <!-- Plugin head injection -->
25
+ {{headInject}}
26
+
27
+ <!-- Late head injection — custom CSS always loads last so it can override everything -->
28
+ {{headInjectLate}}
29
+ </head>
30
+ <body class="dm-cloaked dm-theme-{{theme}}" data-layout="{{layout}}">
31
+
32
+ {{#if showNavbar}}
33
+ <nav id="site-navbar"></nav>
34
+ {{/if}}
35
+
36
+ <main class="site-main {{#if showSidebar}}with-sidebar{{/if}}">
37
+ {{#if showSidebar}}
38
+ <aside id="site-sidebar" class="site-sidebar"></aside>
39
+ {{/if}}
40
+
41
+ <article class="site-content">
42
+ <div class="container">
43
+ <div class="page-body">
44
+ {{html}}
45
+ </div>
46
+ </div>
47
+ </article>
48
+ </main>
49
+
50
+ {{#if showFooter}}
51
+ <footer id="site-footer" class="page-footer"></footer>
52
+ {{/if}}
53
+
54
+ <!-- DOMPurify - must load before DommaJS -->
55
+ <script src="https://cdn.jsdelivr.net/npm/dompurify@3/dist/purify.min.js"></script>
56
+
57
+ <!-- DommaJS -->
58
+ <script src="/dist/domma/domma.min.js"></script>
59
+
60
+ <!-- Initialise DommaJS before module loads -->
61
+ <script>
62
+ if (window.Domma && typeof window.Domma.init === 'function') {
63
+ window.Domma.init();
64
+ }
65
+ if (window.Domma && window.Domma.theme) {
66
+ window.Domma.theme.init({ theme: '{{theme}}', persist: false });
67
+ }
68
+ window.__CMS_NAV__ = {{navJson}};
69
+ window.__CMS_SITE__ = {{siteJson}};
70
+ </script>
71
+
72
+ <!-- Site initialisation -->
73
+ <script src="/public/js/site.js" type="module"></script>
74
+
75
+ <!-- Plugin body-end injection -->
76
+ {{bodyEndInject}}
77
+ </body>
78
+ </html>