637b32515a
Recursive workspace sync now imports files in oa/private/public subfolders, references workspace paths without duplicating storage, and includes workspace-sourced assets in category itemCount. Refresh counts when leaving a category view. Co-authored-by: Cursor <cursoragent@cursor.com>
42 lines
1.8 KiB
JavaScript
42 lines
1.8 KiB
JavaScript
import path from 'node:path';
|
|
import { resolveUserWorkspaceRoot, resolveZoneFilePath } from './user-space.mjs';
|
|
|
|
export const WORKSPACE_STORAGE_PREFIX = 'workspace://';
|
|
|
|
export function buildWorkspaceStorageKey(userId, categoryCode, relativeFilename) {
|
|
const category = String(categoryCode ?? '').trim();
|
|
const relativePath = String(relativeFilename ?? '').replace(/\\/g, '/').replace(/^\/+/, '');
|
|
if (!userId || !category || !relativePath) {
|
|
throw new Error('invalid workspace storage key parts');
|
|
}
|
|
if (relativePath.split('/').some((part) => part === '..' || part === '.')) {
|
|
throw new Error('invalid workspace relative path');
|
|
}
|
|
return `${WORKSPACE_STORAGE_PREFIX}${userId}/${category}/${relativePath}`;
|
|
}
|
|
|
|
export function isWorkspaceStorageKey(storageKey) {
|
|
return String(storageKey ?? '').startsWith(WORKSPACE_STORAGE_PREFIX);
|
|
}
|
|
|
|
export function resolveWorkspaceStoragePath(h5Root, storageKey) {
|
|
if (!h5Root || !isWorkspaceStorageKey(storageKey)) return null;
|
|
const rest = storageKey.slice(WORKSPACE_STORAGE_PREFIX.length);
|
|
const slash = rest.indexOf('/');
|
|
if (slash <= 0) return null;
|
|
const userId = rest.slice(0, slash);
|
|
const remainder = rest.slice(slash + 1);
|
|
const slash2 = remainder.indexOf('/');
|
|
if (slash2 <= 0) return null;
|
|
const categoryCode = remainder.slice(0, slash2);
|
|
const relativeFilename = remainder.slice(slash2 + 1);
|
|
if (!relativeFilename) return null;
|
|
const workspaceRoot = resolveUserWorkspaceRoot(h5Root, { id: userId });
|
|
const resolved = path.resolve(resolveZoneFilePath(workspaceRoot, categoryCode, relativeFilename));
|
|
const zoneRoot = path.resolve(resolveZoneFilePath(workspaceRoot, categoryCode, ''));
|
|
if (resolved !== zoneRoot && !resolved.startsWith(`${zoneRoot}${path.sep}`)) {
|
|
return null;
|
|
}
|
|
return resolved;
|
|
}
|