cyber-elx 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.
package/ELX_PAGES.md ADDED
@@ -0,0 +1,186 @@
1
+ # ELX Custom Pages API Documentation
2
+
3
+ ## Overview
4
+
5
+ These endpoints allow you to retrieve and update custom ELX (E-Learning Experience) pages. The pages are Liquid templates that can be customized for different sections of the e-learning platform.
6
+
7
+ ---
8
+
9
+ ## Authentication
10
+
11
+ All endpoints require authentication. You must include the `_token` header in your requests.
12
+
13
+ **Required Header:**
14
+
15
+ | Header | Description |
16
+ |--------|-------------|
17
+ | `_token` | Your authentication token |
18
+
19
+ **Unauthorized Response (403):**
20
+
21
+ ```json
22
+ {
23
+ "success": false,
24
+ "message": "Unauthorized"
25
+ }
26
+ ```
27
+
28
+ > **Note:** Only users with `admin` role can access these endpoints.
29
+
30
+ ---
31
+
32
+ ## Endpoints
33
+
34
+ ### 1. Get ELX Pages
35
+
36
+ Retrieves all custom ELX pages.
37
+
38
+ **URL:** `GET https://domain_name.com/api/plugin_api/el-x/get_elx_pages`
39
+
40
+ **Response:**
41
+
42
+ ```json
43
+ {
44
+ "success": true,
45
+ "pages": [
46
+ {
47
+ "type": "template", // "template" or "section"
48
+ "key": "home_page",
49
+ "content": "<!-- Liquid template content -->",
50
+ "created_at": 123456798,
51
+ "updated_at": 123456798
52
+ },
53
+ {
54
+ "type": "template", // "template" or "section"
55
+ "key": "courses_page",
56
+ "content": "<!-- Liquid template content -->",
57
+ "created_at": 123456798,
58
+ "updated_at": 123456798
59
+ }
60
+ ]
61
+ }
62
+ ```
63
+
64
+ **Default Page Keys (Templates):**
65
+
66
+ | Key | Description |
67
+ |-----|-------------|
68
+ | `home_page` | Home page template |
69
+ | `courses_page` | Courses listing page template |
70
+ | `course_page` | Single course page template |
71
+ | `about_page` | About page template |
72
+ | `category_page` | Category page template |
73
+ | `blogs_page` | Blogs listing page template |
74
+ | `blog_page` | Single blog page template |
75
+ | `contact_page` | Contact page template |
76
+
77
+ - The API may return sections or more pages, so if detected just created them locally, and when updating, update the sections and pages.
78
+
79
+ ---
80
+
81
+ ### 2. Update ELX Pages
82
+
83
+ Updates one or more custom ELX pages.
84
+
85
+ **URL:** `POST https://domain_name.com/api/plugin_api/el-x/update_elx_pages`
86
+
87
+ **Request Body:**
88
+
89
+ ```json
90
+ {
91
+ "pages": [
92
+ {
93
+ "type": "template", // "template" or "section"
94
+ "key": "home_page",
95
+ "content": "<!-- Your custom Liquid template -->"
96
+ },
97
+ {
98
+ "type": "template", // "template" or "section"
99
+ "key": "courses_page",
100
+ "content": "<!-- Your custom Liquid template -->"
101
+ }
102
+ ]
103
+ }
104
+ ```
105
+
106
+ **Allowed Page Keys:**
107
+
108
+ - `home_page`
109
+ - `courses_page`
110
+ - `course_page`
111
+ - `about_page`
112
+ - `category_page`
113
+ - `blogs_page`
114
+ - `blog_page`
115
+ - `contact_page`
116
+
117
+ **Response (Success):**
118
+
119
+ ```json
120
+ {
121
+ "success": true,
122
+ "message": "Pages updated successfully",
123
+ "updatedpages": [
124
+ {
125
+ "type": "template", // "template" or "section"
126
+ "key": "home_page",
127
+ "content": "<!-- Your custom Liquid template -->",
128
+ "created_at": 123456798,
129
+ "updated_at": 123456798
130
+ },
131
+ ...
132
+ ]
133
+ }
134
+ ```
135
+
136
+ **Response (No valid pages):**
137
+
138
+ ```json
139
+ {
140
+ "success": true,
141
+ "message": "No pages to update, Allowed pages are 0",
142
+ "updatedpages": []
143
+ }
144
+ ```
145
+
146
+ ---
147
+
148
+ ### 3. Get ELX Default Pages (As an example for the user)
149
+
150
+ Retrieves all default ELX pages.
151
+
152
+ **URL:** `GET https://domain_name.com/api/plugin_api/el-x/get_defaults_for_elx_pages`
153
+
154
+ **Response:**
155
+
156
+ ```json
157
+ {
158
+ "success": true,
159
+ "pages": [
160
+ {
161
+ "type": "template", // "template" or "section"
162
+ "key": "home_page",
163
+ "content": "<!-- Liquid template content -->",
164
+ "created_at": 123456798,
165
+ "updated_at": 123456798
166
+ },
167
+ {
168
+ "type": "template", // "template" or "section"
169
+ "key": "courses_page",
170
+ "content": "<!-- Liquid template content -->",
171
+ "created_at": 123456798,
172
+ "updated_at": 123456798
173
+ }
174
+ ]
175
+ }
176
+ ```
177
+
178
+ - The API will return all defaults (Defaults may change in the future at any given time)
179
+
180
+ ---
181
+
182
+ ## Behavior Notes
183
+
184
+ 1. **Empty Content:** Pages with empty or whitespace-only content will be skipped during update.
185
+
186
+ 2. **Validation:** Only pages with keys matching the allowed list will be processed. Invalid keys are silently ignored.
package/README.md ADDED
@@ -0,0 +1,92 @@
1
+ # cyber-elx
2
+
3
+ CLI tool to upload/download ELX custom pages (Liquid templates).
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g .
9
+ # or
10
+ npm link
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ### Initialize a new project
16
+
17
+ ```bash
18
+ cd /path/to/your/project
19
+ cyber-elx init
20
+ ```
21
+
22
+ This will:
23
+ 1. Prompt for your website URL and authentication token
24
+ 2. Create `cyber-elx.jsonc` config file
25
+ 3. Download all pages from the server
26
+
27
+ ### Download pages
28
+
29
+ ```bash
30
+ cyber-elx download
31
+ ```
32
+
33
+ Downloads pages from the server. If local files have been modified, you'll be prompted before overwriting.
34
+
35
+ Options:
36
+ - `-f, --force` - Force download without confirmation prompts
37
+
38
+ ### Upload pages
39
+
40
+ ```bash
41
+ cyber-elx upload
42
+ ```
43
+
44
+ Uploads local pages to the server. If server pages have been modified since last download, you'll be prompted before overwriting.
45
+
46
+ Options:
47
+ - `-f, --force` - Force upload without confirmation prompts
48
+
49
+ ## Folder Structure
50
+
51
+ ```
52
+ your-project/
53
+ ├── cyber-elx.jsonc # Config file (url + token)
54
+ ├── .cache # Timestamps cache (auto-generated)
55
+ ├── layouts/ # Custom layouts (*.liquid)
56
+ ├── sections/ # Custom sections (*.liquid)
57
+ ├── templates/ # Custom templates (*.liquid)
58
+ └── defaults/ # Read-only default templates
59
+ ├── sections/
60
+ └── templates/
61
+ ```
62
+
63
+ ## Config File
64
+
65
+ The `cyber-elx.jsonc` file contains your website URL and authentication token:
66
+
67
+ ```jsonc
68
+ {
69
+ // ELX Custom Pages Configuration
70
+ "url": "https://my-website.net",
71
+ "token": "your-auth-token"
72
+ }
73
+ ```
74
+
75
+ ## Default Templates
76
+
77
+ The `defaults/` folder contains read-only copies of the default templates. Use these as reference when creating your custom pages. If a custom page is empty, the default will be used automatically by the server.
78
+
79
+ ## Available Page Keys
80
+
81
+ ### Templates
82
+ - `home_page` - Home page
83
+ - `courses_page` - Courses listing
84
+ - `course_page` - Single course
85
+ - `about_page` - About page
86
+ - `category_page` - Category page
87
+ - `blogs_page` - Blogs listing
88
+ - `blog_page` - Single blog
89
+ - `contact_page` - Contact page
90
+
91
+ ### Sections
92
+ Additional sections may be available depending on your server configuration.
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+
3
+ require('../src/index.js');
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "cyber-elx",
3
+ "version": "1.0.0",
4
+ "description": "CLI tool to upload/download ELX custom pages",
5
+ "main": "src/index.js",
6
+ "bin": {
7
+ "cyber-elx": "./bin/cyber-elx.js"
8
+ },
9
+ "scripts": {
10
+ "test": "echo \"Error: no test specified\" && exit 1"
11
+ },
12
+ "keywords": [
13
+ "elx",
14
+ "cli",
15
+ "liquid",
16
+ "templates"
17
+ ],
18
+ "author": "",
19
+ "license": "MIT",
20
+ "dependencies": {
21
+ "axios": "^1.6.0",
22
+ "chalk": "^4.1.2",
23
+ "commander": "^11.1.0",
24
+ "inquirer": "^8.2.6",
25
+ "jsonc-parser": "^3.2.0"
26
+ }
27
+ }
package/src/api.js ADDED
@@ -0,0 +1,35 @@
1
+ const axios = require('axios');
2
+
3
+ function createApiClient(config) {
4
+ const client = axios.create({
5
+ baseURL: config.url,
6
+ headers: {
7
+ '_token': config.token,
8
+ 'Content-Type': 'application/json'
9
+ }
10
+ });
11
+
12
+ return {
13
+ async getPages() {
14
+ const response = await client.get('/api/plugin_api/el-x/get_elx_pages');
15
+ return response.data;
16
+ },
17
+
18
+ async updatePages(pages) {
19
+ try {
20
+ const response = await client.post('/api/plugin_api/el-x/update_elx_pages', { pages });
21
+ return response.data;
22
+ } catch (error) {
23
+ console.error(error);
24
+ throw error;
25
+ }
26
+ },
27
+
28
+ async getDefaultPages() {
29
+ const response = await client.get('/api/plugin_api/el-x/get_defaults_for_elx_pages');
30
+ return response.data;
31
+ }
32
+ };
33
+ }
34
+
35
+ module.exports = { createApiClient };
package/src/cache.js ADDED
@@ -0,0 +1,53 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const CACHE_FILE = '.cache';
5
+
6
+ function getCachePath(cwd = process.cwd()) {
7
+ return path.join(cwd, CACHE_FILE);
8
+ }
9
+
10
+ function readCache(cwd = process.cwd()) {
11
+ const cachePath = getCachePath(cwd);
12
+ if (!fs.existsSync(cachePath)) {
13
+ return { pages: {} };
14
+ }
15
+ try {
16
+ const content = fs.readFileSync(cachePath, 'utf-8');
17
+ return JSON.parse(content);
18
+ } catch (e) {
19
+ return { pages: {} };
20
+ }
21
+ }
22
+
23
+ function writeCache(cache, cwd = process.cwd()) {
24
+ const cachePath = getCachePath(cwd);
25
+ fs.writeFileSync(cachePath, JSON.stringify(cache, null, 2), 'utf-8');
26
+ }
27
+
28
+ function getPageCacheKey(type, key) {
29
+ return `${type}:${key}`;
30
+ }
31
+
32
+ function getPageTimestamp(cache, type, key) {
33
+ const cacheKey = getPageCacheKey(type, key);
34
+ return cache.pages[cacheKey]?.updated_at || null;
35
+ }
36
+
37
+ function setPageTimestamp(cache, type, key, updated_at) {
38
+ const cacheKey = getPageCacheKey(type, key);
39
+ if (!cache.pages) {
40
+ cache.pages = {};
41
+ }
42
+ cache.pages[cacheKey] = { updated_at };
43
+ }
44
+
45
+ module.exports = {
46
+ CACHE_FILE,
47
+ getCachePath,
48
+ readCache,
49
+ writeCache,
50
+ getPageCacheKey,
51
+ getPageTimestamp,
52
+ setPageTimestamp
53
+ };
package/src/config.js ADDED
@@ -0,0 +1,57 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const jsonc = require('jsonc-parser');
4
+
5
+ const CONFIG_FILE = 'cyber-elx.jsonc';
6
+
7
+ function getConfigPath(cwd = process.cwd()) {
8
+ return path.join(cwd, CONFIG_FILE);
9
+ }
10
+
11
+ function configExists(cwd = process.cwd()) {
12
+ return fs.existsSync(getConfigPath(cwd));
13
+ }
14
+
15
+ function readConfig(cwd = process.cwd()) {
16
+ const configPath = getConfigPath(cwd);
17
+ if (!fs.existsSync(configPath)) {
18
+ return null;
19
+ }
20
+ const content = fs.readFileSync(configPath, 'utf-8');
21
+ return jsonc.parse(content);
22
+ }
23
+
24
+ function writeConfig(config, cwd = process.cwd()) {
25
+ const configPath = getConfigPath(cwd);
26
+ const content = `{
27
+ // ELX Custom Pages Configuration
28
+ // URL of your website (without trailing slash)
29
+ "url": "${config.url}",
30
+ // Authentication token (get from admin panel)
31
+ "token": "${config.token}"
32
+ }
33
+ `;
34
+ fs.writeFileSync(configPath, content, 'utf-8');
35
+ }
36
+
37
+ function validateConfig(config) {
38
+ if (!config) {
39
+ return { valid: false, error: 'Config file not found. Run "cyber-elx init" first.' };
40
+ }
41
+ if (!config.url) {
42
+ return { valid: false, error: 'Missing "url" in config file.' };
43
+ }
44
+ if (!config.token) {
45
+ return { valid: false, error: 'Missing "token" in config file.' };
46
+ }
47
+ return { valid: true };
48
+ }
49
+
50
+ module.exports = {
51
+ CONFIG_FILE,
52
+ getConfigPath,
53
+ configExists,
54
+ readConfig,
55
+ writeConfig,
56
+ validateConfig
57
+ };
package/src/files.js ADDED
@@ -0,0 +1,114 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const DEFAULT_TEMPLATE_KEYS = [
5
+ 'home_page',
6
+ 'courses_page',
7
+ 'course_page',
8
+ 'about_page',
9
+ 'category_page',
10
+ 'blogs_page',
11
+ 'blog_page',
12
+ 'contact_page'
13
+ ];
14
+
15
+ function ensureDirectories(cwd = process.cwd()) {
16
+ const dirs = [
17
+ path.join(cwd, 'sections'),
18
+ path.join(cwd, 'templates'),
19
+ path.join(cwd, 'layouts'),
20
+ path.join(cwd, 'defaults', 'sections'),
21
+ path.join(cwd, 'defaults', 'templates'),
22
+ path.join(cwd, 'defaults', 'layouts')
23
+ ];
24
+ for (const dir of dirs) {
25
+ if (!fs.existsSync(dir)) {
26
+ fs.mkdirSync(dir, { recursive: true });
27
+ }
28
+ }
29
+ }
30
+
31
+ function getFolder(type) {
32
+ if (type === 'template') return 'templates';
33
+ if (type === 'section') return 'sections';
34
+ if (type === 'layout') return 'layouts';
35
+ return type + 's';
36
+ }
37
+
38
+ function getFilePath(type, key, cwd = process.cwd(), isDefault = false) {
39
+ const folder = getFolder(type);
40
+ if (isDefault) {
41
+ return path.join(cwd, 'defaults', folder, `${key}.liquid`);
42
+ }
43
+ return path.join(cwd, folder, `${key}.liquid`);
44
+ }
45
+
46
+ function readPageFile(type, key, cwd = process.cwd()) {
47
+ const filePath = getFilePath(type, key, cwd);
48
+ if (!fs.existsSync(filePath)) {
49
+ return null;
50
+ }
51
+ return fs.readFileSync(filePath, 'utf-8');
52
+ }
53
+
54
+ function writePageFile(type, key, content, cwd = process.cwd(), isDefault = false) {
55
+ const filePath = getFilePath(type, key, cwd, isDefault);
56
+ const dir = path.dirname(filePath);
57
+ if (!fs.existsSync(dir)) {
58
+ fs.mkdirSync(dir, { recursive: true });
59
+ }
60
+ fs.writeFileSync(filePath, content, 'utf-8');
61
+ }
62
+
63
+ function fileExists(type, key, cwd = process.cwd()) {
64
+ const filePath = getFilePath(type, key, cwd);
65
+ return fs.existsSync(filePath);
66
+ }
67
+
68
+ function getLocalPages(cwd = process.cwd()) {
69
+ const pages = [];
70
+
71
+ const templatesDir = path.join(cwd, 'templates');
72
+ const sectionsDir = path.join(cwd, 'sections');
73
+ const layoutsDir = path.join(cwd, 'layouts');
74
+
75
+ if (fs.existsSync(templatesDir)) {
76
+ const files = fs.readdirSync(templatesDir).filter(f => f.endsWith('.liquid'));
77
+ for (const file of files) {
78
+ const key = file.replace('.liquid', '');
79
+ const content = fs.readFileSync(path.join(templatesDir, file), 'utf-8');
80
+ pages.push({ type: 'template', key, content });
81
+ }
82
+ }
83
+
84
+ if (fs.existsSync(sectionsDir)) {
85
+ const files = fs.readdirSync(sectionsDir).filter(f => f.endsWith('.liquid'));
86
+ for (const file of files) {
87
+ const key = file.replace('.liquid', '');
88
+ const content = fs.readFileSync(path.join(sectionsDir, file), 'utf-8');
89
+ pages.push({ type: 'section', key, content });
90
+ }
91
+ }
92
+
93
+ if (fs.existsSync(layoutsDir)) {
94
+ const files = fs.readdirSync(layoutsDir).filter(f => f.endsWith('.liquid'));
95
+ for (const file of files) {
96
+ const key = file.replace('.liquid', '');
97
+ const content = fs.readFileSync(path.join(layoutsDir, file), 'utf-8');
98
+ pages.push({ type: 'layout', key, content });
99
+ }
100
+ }
101
+
102
+ return pages;
103
+ }
104
+
105
+ module.exports = {
106
+ DEFAULT_TEMPLATE_KEYS,
107
+ ensureDirectories,
108
+ getFolder,
109
+ getFilePath,
110
+ readPageFile,
111
+ writePageFile,
112
+ fileExists,
113
+ getLocalPages
114
+ };