diff --git a/migrations/20240417142800-apps-add-checklistJson.js b/migrations/20240417142800-apps-add-checklistJson.js new file mode 100644 index 000000000..d55492ea9 --- /dev/null +++ b/migrations/20240417142800-apps-add-checklistJson.js @@ -0,0 +1,9 @@ +'use strict'; + +exports.up = async function (db) { + await db.runSql('ALTER TABLE apps ADD COLUMN checklistJson TEXT'); +}; + +exports.down = async function (db) { + await db.runSql('ALTER TABLE apps DROP COLUMN checklistJson'); +}; diff --git a/migrations/schema.sql b/migrations/schema.sql index ada41acfc..cf1c7cf7d 100644 --- a/migrations/schema.sql +++ b/migrations/schema.sql @@ -104,6 +104,7 @@ CREATE TABLE IF NOT EXISTS apps( icon MEDIUMBLOB, crontab TEXT, upstreamUri VARCHAR(256) DEFAULT "", + checklistJson TEXT, // checklist for admins FOREIGN KEY(mailboxDomain) REFERENCES domains(domain), FOREIGN KEY(inboxDomain) REFERENCES domains(domain), diff --git a/package-lock.json b/package-lock.json index 397a73547..9674796ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "aws-sdk": "^2.1595.0", "basic-auth": "^2.0.1", "body-parser": "^1.20.2", - "cloudron-manifestformat": "^5.22.1", + "cloudron-manifestformat": "^5.23.0", "connect": "^3.7.0", "connect-lastmile": "^2.2.0", "connect-timeout": "^1.9.0", @@ -992,11 +992,11 @@ } }, "node_modules/cloudron-manifestformat": { - "version": "5.22.1", - "resolved": "https://registry.npmjs.org/cloudron-manifestformat/-/cloudron-manifestformat-5.22.1.tgz", - "integrity": "sha512-WfCko1oNbrwMLoErZEXD68Z62Ia/1hGrA2urVs5k6NbpbRvL7/kwIPHfGTYLr7N3jDHrgIudwanemO0YwfzNrg==", + "version": "5.23.0", + "resolved": "https://registry.npmjs.org/cloudron-manifestformat/-/cloudron-manifestformat-5.23.0.tgz", + "integrity": "sha512-HZECVY16wQO5IWdUzyxIUIMlVty3jGpsja2IRmywAM8G6NVVoGo35m1anWrwqAVj3n9e9QdU0PiWm6JiRRD1Ow==", "dependencies": { - "cron": "^3.1.6", + "cron": "^3.1.7", "java-packagename-regex": "^1.0.0", "safetydance": "2.4.0", "semver": "^7.6.0", @@ -1004,12 +1004,17 @@ "validator": "^13.11.0" } }, + "node_modules/cloudron-manifestformat/node_modules/@types/luxon": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz", + "integrity": "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==" + }, "node_modules/cloudron-manifestformat/node_modules/cron": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cron/-/cron-3.1.6.tgz", - "integrity": "sha512-cvFiQCeVzsA+QPM6fhjBtlKGij7tLLISnTSvFxVdnFGLdz+ZdXN37kNe0i2gefmdD17XuZA6n2uPVwzl4FxW/w==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/cron/-/cron-3.1.7.tgz", + "integrity": "sha512-tlBg7ARsAMQLzgwqVxy8AZl/qlTc5nibqYwtNGoCrd+cV+ugI+tvZC1oT/8dFH8W455YrywGykx/KMmAqOr7Jw==", "dependencies": { - "@types/luxon": "~3.3.0", + "@types/luxon": "~3.4.0", "luxon": "~3.4.0" } }, diff --git a/package.json b/package.json index 9bde9ea90..e27e82269 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "aws-sdk": "^2.1595.0", "basic-auth": "^2.0.1", "body-parser": "^1.20.2", - "cloudron-manifestformat": "^5.22.1", + "cloudron-manifestformat": "^5.23.0", "connect": "^3.7.0", "connect-lastmile": "^2.2.0", "connect-timeout": "^1.9.0", diff --git a/src/apps.js b/src/apps.js index c5aa0c902..9d5b72158 100644 --- a/src/apps.js +++ b/src/apps.js @@ -187,7 +187,7 @@ const APPS_FIELDS_PREFIXED = [ 'apps.id', 'apps.appStoreId', 'apps.installationS 'apps.health', 'apps.containerId', 'apps.manifestJson', 'apps.accessRestrictionJson', 'apps.memoryLimit', 'apps.cpuQuota', 'apps.label', 'apps.notes', 'apps.tagsJson', 'apps.taskId', 'apps.reverseProxyConfigJson', 'apps.servicesConfigJson', 'apps.operatorsJson', 'apps.sso', 'apps.debugModeJson', 'apps.enableBackup', 'apps.proxyAuth', 'apps.containerIp', 'apps.crontab', - 'apps.creationTime', 'apps.updateTime', 'apps.enableAutomaticUpdate', 'apps.upstreamUri', + 'apps.creationTime', 'apps.updateTime', 'apps.enableAutomaticUpdate', 'apps.upstreamUri', 'apps.checklistJson', 'apps.enableMailbox', 'apps.mailboxDisplayName', 'apps.mailboxName', 'apps.mailboxDomain', 'apps.enableInbox', 'apps.inboxName', 'apps.inboxDomain', 'apps.enableTurn', 'apps.enableRedis', 'apps.storageVolumeId', 'apps.storageVolumePrefix', 'apps.ts', 'apps.healthTime', '(apps.icon IS NOT NULL) AS hasIcon', '(apps.appStoreIcon IS NOT NULL) AS hasAppStoreIcon' ].join(','); @@ -576,7 +576,7 @@ function removeInternalFields(app) { 'accessRestriction', 'manifest', 'portBindings', 'iconUrl', 'memoryLimit', 'cpuQuota', 'operators', 'sso', 'debugMode', 'reverseProxyConfig', 'enableBackup', 'creationTime', 'updateTime', 'ts', 'tags', 'label', 'notes', 'secondaryDomains', 'redirectDomains', 'aliasDomains', 'env', 'enableAutomaticUpdate', - 'storageVolumeId', 'storageVolumePrefix', 'mounts', 'enableTurn', 'enableRedis', + 'storageVolumeId', 'storageVolumePrefix', 'mounts', 'enableTurn', 'enableRedis', 'checklist', 'enableMailbox', 'mailboxDisplayName', 'mailboxName', 'mailboxDomain', 'enableInbox', 'inboxName', 'inboxDomain'); removeCertificateKeys(result); @@ -632,6 +632,10 @@ function postProcess(result) { result.tags = safe.JSON.parse(result.tagsJson) || []; delete result.tagsJson; + assert(result.checklistJson === null || typeof result.checklistJson === 'string'); + result.checklist = safe.JSON.parse(result.checklistJson) || {}; + delete result.checklistJson; + assert(result.reverseProxyConfigJson === null || typeof result.reverseProxyConfigJson === 'string'); result.reverseProxyConfig = safe.JSON.parse(result.reverseProxyConfigJson) || {}; delete result.reverseProxyConfigJson; @@ -841,6 +845,7 @@ async function add(id, appStoreId, manifest, subdomain, domain, portBindings, da env = data.env || {}, label = data.label || null, tagsJson = data.tags ? JSON.stringify(data.tags) : null, + checklistJson = data.checklist ? JSON.stringify(data.checklist) : null, mailboxName = data.mailboxName || null, mailboxDomain = data.mailboxDomain || null, mailboxDisplayName = data.mailboxDisplayName || '', @@ -858,11 +863,11 @@ async function add(id, appStoreId, manifest, subdomain, domain, portBindings, da queries.push({ query: 'INSERT INTO apps (id, appStoreId, manifestJson, installationState, runState, accessRestrictionJson, memoryLimit, cpuQuota, ' - + 'sso, debugModeJson, mailboxName, mailboxDomain, label, tagsJson, reverseProxyConfigJson, servicesConfigJson, icon, ' + + 'sso, debugModeJson, mailboxName, mailboxDomain, label, tagsJson, reverseProxyConfigJson, checklistJson, servicesConfigJson, icon, ' + 'enableMailbox, mailboxDisplayName, upstreamUri, enableTurn, enableRedis) ' - + ' VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', + + ' VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', args: [ id, appStoreId, manifestJson, installationState, runState, accessRestrictionJson, memoryLimit, cpuQuota, - sso, debugModeJson, mailboxName, mailboxDomain, label, tagsJson, reverseProxyConfigJson, servicesConfigJson, icon, + sso, debugModeJson, mailboxName, mailboxDomain, label, tagsJson, reverseProxyConfigJson, checklistJson, servicesConfigJson, icon, enableMailbox, mailboxDisplayName, upstreamUri, enableTurn, enableRedis ] }); @@ -936,6 +941,7 @@ async function updateWithConstraints(id, app, constraints) { assert(!('redirectDomains' in app) || Array.isArray(app.redirectDomains)); assert(!('aliasDomains' in app) || Array.isArray(app.aliasDomains)); assert(!('tags' in app) || Array.isArray(app.tags)); + assert(!('checklist' in app) || typeof app.checklist === 'object'); assert(!('env' in app) || typeof app.env === 'object'); @@ -997,7 +1003,7 @@ async function updateWithConstraints(id, app, constraints) { const fields = [ ], values = [ ]; for (const p in app) { - if (p === 'manifest' || p === 'tags' || p === 'accessRestriction' || p === 'debugMode' || p === 'error' || p === 'reverseProxyConfig' || p === 'servicesConfig' || p === 'operators') { + if (p === 'manifest' || p === 'tags' || p === 'checklist' || p === 'accessRestriction' || p === 'debugMode' || p === 'error' || p === 'reverseProxyConfig' || p === 'servicesConfig' || p === 'operators') { fields.push(`${p}Json = ?`); values.push(JSON.stringify(app[p])); } else if (p !== 'portBindings' && p !== 'subdomain' && p !== 'domain' && p !== 'secondaryDomains' && p !== 'redirectDomains' && p !== 'aliasDomains' && p !== 'env' && p !== 'mounts') { @@ -1325,6 +1331,7 @@ async function install(data, auditSource) { env = data.env || {}, label = data.label || null, tags = data.tags || [], + checklist = data.manifest.checklist || {}, overwriteDns = 'overwriteDns' in data ? data.overwriteDns : false, skipDnsSetup = 'skipDnsSetup' in data ? data.skipDnsSetup : false, enableTurn = 'enableTurn' in data ? data.enableTurn : true, @@ -1414,6 +1421,7 @@ async function install(data, auditSource) { env, label, tags, + checklist, icon, enableMailbox, upstreamUri, @@ -1992,6 +2000,8 @@ async function updateApp(app, data, auditSource) { error = await checkManifest(manifest); if (error) throw error; + // TODO check if checklist needs to be adjusted either here or in the apptask + const updateConfig = { skipBackup, manifest, appStoreId }; // this will clear appStoreId when updating from a repo and set it if passed in for update route // prevent user from installing a app with different manifest id over an existing app