radix-cms 0.1.0 → 1.0.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 (59) hide show
  1. package/.env.example +17 -0
  2. package/CHANGELOG.md +10 -0
  3. package/CONTRIBUTING.md +40 -0
  4. package/LICENSE +197 -0
  5. package/NOTICE +3 -0
  6. package/README.md +126 -56
  7. package/SECURITY.md +22 -0
  8. package/dist/app.js +80 -0
  9. package/dist/config/db.js +66 -0
  10. package/dist/init/dbInit.js +132 -0
  11. package/dist/init/dbInit2.js +234 -0
  12. package/dist/middleware/auth.js +48 -0
  13. package/dist/plugins/my-seo-plugin/index.js +29 -0
  14. package/dist/routes/admin.js +808 -0
  15. package/dist/routes/index.js +218 -0
  16. package/dist/sql/radix.sql +118 -0
  17. package/dist/types/plugin.js +10 -0
  18. package/dist/utils/PluginManager.js +112 -0
  19. package/dist/utils/settings.js +80 -0
  20. package/dist/utils/themeScanner.js +33 -0
  21. package/dist/utils/versionCheck.js +74 -0
  22. package/docs/CMS_CODE_GUIDE.md +372 -0
  23. package/docs/CMS_USER_GUIDE.md +344 -0
  24. package/docs/Publishing Guidelines.txt +14 -0
  25. package/package.json +62 -6
  26. package/public/css/admin.css +178 -0
  27. package/public/css/fallback.css +53 -0
  28. package/public/hello.html +1 -0
  29. package/views/admin/create-page.ejs +82 -0
  30. package/views/admin/dashboard.ejs +44 -0
  31. package/views/admin/edit-page.ejs +87 -0
  32. package/views/admin/edit-user.ejs +45 -0
  33. package/views/admin/login.ejs +39 -0
  34. package/views/admin/media.ejs +79 -0
  35. package/views/admin/pages.ejs +82 -0
  36. package/views/admin/partials/footer.ejs +4 -0
  37. package/views/admin/partials/header.ejs +57 -0
  38. package/views/admin/settings.ejs +91 -0
  39. package/views/admin/setup-admin.ejs +69 -0
  40. package/views/admin/setup-db.ejs +112 -0
  41. package/views/admin/themes.ejs +24 -0
  42. package/views/admin/updates.ejs +79 -0
  43. package/views/admin/users.ejs +107 -0
  44. package/views/defaults/404.ejs +20 -0
  45. package/views/defaults/500.ejs +20 -0
  46. package/views/layouts/admin-layout.ejs +18 -0
  47. package/views/pages/gallery.ejs +5 -0
  48. package/views/pages/hello.ejs +4 -0
  49. package/views/pages/services.ejs +3 -0
  50. package/views/themes/default/assets/css/style.css +199 -0
  51. package/views/themes/default/assets/js/main.js +8 -0
  52. package/views/themes/default/index.ejs +55 -0
  53. package/views/themes/landing/assets/css/style.css +6 -0
  54. package/views/themes/landing/assets/js/main.js +8 -0
  55. package/views/themes/landing/index.ejs +26 -0
  56. package/views/themes/test/default.ejs +0 -0
  57. package/views/themes/test/full-width.ejs +0 -0
  58. package/views/themes/test/landing-page.ejs +0 -0
  59. package/index.js +0 -15
@@ -0,0 +1,218 @@
1
+ "use strict";
2
+ /**************************************************************************************************
3
+ * RadixCMS
4
+ *
5
+ * DESCRIPTION: public page routing and theme rendering.
6
+ *
7
+ * Copyright (C) 2026 Ultra Spark Software <salve@ultraspark.net>
8
+ * SPDX-License-Identifier: GPL-3.0-or-later
9
+ **************************************************************************************************/
10
+ var __importDefault = (this && this.__importDefault) || function (mod) {
11
+ return (mod && mod.__esModule) ? mod : { "default": mod };
12
+ };
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ const express_1 = require("express");
15
+ const path_1 = __importDefault(require("path"));
16
+ const fs_1 = __importDefault(require("fs"));
17
+ const ejs_1 = __importDefault(require("ejs"));
18
+ const db_1 = __importDefault(require("../config/db")); // Adjust path if your database module lives elsewhere
19
+ const router = (0, express_1.Router)();
20
+ // Hierarchy mapping for page access checks
21
+ const ROLE_HIERARCHY = {
22
+ 'Administrator': 5,
23
+ 'Manager': 4,
24
+ 'Editor': 3,
25
+ 'Registered': 2,
26
+ 'Unregistered': 1
27
+ };
28
+ /*
29
+ * resolveThemePath
30
+ *
31
+ * PURPOSE:
32
+ * Resolves a theme directory or legacy top-level theme file and falls back to
33
+ * the default theme when the requested template is unavailable.
34
+ *
35
+ * PARAMETERS:
36
+ * templateName (string): Requested theme or template name.
37
+ *
38
+ * RETURNS:
39
+ * Returns the EJS view path and the selected theme name.
40
+ */
41
+ function resolveThemePath(templateName) {
42
+ const sanitizedName = templateName?.trim().toLowerCase().replace(/[^a-z0-9_-]/g, '') || 'default';
43
+ const themesDir = path_1.default.join(__dirname, '../../views/themes');
44
+ const themeDirectory = path_1.default.join(themesDir, sanitizedName);
45
+ const themeFile = path_1.default.join(themesDir, `${sanitizedName}.ejs`);
46
+ if (fs_1.default.existsSync(path_1.default.join(themeDirectory, 'index.ejs'))) {
47
+ return {
48
+ view: `themes/${sanitizedName}/index`,
49
+ currentTheme: sanitizedName
50
+ };
51
+ }
52
+ if (fs_1.default.existsSync(themeFile)) {
53
+ return {
54
+ view: `themes/${sanitizedName}`,
55
+ currentTheme: sanitizedName
56
+ };
57
+ }
58
+ return {
59
+ view: 'themes/default/index',
60
+ currentTheme: 'default'
61
+ };
62
+ }
63
+ /*
64
+ * getStaticPageTheme
65
+ *
66
+ * PURPOSE:
67
+ * Reads an optional theme directive from a static EJS page.
68
+ *
69
+ * PARAMETERS:
70
+ * staticPageSource (string): Static page source to inspect.
71
+ *
72
+ * RETURNS:
73
+ * Returns the requested theme name, or undefined when no directive exists.
74
+ */
75
+ function getStaticPageTheme(staticPageSource) {
76
+ const directive = staticPageSource.match(/<%#\s*(?:theme|layout)\s*:\s*([a-z0-9_-]+)\s*%>/i);
77
+ return directive?.[1];
78
+ }
79
+ /*
80
+ * getStaticPageMinimumRole
81
+ *
82
+ * PURPOSE:
83
+ * Reads and normalizes the minimum role directive from a static EJS page.
84
+ *
85
+ * PARAMETERS:
86
+ * staticPageSource (string): Static page source to inspect.
87
+ *
88
+ * RETURNS:
89
+ * Returns the minimum role or Unregistered when no directive exists.
90
+ */
91
+ function getStaticPageMinimumRole(staticPageSource) {
92
+ const directive = staticPageSource.match(/<%#\s*minRole\s*:\s*(Administrator|Manager|Editor|Registered|Unregistered)\s*%>/i);
93
+ const role = directive?.[1];
94
+ return Object.keys(ROLE_HIERARCHY).find(roleName => roleName.toLowerCase() === role?.toLowerCase()) || 'Unregistered';
95
+ }
96
+ /*
97
+ * renderPageBySlug
98
+ *
99
+ * PURPOSE:
100
+ * Loads a page by slug, checks publication and role restrictions, and renders
101
+ * the selected static or database-backed theme.
102
+ *
103
+ * PARAMETERS:
104
+ * slug (string): Page slug used for lookup.
105
+ * req (Request): Express request containing session information.
106
+ * res (Response): Express response used to render or redirect.
107
+ *
108
+ * RETURNS:
109
+ * Returns a promise that resolves after the response has been handled.
110
+ */
111
+ async function renderPageBySlug(slug, req, res) {
112
+ try {
113
+ const [pages] = await db_1.default.execute('SELECT * FROM pages WHERE slug = ?', [slug]);
114
+ const page = pages[0];
115
+ const user = req.session?.user;
116
+ // Static page files provide content, while a matching DB row can select its theme.
117
+ const staticPagePath = path_1.default.join(__dirname, '../../views/pages', `${slug}.ejs`);
118
+ if (fs_1.default.existsSync(staticPagePath)) {
119
+ const staticPageSource = await fs_1.default.promises.readFile(staticPagePath, 'utf8');
120
+ const userRole = user?.role || 'Unregistered';
121
+ const requiredRole = getStaticPageMinimumRole(staticPageSource);
122
+ const userLevel = ROLE_HIERARCHY[userRole] || ROLE_HIERARCHY['Unregistered'];
123
+ const requiredLevel = ROLE_HIERARCHY[requiredRole] || ROLE_HIERARCHY['Unregistered'];
124
+ if (userLevel < requiredLevel) {
125
+ if (!user) {
126
+ res.redirect(`/admin/login?redirect=/${slug}`);
127
+ }
128
+ else {
129
+ res.status(403).render('defaults/403', {
130
+ message: 'Insufficient permissions to view this page.'
131
+ });
132
+ }
133
+ return;
134
+ }
135
+ const content = await ejs_1.default.renderFile(staticPagePath, {
136
+ title: page?.title || slug.charAt(0).toUpperCase() + slug.slice(1),
137
+ page,
138
+ user
139
+ });
140
+ const staticTheme = getStaticPageTheme(staticPageSource);
141
+ const { view: themeView, currentTheme } = resolveThemePath(staticTheme || page?.template_name);
142
+ res.render(themeView, {
143
+ ...page,
144
+ page,
145
+ user,
146
+ currentTheme,
147
+ title: page?.title || slug.charAt(0).toUpperCase() + slug.slice(1),
148
+ content,
149
+ siteName: process.env.APP_NAME || 'Radix',
150
+ appVersion: process.env.APP_VERSION || '0.11.0'
151
+ });
152
+ return;
153
+ }
154
+ if (pages.length === 0) {
155
+ res.status(404).render('defaults/404');
156
+ return;
157
+ }
158
+ const userRole = user ? user.role : 'Unregistered';
159
+ // 1. Status & Scheduled Publish Check
160
+ const isPublished = page.status === 'Published';
161
+ const isPastPublishDate = new Date(page.publish_at) <= new Date();
162
+ const isStaff = (ROLE_HIERARCHY[userRole] || 1) >= ROLE_HIERARCHY['Editor'];
163
+ if ((!isPublished || !isPastPublishDate) && !isStaff) {
164
+ res.status(404).render('defaults/404');
165
+ return;
166
+ }
167
+ // 2. Minimum Role Access Verification
168
+ const requiredLevel = ROLE_HIERARCHY[page.min_role] || 1;
169
+ const userLevel = ROLE_HIERARCHY[userRole] || 1;
170
+ if (userLevel < requiredLevel) {
171
+ if (!user) {
172
+ res.redirect(`/admin/login?redirect=/${slug}`);
173
+ }
174
+ else {
175
+ res.status(403).render('defaults/403', {
176
+ message: 'Insufficient permissions to view this page.'
177
+ });
178
+ }
179
+ return;
180
+ }
181
+ // 3. Resolve Dynamic Theme Template Path
182
+ const { view: themeView, currentTheme } = resolveThemePath(page.template_name);
183
+ // 4. Render Page
184
+ res.render(themeView, {
185
+ ...page,
186
+ page,
187
+ user,
188
+ currentTheme,
189
+ title: page.title,
190
+ content: page.content || '',
191
+ siteName: process.env.APP_NAME || 'Radix',
192
+ appVersion: process.env.APP_VERSION || '0.11.0'
193
+ });
194
+ }
195
+ catch (err) {
196
+ console.error(`[Radix Public Route Error - /${slug}]:`, err);
197
+ res.status(500).render('defaults/500');
198
+ }
199
+ }
200
+ // GET / - Render Homepage (slug: 'home')
201
+ router.get('/', async (req, res) => {
202
+ await renderPageBySlug('home', req, res);
203
+ });
204
+ // GET /:slug - Dynamic Public Catch-All Route
205
+ router.get('/:slug', async (req, res) => {
206
+ const slug = req.params.slug;
207
+ if (!slug) {
208
+ res.status(404).render('defaults/404');
209
+ return;
210
+ }
211
+ // Skip reserved routes / static assets if mounted at app root
212
+ const reservedSlugs = ['admin', 'uploads', 'favicon.ico', 'css', 'js'];
213
+ if (reservedSlugs.includes(slug)) {
214
+ return;
215
+ }
216
+ await renderPageBySlug(slug, req, res);
217
+ });
218
+ exports.default = router;
@@ -0,0 +1,118 @@
1
+ /*
2
+ SQLyog Community v13.2.1 (64 bit)
3
+ MySQL - 10.9.1-MariaDB : Database - radix
4
+ *********************************************************************
5
+ */
6
+
7
+ /*!40101 SET NAMES utf8 */;
8
+
9
+ /*!40101 SET SQL_MODE=''*/;
10
+
11
+ /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
12
+ /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
13
+ /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
14
+ /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
15
+ CREATE DATABASE /*!32312 IF NOT EXISTS*/`radix` /*!40100 DEFAULT CHARACTER SET latin1 */;
16
+
17
+ USE `radix`;
18
+
19
+ /*Table structure for table `pages` */
20
+
21
+ DROP TABLE IF EXISTS `pages`;
22
+
23
+ CREATE TABLE `pages` (
24
+ `id` int(11) NOT NULL AUTO_INCREMENT,
25
+ `title` varchar(255) NOT NULL,
26
+ `slug` varchar(255) NOT NULL,
27
+ `content` text NOT NULL,
28
+ `template_name` varchar(50) DEFAULT 'default',
29
+ `created_at` timestamp NOT NULL DEFAULT current_timestamp(),
30
+ `status` enum('Published','Pending Review','Draft','Archived','Deleted') NOT NULL DEFAULT 'Draft',
31
+ `min_role` enum('Administrator','Manager','Editor','Registered','Unregistered') NOT NULL DEFAULT 'Unregistered',
32
+ `publish_at` datetime NOT NULL DEFAULT current_timestamp(),
33
+ PRIMARY KEY (`id`),
34
+ UNIQUE KEY `slug` (`slug`)
35
+ ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=latin1;
36
+
37
+ /*Data for the table `pages` */
38
+
39
+ insert into `pages`(`id`,`title`,`slug`,`content`,`template_name`,`created_at`,`status`,`min_role`,`publish_at`) values
40
+ (1,'Welcome Home','home','<h1>Hello World!</h1><p>Welcome to my lightweight Node.js CMS.</p>','default','2026-06-26 15:16:12','Published','Unregistered','2026-08-06 15:45:00'),
41
+ (2,'About Us','about','<h1>About Our System</h1>\r\n<p>This page is completely dynamic.</p>\r\n<h2>This is a test.</h2>','default','2026-06-26 15:16:12','Published','Unregistered','2026-08-05 17:27:00');
42
+
43
+ /*Table structure for table `plugins` */
44
+
45
+ DROP TABLE IF EXISTS `plugins`;
46
+
47
+ CREATE TABLE `plugins` (
48
+ `id` int(11) NOT NULL AUTO_INCREMENT,
49
+ `folder_name` varchar(150) NOT NULL,
50
+ `is_active` tinyint(1) DEFAULT 0,
51
+ `updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
52
+ PRIMARY KEY (`id`),
53
+ UNIQUE KEY `folder_name` (`folder_name`)
54
+ ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
55
+
56
+ /*Data for the table `plugins` */
57
+
58
+ /*Table structure for table `schema_meta` */
59
+
60
+ DROP TABLE IF EXISTS `schema_meta`;
61
+
62
+ CREATE TABLE `schema_meta` (
63
+ `key_name` varchar(100) NOT NULL,
64
+ `value_text` varchar(255) NOT NULL,
65
+ `updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
66
+ PRIMARY KEY (`key_name`)
67
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
68
+
69
+ /*Data for the table `schema_meta` */
70
+
71
+ insert into `schema_meta`(`key_name`,`value_text`,`updated_at`) values
72
+ ('db_initialized','1','2026-08-27 08:47:08'),
73
+ ('schema_version','1.0.0','2026-08-27 08:47:08');
74
+
75
+ /*Table structure for table `settings` */
76
+
77
+ DROP TABLE IF EXISTS `settings`;
78
+
79
+ CREATE TABLE `settings` (
80
+ `setting_key` varchar(100) NOT NULL,
81
+ `setting_value` text DEFAULT NULL,
82
+ PRIMARY KEY (`setting_key`)
83
+ ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
84
+
85
+ /*Data for the table `settings` */
86
+
87
+ insert into `settings`(`setting_key`,`setting_value`) values
88
+ ('app_name','Radix'),
89
+ ('app_version','0.11.0'),
90
+ ('mail_from','noreply-bc@astleygilbert.com'),
91
+ ('mail_host','192.168.11.10'),
92
+ ('mail_pass',''),
93
+ ('mail_port','25'),
94
+ ('mail_secure','0'),
95
+ ('mail_user',''),
96
+ ('site_logo','');
97
+
98
+ /*Table structure for table `users` */
99
+
100
+ DROP TABLE IF EXISTS `users`;
101
+
102
+ CREATE TABLE `users` (
103
+ `id` int(11) NOT NULL AUTO_INCREMENT,
104
+ `username` varchar(100) NOT NULL,
105
+ `password` varchar(255) DEFAULT NULL,
106
+ `full_name` varchar(255) DEFAULT NULL,
107
+ `password_hash` varchar(255) NOT NULL,
108
+ `email` varchar(255) DEFAULT NULL,
109
+ `role` enum('Administrator','Manager','Editor','Registered','Unregistered') NOT NULL DEFAULT 'Registered',
110
+ `created_at` timestamp NOT NULL DEFAULT current_timestamp(),
111
+ PRIMARY KEY (`id`),
112
+ UNIQUE KEY `username` (`username`)
113
+ ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1;
114
+
115
+ /*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
116
+ /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
117
+ /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
118
+ /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ /**************************************************************************************************
3
+ * RadixCMS
4
+ *
5
+ * DESCRIPTION: plugin interfaces and extension contracts.
6
+ *
7
+ * Copyright (C) 2026 Ultra Spark Software <salve@ultraspark.net>
8
+ * SPDX-License-Identifier: GPL-3.0-or-later
9
+ **************************************************************************************************/
10
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ /**************************************************************************************************
3
+ * RadixCMS
4
+ *
5
+ * DESCRIPTION: plugin discovery, loading, and lifecycle management.
6
+ *
7
+ * Copyright (C) 2026 Ultra Spark Software <salve@ultraspark.net>
8
+ * SPDX-License-Identifier: GPL-3.0-or-later
9
+ **************************************************************************************************/
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
23
+ }) : function(o, v) {
24
+ o["default"] = v;
25
+ });
26
+ var __importStar = (this && this.__importStar) || (function () {
27
+ var ownKeys = function(o) {
28
+ ownKeys = Object.getOwnPropertyNames || function (o) {
29
+ var ar = [];
30
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
+ return ar;
32
+ };
33
+ return ownKeys(o);
34
+ };
35
+ return function (mod) {
36
+ if (mod && mod.__esModule) return mod;
37
+ var result = {};
38
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
+ __setModuleDefault(result, mod);
40
+ return result;
41
+ };
42
+ })();
43
+ var __importDefault = (this && this.__importDefault) || function (mod) {
44
+ return (mod && mod.__esModule) ? mod : { "default": mod };
45
+ };
46
+ Object.defineProperty(exports, "__esModule", { value: true });
47
+ exports.pluginManager = exports.PluginManager = void 0;
48
+ const fs_1 = __importDefault(require("fs"));
49
+ const path_1 = __importDefault(require("path"));
50
+ const db_1 = __importDefault(require("../config/db"));
51
+ class PluginManager {
52
+ hooks = new Map();
53
+ filters = new Map();
54
+ // Register an Action Listener
55
+ registerHook(event, callback) {
56
+ if (!this.hooks.has(event))
57
+ this.hooks.set(event, []);
58
+ this.hooks.get(event).push(callback);
59
+ }
60
+ // Trigger an Action
61
+ async triggerHook(event, payload) {
62
+ const callbacks = this.hooks.get(event) || [];
63
+ for (const cb of callbacks) {
64
+ await cb(payload);
65
+ }
66
+ }
67
+ // Register a Filter
68
+ registerFilter(filterName, callback) {
69
+ if (!this.filters.has(filterName))
70
+ this.filters.set(filterName, []);
71
+ this.filters.get(filterName).push(callback);
72
+ }
73
+ // Apply Filters to Data sequentially
74
+ async applyFilter(filterName, initialData) {
75
+ const callbacks = this.filters.get(filterName) || [];
76
+ let currentData = initialData;
77
+ for (const cb of callbacks) {
78
+ currentData = await cb(currentData);
79
+ }
80
+ return currentData;
81
+ }
82
+ // Scan and Load Plugins
83
+ async loadPlugins(router) {
84
+ const pluginsDir = path_1.default.join(__dirname, '../../plugins');
85
+ if (!fs_1.default.existsSync(pluginsDir))
86
+ return;
87
+ const folders = fs_1.default.readdirSync(pluginsDir);
88
+ for (const folder of folders) {
89
+ const pluginPath = path_1.default.join(pluginsDir, folder, 'index.ts');
90
+ if (fs_1.default.existsSync(pluginPath)) {
91
+ try {
92
+ // Dynamic import for Node/TypeScript
93
+ const pluginModule = await Promise.resolve(`${pluginPath}`).then(s => __importStar(require(s)));
94
+ const plugin = pluginModule.default;
95
+ const ctx = {
96
+ router,
97
+ registerHook: this.registerHook.bind(this),
98
+ registerFilter: this.registerFilter.bind(this),
99
+ db: db_1.default
100
+ };
101
+ await plugin.init(ctx);
102
+ console.log(`[Radix Plugin Loaded]: ${plugin.name} v${plugin.version}`);
103
+ }
104
+ catch (err) {
105
+ console.error(`[Radix Plugin Error] Failed to load ${folder}:`, err);
106
+ }
107
+ }
108
+ }
109
+ }
110
+ }
111
+ exports.PluginManager = PluginManager;
112
+ exports.pluginManager = new PluginManager();
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ /**************************************************************************************************
3
+ * RadixCMS
4
+ *
5
+ * DESCRIPTION: application settings retrieval and cache management.
6
+ *
7
+ * Copyright (C) 2026 Ultra Spark Software <salve@ultraspark.net>
8
+ * SPDX-License-Identifier: GPL-3.0-or-later
9
+ **************************************************************************************************/
10
+ var __importDefault = (this && this.__importDefault) || function (mod) {
11
+ return (mod && mod.__esModule) ? mod : { "default": mod };
12
+ };
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.getSettings = getSettings;
15
+ exports.getSetting = getSetting;
16
+ exports.clearSettingsCache = clearSettingsCache;
17
+ const db_1 = __importDefault(require("../config/db"));
18
+ let cachedSettings = null;
19
+ /*
20
+ * getSettings
21
+ *
22
+ * PURPOSE:
23
+ * Fetches all global site settings and caches them as a key-value object.
24
+ *
25
+ * PARAMETERS:
26
+ * forceRefresh (boolean): Reloads settings from the database when true.
27
+ *
28
+ * RETURNS:
29
+ * Returns a promise containing the settings keyed by setting name.
30
+ */
31
+ async function getSettings(forceRefresh = false) {
32
+ if (cachedSettings && !forceRefresh) {
33
+ return cachedSettings;
34
+ }
35
+ try {
36
+ const [rows] = await db_1.default.execute('SELECT * FROM settings');
37
+ const settings = {};
38
+ rows.forEach(row => {
39
+ settings[row.setting_key] = row.setting_value || '';
40
+ });
41
+ cachedSettings = settings;
42
+ return settings;
43
+ }
44
+ catch (err) {
45
+ console.error('[Radix Settings Helper Error]:', err);
46
+ return cachedSettings || {};
47
+ }
48
+ }
49
+ /*
50
+ * getSetting
51
+ *
52
+ * PURPOSE:
53
+ * Fetches one global setting by key and supplies a fallback when it is absent.
54
+ *
55
+ * PARAMETERS:
56
+ * key (string): Setting name to retrieve.
57
+ * defaultValue (string): Value returned when the setting does not exist.
58
+ *
59
+ * RETURNS:
60
+ * Returns a promise containing the setting value.
61
+ */
62
+ async function getSetting(key, defaultValue = '') {
63
+ const settings = await getSettings();
64
+ return settings[key] !== undefined ? settings[key] : defaultValue;
65
+ }
66
+ /*
67
+ * clearSettingsCache
68
+ *
69
+ * PURPOSE:
70
+ * Invalidates the in-memory settings cache after settings are changed.
71
+ *
72
+ * PARAMETERS:
73
+ * None.
74
+ *
75
+ * RETURNS:
76
+ * Returns void.
77
+ */
78
+ function clearSettingsCache() {
79
+ cachedSettings = null;
80
+ }
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ /**************************************************************************************************
3
+ * RadixCMS
4
+ *
5
+ * DESCRIPTION: discovery of available public themes.
6
+ *
7
+ * Copyright (C) 2026 Ultra Spark Software <salve@ultraspark.net>
8
+ * SPDX-License-Identifier: GPL-3.0-or-later
9
+ **************************************************************************************************/
10
+ var __importDefault = (this && this.__importDefault) || function (mod) {
11
+ return (mod && mod.__esModule) ? mod : { "default": mod };
12
+ };
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.getAvailableTemplates = getAvailableTemplates;
15
+ const fs_1 = __importDefault(require("fs"));
16
+ const path_1 = __importDefault(require("path"));
17
+ function getAvailableTemplates() {
18
+ // Adjust relative path based on compiled structure in /dist vs /src
19
+ const themesDir = path_1.default.join(__dirname, '../../views/themes');
20
+ if (!fs_1.default.existsSync(themesDir)) {
21
+ return ['default'];
22
+ }
23
+ // Read all folders and files inside /views/themes
24
+ const entries = fs_1.default.readdirSync(themesDir, { withFileTypes: true });
25
+ const themes = entries
26
+ .filter(entry => entry.isDirectory() || entry.name.endsWith('.ejs'))
27
+ .map(entry => entry.name.replace('.ejs', '').toLowerCase());
28
+ // Ensure 'default' is always in the list
29
+ if (!themes.includes('default')) {
30
+ themes.unshift('default');
31
+ }
32
+ return Array.from(new Set(themes));
33
+ }
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ /**************************************************************************************************
3
+ * RadixCMS
4
+ *
5
+ * DESCRIPTION: application version lookup and update checking.
6
+ *
7
+ * Copyright (C) 2026 Ultra Spark Software <salve@ultraspark.net>
8
+ * SPDX-License-Identifier: GPL-3.0-or-later
9
+ **************************************************************************************************/
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.checkForUpdates = checkForUpdates;
12
+ const settings_1 = require("./settings");
13
+ /*
14
+ * checkForUpdates
15
+ *
16
+ * PURPOSE:
17
+ * Retrieves the configured remote version and compares it with the installed
18
+ * application version.
19
+ *
20
+ * PARAMETERS:
21
+ * None.
22
+ *
23
+ * RETURNS:
24
+ * Returns a promise containing version details, update status, and any error.
25
+ */
26
+ async function checkForUpdates() {
27
+ // Fetch the version setting asynchronously from DB (cached after 1st run)
28
+ const settings = await (0, settings_1.getSettings)();
29
+ // const currentVersion = settings['app_version'] || '0.0.0';
30
+ const currentVersion = process.env.APP_VERSION || '0.0.0';
31
+ const ENDPOINT = settings['update_url'] || 'https://vault.ultraspark.net/radix/version.json';
32
+ try {
33
+ const response = await fetch(ENDPOINT, { signal: AbortSignal.timeout(5000) });
34
+ if (!response.ok) {
35
+ throw new Error(`Server returned status code ${response.status}`);
36
+ }
37
+ const data = (await response.json());
38
+ const app = data.Application;
39
+ const remoteVersion = `${app.MajorVersion}.${app.MinorVersion}.${app.UpdateVersion}`;
40
+ const hasUpdate = compareSemVer(remoteVersion, currentVersion) > 0;
41
+ return {
42
+ currentVersion,
43
+ remoteVersion,
44
+ hasUpdate,
45
+ details: app,
46
+ error: null
47
+ };
48
+ }
49
+ catch (err) {
50
+ return {
51
+ currentVersion,
52
+ remoteVersion: null,
53
+ hasUpdate: false,
54
+ details: null,
55
+ error: `Failed to retrieve update status: ${err.message}`
56
+ };
57
+ }
58
+ }
59
+ /*
60
+ * compareSemVer
61
+ *
62
+ * PURPOSE:
63
+ * Compares two semantic version strings using numeric ordering.
64
+ *
65
+ * PARAMETERS:
66
+ * v1 (string): First version to compare.
67
+ * v2 (string): Second version to compare.
68
+ *
69
+ * RETURNS:
70
+ * Returns a negative number, zero, or positive number according to the comparison.
71
+ */
72
+ function compareSemVer(v1, v2) {
73
+ return v1.localeCompare(v2, undefined, { numeric: true, sensitivity: 'base' });
74
+ }