archives: use separate table

Cleaner to separate things from the backups table.

* icon, appConfig, appStoreIcon etc are only valid for archives
* older version cloudron does not have appConfig in backups table (so it
  cannot be an archive entry)
This commit is contained in:
Girish Ramakrishnan
2024-12-10 10:06:52 +01:00
parent 2ad93c114e
commit 490840b71d
12 changed files with 261 additions and 122 deletions

View File

@@ -10,13 +10,6 @@ exports = module.exports = {
list,
del,
archives: {
get: archivesGet,
getIcon: archivesGetIcon,
list: archivesList,
del: archivesDel
},
startBackupTask,
startCleanupTask,
@@ -80,7 +73,7 @@ const assert = require('assert'),
tasks = require('./tasks.js'),
_ = require('underscore');
const BACKUPS_FIELDS = [ 'id', 'remotePath', 'label', 'identifier', 'creationTime', 'packageVersion', 'type', 'dependsOnJson', 'state', 'manifestJson', 'format', 'preserveSecs', 'encryptionVersion', 'archive', 'appConfigJson' ];
const BACKUPS_FIELDS = [ 'id', 'remotePath', 'label', 'identifier', 'creationTime', 'packageVersion', 'type', 'dependsOnJson', 'state', 'manifestJson', 'format', 'preserveSecs', 'encryptionVersion' ];
function postProcess(result) {
assert.strictEqual(typeof result, 'object');
@@ -91,9 +84,6 @@ function postProcess(result) {
result.manifest = result.manifestJson ? safe.JSON.parse(result.manifestJson) : null;
delete result.manifestJson;
result.appConfig = result.appConfigJson ? safe.JSON.parse(result.appConfigJson) : null;
delete result.appConfigJson;
return result;
}
@@ -137,10 +127,9 @@ async function add(data) {
const manifestJson = JSON.stringify(data.manifest);
const prefixId = data.type === exports.BACKUP_TYPE_APP ? `${data.type}_${data.identifier}` : data.type; // type and identifier are same for other types
const id = `${prefixId}_v${data.packageVersion}_${hat(32)}`; // id is used by the UI to derive dependent packages. making this a UUID will require a lot of db querying
const appConfigJson = 'appConfig' in data ? JSON.stringify(data.appConfig) : null;
const [error] = await safe(database.query('INSERT INTO backups (id, remotePath, identifier, encryptionVersion, packageVersion, type, creationTime, state, dependsOnJson, manifestJson, format, preserveSecs, appConfigJson) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
[ id, data.remotePath, data.identifier, data.encryptionVersion, data.packageVersion, data.type, creationTime, data.state, JSON.stringify(data.dependsOn), manifestJson, data.format, data.preserveSecs, appConfigJson ]));
const [error] = await safe(database.query('INSERT INTO backups (id, remotePath, identifier, encryptionVersion, packageVersion, type, creationTime, state, dependsOnJson, manifestJson, format, preserveSecs) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
[ id, data.remotePath, data.identifier, data.encryptionVersion, data.packageVersion, data.type, creationTime, data.state, JSON.stringify(data.dependsOn), manifestJson, data.format, data.preserveSecs ]));
if (error && error.code === 'ER_DUP_ENTRY') throw new BoxError(BoxError.ALREADY_EXISTS, 'Backup already exists');
if (error) throw error;
@@ -182,55 +171,6 @@ async function getByTypePaged(type, page, perPage) {
return results;
}
async function archivesGet(id) {
assert.strictEqual(typeof id, 'string');
const result = await database.query(`SELECT ${BACKUPS_FIELDS} FROM backups WHERE id = ? AND archive = 1 ORDER BY creationTime DESC`, [ id ]);
if (result.length === 0) return null;
return postProcess(result[0]);
}
async function archivesGetIcons(id) {
assert.strictEqual(typeof id, 'string');
const results = await database.query('SELECT icon, appStoreIcon FROM backups WHERE id = ?', [ id ]);
if (results.length === 0) return null;
return { icon: results[0].icon, appStoreIcon: results[0].appStoreIcon };
}
async function archivesGetIcon(id, options) {
assert.strictEqual(typeof id, 'string');
assert.strictEqual(typeof options, 'object');
const icons = await archivesGetIcons(id);
if (!icons) throw new BoxError(BoxError.NOT_FOUND, 'No such backup');
if (!options.original && icons.icon) return icons.icon;
if (icons.appStoreIcon) return icons.appStoreIcon;
return null;
}
async function archivesList(page, perPage) {
assert(typeof page === 'number' && page > 0);
assert(typeof perPage === 'number' && perPage > 0);
const results = await database.query(`SELECT ${BACKUPS_FIELDS} FROM backups WHERE archive = 1 ORDER BY creationTime DESC LIMIT ?,?`, [ (page-1)*perPage, perPage ]);
results.forEach(function (result) { postProcess(result); });
return results;
}
async function archivesDel(id, auditSource) {
assert.strictEqual(typeof id, 'string');
assert(auditSource && typeof auditSource === 'object');
const result = await database.query('UPDATE backups SET archive = 0 WHERE id=?', [ id ]);
if (result.affectedRows !== 1) throw new BoxError(BoxError.NOT_FOUND, 'Backup not found in archive');
}
function validateLabel(label) {
assert.strictEqual(typeof label, 'string');
@@ -269,12 +209,9 @@ async function update(id, data) {
const fields = [], values = [];
for (const p in data) {
if (p === 'label' || p === 'preserveSecs' || p === 'archive' || p === 'icon' || p === 'appStoreIcon') {
if (p === 'label' || p === 'preserveSecs') {
fields.push(p + ' = ?');
values.push(data[p]);
} else if (p === 'appConfig') {
fields.push(`${p}Json = ?`);
values.push(JSON.stringify(data[p]));
}
}
values.push(id);