@riligar/elysia-backup 1.2.0 → 1.4.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/README.md CHANGED
@@ -45,18 +45,18 @@ console.log('Backup UI at http://localhost:3000/backup')
45
45
 
46
46
  ## Configuration
47
47
 
48
- | Option | Type | Required | Description |
49
- | ----------------- | -------- | -------- | ------------------------------------------------------------- |
50
- | `bucket` | string | ✅ | R2/S3 bucket name |
51
- | `accessKeyId` | string | ✅ | R2/S3 Access Key ID |
52
- | `secretAccessKey` | string | ✅ | R2/S3 Secret Access Key |
53
- | `endpoint` | string | ✅ | R2/S3 Endpoint URL |
54
- | `sourceDir` | string | ✅ | Local directory to backup |
55
- | `prefix` | string | ❌ | Prefix for S3 keys (e.g., 'backups/') |
56
- | `extensions` | string[] | ❌ | File extensions to include |
57
- | `cronSchedule` | string | ❌ | Cron expression for scheduled backups |
58
- | `cronEnabled` | boolean | ❌ | Enable/disable scheduled backups |
59
- | `configPath` | string | ❌ | Path to save runtime config (default: './backup-config.json') |
48
+ | Option | Type | Required | Description |
49
+ | ----------------- | -------- | -------- | ------------------------------------------------------ |
50
+ | `bucket` | string | ✅ | R2/S3 bucket name |
51
+ | `accessKeyId` | string | ✅ | R2/S3 Access Key ID |
52
+ | `secretAccessKey` | string | ✅ | R2/S3 Secret Access Key |
53
+ | `endpoint` | string | ✅ | R2/S3 Endpoint URL |
54
+ | `sourceDir` | string | ✅ | Local directory to backup |
55
+ | `prefix` | string | ❌ | Prefix for S3 keys (e.g., 'backups/') |
56
+ | `extensions` | string[] | ❌ | File extensions to include |
57
+ | `cronSchedule` | string | ❌ | Cron expression for scheduled backups |
58
+ | `cronEnabled` | boolean | ❌ | Enable/disable scheduled backups |
59
+ | `configPath` | string | ❌ | Path to save runtime config (default: './config.json') |
60
60
 
61
61
  ## API Endpoints
62
62
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riligar/elysia-backup",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Elysia plugin for R2/S3 backup with a built-in UI dashboard",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -19,11 +19,11 @@
19
19
  },
20
20
  "repository": {
21
21
  "type": "git",
22
- "url": "git+https://github.com/riligar/elysia-backup.git"
22
+ "url": "git+https://github.com/riligar-solutions/elysia-backup.git"
23
23
  },
24
- "homepage": "https://github.com/riligar/elysia-backup#readme",
24
+ "homepage": "https://github.com/riligar-solutions/elysia-backup#readme",
25
25
  "bugs": {
26
- "url": "https://github.com/riligar/elysia-backup/issues"
26
+ "url": "https://github.com/riligar-solutions/elysia-backup/issues"
27
27
  },
28
28
  "scripts": {
29
29
  "dev": "bun run demo/index.js"
@@ -46,7 +46,9 @@
46
46
  "@elysiajs/html": ">=1.0.0"
47
47
  },
48
48
  "dependencies": {
49
- "cron": "^3.5.0"
49
+ "cron": "^3.5.0",
50
+ "otplib": "^12.0.1",
51
+ "qrcode": "^1.5.4"
50
52
  },
51
53
  "devDependencies": {
52
54
  "@elysiajs/html": "^1.4.0",
Binary file
Binary file
@@ -0,0 +1,70 @@
1
+ import { readFileSync, existsSync } from 'node:fs'
2
+ import { writeFile } from 'node:fs/promises'
3
+
4
+ /**
5
+ * Load configuration from a JSON file
6
+ * @param {string} configPath - Path to the config file
7
+ * @returns {Object} Loaded configuration or empty object
8
+ */
9
+ export const loadConfig = configPath => {
10
+ if (!existsSync(configPath)) {
11
+ return {}
12
+ }
13
+
14
+ try {
15
+ const fileContent = readFileSync(configPath, 'utf-8')
16
+ const config = JSON.parse(fileContent)
17
+ console.log('Loaded backup config from', configPath)
18
+ return config
19
+ } catch (e) {
20
+ console.error('Failed to load backup config:', e)
21
+ return {}
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Save configuration to a JSON file
27
+ * @param {string} configPath - Path to save the config
28
+ * @param {Object} config - Configuration object to save
29
+ */
30
+ export const saveConfig = async (configPath, config) => {
31
+ try {
32
+ await writeFile(configPath, JSON.stringify(config, null, 2))
33
+ return true
34
+ } catch (e) {
35
+ console.error('Failed to save config:', e)
36
+ return false
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Create a configuration manager
42
+ * @param {Object} initialConfig - Initial configuration from code
43
+ * @returns {Object} Configuration manager with get/set/save methods
44
+ */
45
+ export const createConfigManager = initialConfig => {
46
+ const configPath = initialConfig.configPath || './config.json'
47
+ const savedConfig = loadConfig(configPath)
48
+
49
+ let config = { ...initialConfig, ...savedConfig }
50
+
51
+ return {
52
+ get: () => config,
53
+ getPath: () => configPath,
54
+
55
+ set: newConfig => {
56
+ config = { ...config, ...newConfig }
57
+ return config
58
+ },
59
+
60
+ save: async () => {
61
+ return saveConfig(configPath, config)
62
+ },
63
+
64
+ update: async newConfig => {
65
+ config = { ...config, ...newConfig }
66
+ await saveConfig(configPath, config)
67
+ return config
68
+ },
69
+ }
70
+ }
@@ -0,0 +1,99 @@
1
+ import { CronJob } from 'cron'
2
+
3
+ /**
4
+ * Create a backup scheduler
5
+ * @param {Function} onBackup - Callback to execute on scheduled backup
6
+ * @returns {Object} Scheduler with setup/stop/getStatus methods
7
+ */
8
+ export const createScheduler = onBackup => {
9
+ let backupJob = null
10
+
11
+ return {
12
+ /**
13
+ * Setup or restart the cron scheduler
14
+ * @param {string} cronSchedule - Cron expression
15
+ * @param {boolean} enabled - Whether scheduling is enabled
16
+ */
17
+ setup: (cronSchedule, enabled = true) => {
18
+ // Stop existing job if any
19
+ if (backupJob) {
20
+ backupJob.stop()
21
+ backupJob = null
22
+ }
23
+
24
+ if (!cronSchedule || enabled === false) {
25
+ return
26
+ }
27
+
28
+ console.log(`Setting up backup cron: ${cronSchedule}`)
29
+
30
+ try {
31
+ backupJob = new CronJob(
32
+ cronSchedule,
33
+ async () => {
34
+ console.log('Running scheduled backup...')
35
+ try {
36
+ // Generate timestamp for the backup
37
+ const now = new Date()
38
+ const timestamp =
39
+ now.getFullYear() +
40
+ '-' +
41
+ String(now.getMonth() + 1).padStart(2, '0') +
42
+ '-' +
43
+ String(now.getDate()).padStart(2, '0') +
44
+ '_' +
45
+ String(now.getHours()).padStart(2, '0') +
46
+ '-' +
47
+ String(now.getMinutes()).padStart(2, '0') +
48
+ '-' +
49
+ String(now.getSeconds()).padStart(2, '0')
50
+
51
+ await onBackup(timestamp)
52
+ console.log('Scheduled backup completed')
53
+ } catch (e) {
54
+ console.error('Scheduled backup failed:', e)
55
+ }
56
+ },
57
+ null,
58
+ true // start immediately
59
+ )
60
+ } catch (e) {
61
+ console.error('Invalid cron schedule:', e.message)
62
+ }
63
+ },
64
+
65
+ /**
66
+ * Stop the scheduler
67
+ */
68
+ stop: () => {
69
+ if (backupJob) {
70
+ backupJob.stop()
71
+ backupJob = null
72
+ }
73
+ },
74
+
75
+ /**
76
+ * Get current scheduler status
77
+ * @param {boolean} cronEnabled - Whether cron is enabled in config
78
+ * @returns {Object} Status with isRunning and nextRun
79
+ */
80
+ getStatus: cronEnabled => {
81
+ const isRunning = !!backupJob && cronEnabled !== false
82
+ let nextRun = null
83
+
84
+ if (isRunning && backupJob) {
85
+ try {
86
+ const nextDate = backupJob.nextDate()
87
+ if (nextDate) {
88
+ // cron returns Luxon DateTime, convert to JS Date
89
+ nextRun = nextDate.toJSDate().toISOString()
90
+ }
91
+ } catch (e) {
92
+ console.error('Error getting next date', e)
93
+ }
94
+ }
95
+
96
+ return { isRunning, nextRun }
97
+ },
98
+ }
99
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Session Management Module
3
+ * Handles secure session creation, validation, and cleanup
4
+ */
5
+
6
+ /**
7
+ * Generate a secure random session token
8
+ * @returns {string} 64-character hex token
9
+ */
10
+ export const generateSessionToken = () => {
11
+ const array = new Uint8Array(32)
12
+ crypto.getRandomValues(array)
13
+ return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('')
14
+ }
15
+
16
+ /**
17
+ * Create a session manager instance
18
+ * @returns {Object} Session manager with create/get/delete methods
19
+ */
20
+ export const createSessionManager = () => {
21
+ const sessions = new Map()
22
+
23
+ return {
24
+ /**
25
+ * Create a new session for a user
26
+ * @param {string} username - Username for the session
27
+ * @param {number} sessionDuration - Duration in milliseconds (default: 24h)
28
+ * @returns {Object} Session token and expiration
29
+ */
30
+ create: (username, sessionDuration = 24 * 60 * 60 * 1000) => {
31
+ const token = generateSessionToken()
32
+ const expiresAt = Date.now() + sessionDuration
33
+ sessions.set(token, { username, expiresAt })
34
+ return { token, expiresAt }
35
+ },
36
+
37
+ /**
38
+ * Get and validate a session by token
39
+ * @param {string} token - Session token
40
+ * @returns {Object|null} Session data or null if invalid/expired
41
+ */
42
+ get: token => {
43
+ if (!token) return null
44
+
45
+ const session = sessions.get(token)
46
+ if (!session) return null
47
+
48
+ if (Date.now() > session.expiresAt) {
49
+ sessions.delete(token)
50
+ return null
51
+ }
52
+
53
+ return session
54
+ },
55
+
56
+ /**
57
+ * Delete a session
58
+ * @param {string} token - Session token to delete
59
+ */
60
+ delete: token => {
61
+ if (token) {
62
+ sessions.delete(token)
63
+ }
64
+ },
65
+
66
+ /**
67
+ * Get the number of active sessions
68
+ * @returns {number} Count of active sessions
69
+ */
70
+ count: () => sessions.size,
71
+
72
+ /**
73
+ * Clean up expired sessions
74
+ * @returns {number} Number of sessions cleaned
75
+ */
76
+ cleanup: () => {
77
+ const now = Date.now()
78
+ let cleaned = 0
79
+
80
+ for (const [token, session] of sessions) {
81
+ if (now > session.expiresAt) {
82
+ sessions.delete(token)
83
+ cleaned++
84
+ }
85
+ }
86
+
87
+ return cleaned
88
+ },
89
+ }
90
+ }