community: store versionsUrl in the database

This commit is contained in:
Girish Ramakrishnan
2026-02-05 17:29:00 +01:00
parent 91b8f1a457
commit d6eb6d3e3e
13 changed files with 249 additions and 118 deletions
+27 -12
View File
@@ -78,6 +78,7 @@ const apps = require('../apps.js'),
AuditSource = require('../auditsource.js'),
backupSites = require('../backupsites.js'),
BoxError = require('../boxerror.js'),
community = require('../community.js'),
constants = require('../constants.js'),
debug = require('debug')('box:routes/apps'),
HttpError = require('@cloudron/connect-lastmile').HttpError,
@@ -145,7 +146,8 @@ async function install(req, res, next) {
// atleast one
if ('manifest' in data && typeof data.manifest !== 'object') return next(new HttpError(400, 'manifest must be an object'));
if ('appStoreId' in data && typeof data.appStoreId !== 'string') return next(new HttpError(400, 'appStoreId must be a string'));
if (!data.manifest && !data.appStoreId) return next(new HttpError(400, 'appStoreId or manifest is required'));
if ('versionsUrl' in data && typeof data.versionsUrl !== 'string') return next(new HttpError(400, 'versionsUrl must be a string'));
if (!data.manifest && !data.appStoreId && !data.versionsUrl) return next(new HttpError(400, 'appStoreId, versionsUrl, or manifest is required'));
// required
if (typeof data.subdomain !== 'string') return next(new HttpError(400, 'subdomain is required'));
@@ -196,15 +198,22 @@ async function install(req, res, next) {
if ('cpuQuota' in data && data.cpuQuota !== 'number') return next(new HttpError(400, 'cpuQuota is not a number'));
if ('operators' in data && typeof data.operators !== 'object') return next(new HttpError(400, 'operators must be an object'));
let [error, result] = await safe(appstore.downloadManifest(data.appStoreId, data.manifest));
let error, result;
if (data.versionsUrl) {
[error, result] = await safe(community.downloadManifest(data.versionsUrl));
data.manifest = result.manifest;
data.versionsUrl = result.versionsUrl; // without version
} else {
[error, result] = await safe(appstore.downloadManifest(data.appStoreId, data.manifest));
data.manifest = result.manifest;
data.appStoreId = result.appStoreId; // without version
}
if (error) return next(BoxError.toHttpError(error));
if (result.appStoreId === constants.PROXY_APP_APPSTORE_ID && typeof data.upstreamUri !== 'string') return next(new HttpError(400, 'upstreamUri must be a non empty string'));
if (safe.query(result.manifest, 'addons.docker') && req.user.role !== users.ROLE_OWNER) return next(new HttpError(403, '"owner" role is required to install app with docker addon'));
data.appStoreId = result.appStoreId;
data.manifest = result.manifest;
data.sourceArchiveFilePath = req.files && req.files.sourceArchive?.path || null;
// if we have a source archive upload, craft a custom docker image URI for later
@@ -699,20 +708,26 @@ async function update(req, res, next) {
// atleast one
if ('manifest' in data && typeof data.manifest !== 'object') return next(new HttpError(400, 'manifest must be an object'));
if ('appStoreId' in data && typeof data.appStoreId !== 'string') return next(new HttpError(400, 'appStoreId must be a string'));
if (!data.manifest && !data.appStoreId) return next(new HttpError(400, 'appStoreId or manifest is required'));
if ('versionsUrl' in data && typeof data.versionsUrl !== 'string') return next(new HttpError(400, 'versionsUrl must be a string'));
if (!data.manifest && !data.appStoreId && !data.versionsUrl) return next(new HttpError(400, 'appStoreId, versionsUrl, or manifest is required'));
if ('skipBackup' in data && typeof data.skipBackup !== 'boolean') return next(new HttpError(400, 'skipBackup must be a boolean'));
if ('force' in data && typeof data.force !== 'boolean') return next(new HttpError(400, 'force must be a boolean'));
let [error, result] = await safe(appstore.downloadManifest(data.appStoreId, data.manifest));
let error, result;
if (data.versionsUrl) {
[error, result] = await safe(community.downloadManifest(data.versionsUrl));
data.manifest = result.manifest;
data.versionsUrl = result.versionsUrl; // without version
} else {
[error, result] = await safe(appstore.downloadManifest(data.appStoreId, data.manifest));
data.manifest = result.manifest;
data.appStoreId = result.appStoreId; // without version
}
if (error) return next(BoxError.toHttpError(error));
const { appStoreId, manifest } = result;
if (safe.query(data.manifest, 'addons.docker') && req.user.role !== users.ROLE_OWNER) return next(new HttpError(403, '"owner" role is required to update app with docker addon'));
if (safe.query(manifest, 'addons.docker') && req.user.role !== users.ROLE_OWNER) return next(new HttpError(403, '"owner" role is required to update app with docker addon'));
data.appStoreId = appStoreId;
data.manifest = manifest;
data.sourceArchiveFilePath = req.files && req.files.sourceArchive?.path || null;
// if we have a source archive upload, craft a custom docker image URI for later
@@ -1065,7 +1080,7 @@ async function listEventlog(req, res, next) {
async function checkUpdate(req, res, next) {
assert.strictEqual(typeof req.resources.app, 'object');
if (!req.resources.app.appStoreId) return next(new HttpError(400, 'Custom apps have no updates'));
if (!req.resources.app.appStoreId && !req.resources.app.versionsUrl) return next(new HttpError(400, 'Custom apps have no updates'));
// it can take a while sometimes to get all the app updates one by one
req.clearTimeout();