Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 26 additions & 43 deletions src/storage/version/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,8 @@ function orgListFromEnv(env, name) {
return new Set(raw.split(',').map((s) => s.trim()).filter(Boolean));
}

/** Org uses audit.txt as the version list source (new feature). */
function orgUsesAuditFileList(env, org) {
return orgListFromEnv(env, 'VERSIONS_AUDIT_FILE_ORGS').has(org);
}

/**
* With audit-file feature: skip reading org/.da-versions (after migration).
* Only applies when org is also in VERSIONS_AUDIT_FILE_ORGS.
* Skip reading org/.da-versions (after migration is complete for this org).
*/
function orgSkipsLegacy(env, org) {
return orgListFromEnv(env, 'VERSIONS_AUDIT_SKIP_LEGACY_ORGS').has(org);
Expand Down Expand Up @@ -127,56 +121,45 @@ function mergeLegacyAndNewResult(legacyResult, newResult) {

export async function listObjectVersions(env, { bucket, org, key }) {
const current = await getObject(env, { bucket, org, key }, true);
if (current.status === 404 || !current.metadata.id) {
const repo = key.includes('/') ? key.split('/')[0] : '';

if (current.status === 404 || !current.metadata.id || !repo) {
return 404;
}

const fileId = current.metadata.id;
const repo = key.includes('/') ? key.split('/')[0] : '';

if (repo && orgUsesAuditFileList(env, org)) {
let auditLines = [];
try {
auditLines = await readAuditLines(env, { bucket, org }, repo, fileId);
} catch {
// no audit
}
const ext = fileExt(key);
const auditEntries = buildEntriesFromAudit(auditLines, repo, org, fileId, ext);
auditEntries.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
auditEntries.splice(MAX_VERSIONS);
const auditResult = {
status: 200,
contentType: 'application/json',
body: JSON.stringify(auditEntries),
};
if (orgSkipsLegacy(env, org)) {
versionListModeLog({
mode: 'audit_file',
org,
key,
fileId,
legacy: 'skipped',
});
return auditResult;
}
const legacyResult = await listFromLegacyStructure(env, { bucket, org, key }, fileId);
let auditLines = [];
try {
auditLines = await readAuditLines(env, { bucket, org }, repo, fileId);
} catch {
// no audit
}
const ext = fileExt(key);
const auditEntries = buildEntriesFromAudit(auditLines, repo, org, fileId, ext);
auditEntries.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
auditEntries.splice(MAX_VERSIONS);
const auditResult = {
status: 200,
contentType: 'application/json',
body: JSON.stringify(auditEntries),
};
if (orgSkipsLegacy(env, org)) {
versionListModeLog({
mode: 'audit_file',
org,
key,
fileId,
legacy: 'merged',
legacy: 'skipped',
});
return mergeLegacyAndNewResult(legacyResult, auditResult);
return auditResult;
}

const legacyResult = await listFromLegacyStructure(env, { bucket, org, key }, fileId);
versionListModeLog({
mode: 'legacy',
mode: 'audit_file',
org,
key,
fileId,
detail: 'org_root_da_versions_only',
legacy: 'merged',
});
return listFromLegacyStructure(env, { bucket, org, key }, fileId);
return mergeLegacyAndNewResult(legacyResult, auditResult);
}
73 changes: 24 additions & 49 deletions src/storage/version/put.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,10 +211,6 @@ export async function putObjectWithVersion(

const Preparsingstore = storeBody ? Timestamp : pps;

const usesAuditFile = new Set(
(env?.VERSIONS_AUDIT_FILE_ORGS || '').split(',').map((s) => s.trim()).filter(Boolean),
).has(daCtx.org);

// Only create version for explicit label (POST /versionsource) or Restore Point. No Collab Parse.
const shouldCreateVersionObject = createVersion
&& (update.label != null || Label === 'Restore Point');
Expand All @@ -223,7 +219,7 @@ export async function putObjectWithVersion(
const versionResp = await putVersion(config, {
Bucket: input.Bucket,
Org: daCtx.org,
Repo: usesAuditFile ? (daCtx.site || undefined) : undefined,
Repo: daCtx.site || undefined,
Body: (body || storeBody ? current.body : ''),
ContentLength: (body || storeBody ? current.contentLength : undefined),
ContentType: current.contentType,
Expand All @@ -247,50 +243,29 @@ export async function putObjectWithVersion(
// Audit: one entry per versionable PUT; versionLabel + versionId when labelled version created.
// Store path without repo prefix and versionId without extension for readability.
if (createVersion) {
if (usesAuditFile) {
const versionId = versionCreated ? Version : undefined;
const versionLabel = versionCreated ? (Label ?? '') : undefined;
const pathForAudit = (daCtx.site && Path.startsWith(`${daCtx.site}/`))
? Path.slice(daCtx.site.length)
: Path;
let auditErr;
for (let i = 0; i < AUDIT_WRITE_RETRIES; i += 1) {
try {
// eslint-disable-next-line no-await-in-loop
await writeAuditEntry(env, { bucket: input.Bucket, org: daCtx.org }, daCtx.site, ID, {
timestamp: Timestamp,
users: Users,
path: pathForAudit,
versionLabel,
versionId,
});
auditErr = null;
break;
} catch (e) { auditErr = e; }
}
if (auditErr) {
// eslint-disable-next-line no-console
console.error(`Failed to write audit entry after ${AUDIT_WRITE_RETRIES} retries`, auditErr);
}
} else if (!shouldCreateVersionObject) {
// Legacy path: write an empty version object so listFromLegacyStructure can find it.
// Only needed when no snapshot was created — the snapshot itself serves as the marker.
await putVersion(config, {
Bucket: input.Bucket,
Org: daCtx.org,
Body: '',
ContentLength: 0,
ContentType: current.contentType,
ID,
Version,
Ext: daCtx.ext,
Metadata: {
Users,
Timestamp,
Path,
Label: Label ?? '',
},
}, false);
const versionId = versionCreated ? Version : undefined;
const versionLabel = versionCreated ? (Label ?? '') : undefined;
const pathForAudit = (daCtx.site && Path.startsWith(`${daCtx.site}/`))
? Path.slice(daCtx.site.length)
: Path;
let auditErr;
for (let i = 0; i < AUDIT_WRITE_RETRIES; i += 1) {
try {
// eslint-disable-next-line no-await-in-loop
await writeAuditEntry(env, { bucket: input.Bucket, org: daCtx.org }, daCtx.site, ID, {
timestamp: Timestamp,
users: Users,
path: pathForAudit,
versionLabel,
versionId,
});
auditErr = null;
break;
} catch (e) { auditErr = e; }
}
if (auditErr) {
// eslint-disable-next-line no-console
console.error(`Failed to write audit entry after ${AUDIT_WRITE_RETRIES} retries`, auditErr);
}
}

Expand Down
2 changes: 1 addition & 1 deletion test/storage/object/conditionals.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ describe('Conditional Headers', () => {
const clientConditionals = { ifMatch: '"wrongetag"' };

const resp = await putObjectWithVersion(
{ VERSIONS_AUDIT_FILE_ORGS: ORG },
{},
daCtx,
update,
false,
Expand Down
Loading
Loading