mirror of
https://github.com/HeyPuter/puter.git
synced 2026-08-26 16:07:13 +00:00
Generated
+76
-2081
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,6 @@
|
||||
"@stylistic/eslint-plugin": "^5.3.1",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/mime-types": "^3.0.1",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.46.1",
|
||||
"@typescript-eslint/parser": "^8.46.1",
|
||||
"@vitest/coverage-v8": "^4.0.14",
|
||||
@@ -32,7 +31,6 @@
|
||||
"html-webpack-plugin": "^5.6.0",
|
||||
"husky": "^9.1.7",
|
||||
"license-check-and-add": "^4.0.5",
|
||||
"mocha": "^7.2.0",
|
||||
"nodemon": "^3.1.0",
|
||||
"prettier": "^3.8.3",
|
||||
"simple-git": "^3.32.3",
|
||||
|
||||
@@ -1443,21 +1443,6 @@ export class FSController extends PuterController {
|
||||
return candidate;
|
||||
}
|
||||
|
||||
#isDedupeEnabled(fileMetadata: FSEntryWriteInput | undefined): boolean {
|
||||
if (!fileMetadata) {
|
||||
return false;
|
||||
}
|
||||
const metadataRecord = fileMetadata as unknown as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
const dedupeCandidate = this.#firstDefined(
|
||||
fileMetadata.dedupeName,
|
||||
metadataRecord.dedupe_name,
|
||||
);
|
||||
return this.#toBoolean(dedupeCandidate) ?? false;
|
||||
}
|
||||
|
||||
#resolveWriteFileMetadata(
|
||||
fileMetadata: FSEntryWriteInput | undefined,
|
||||
fallbackSource?: unknown,
|
||||
@@ -1834,9 +1819,8 @@ export class FSController extends PuterController {
|
||||
throw new HttpError(400, 'Cannot write to root path');
|
||||
}
|
||||
|
||||
const dedupeEnabled = this.#isDedupeEnabled(normalizedFileMetadata);
|
||||
let pathToCheck = parentPath;
|
||||
if (Boolean(normalizedFileMetadata.overwrite) && !dedupeEnabled) {
|
||||
if (Boolean(normalizedFileMetadata.overwrite)) {
|
||||
const destinationExists =
|
||||
await this.services.fs.entryExistsByPath(targetPath);
|
||||
if (destinationExists) {
|
||||
|
||||
@@ -448,16 +448,31 @@ export class LegacyFSController extends PuterController {
|
||||
if (!rawPath) throw new HttpError(400, '`path` is required');
|
||||
|
||||
// Supports `{ parent, path }` where `path` is a relative suffix.
|
||||
// When `parent` is a path string, use it directly without requiring
|
||||
// the entry to exist — `services.fs.mkdir` honors `create_missing_parents`
|
||||
// and will materialize any missing intermediate directories.
|
||||
let targetPath = rawPath;
|
||||
if (body.parent !== undefined && !rawPath.startsWith('/')) {
|
||||
const parent = await resolveV1Selector(
|
||||
this.stores.fsEntry,
|
||||
body.parent,
|
||||
);
|
||||
let parentPath: string;
|
||||
if (
|
||||
typeof body.parent === 'string' &&
|
||||
(body.parent.startsWith('/') || body.parent.startsWith('~'))
|
||||
) {
|
||||
parentPath = this.#expandTilde(
|
||||
body.parent,
|
||||
actor.user?.username,
|
||||
);
|
||||
} else {
|
||||
const parent = await resolveV1Selector(
|
||||
this.stores.fsEntry,
|
||||
body.parent,
|
||||
);
|
||||
parentPath = parent.path;
|
||||
}
|
||||
targetPath =
|
||||
parent.path === '/'
|
||||
parentPath === '/'
|
||||
? `/${rawPath}`
|
||||
: `${parent.path}/${rawPath}`;
|
||||
: `${parentPath.replace(/\/+$/, '')}/${rawPath}`;
|
||||
}
|
||||
|
||||
const parentPath = pathPosix.dirname(
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
"@aws-sdk/client-polly": "^3.1028.0",
|
||||
"@aws-sdk/client-s3": "^3.1028.0",
|
||||
"@aws-sdk/client-textract": "^3.1028.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.1028.0",
|
||||
"@aws-sdk/credential-providers": "^3.1021.0",
|
||||
"@aws-sdk/lib-dynamodb": "^3.490.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.1028.0",
|
||||
"@google/genai": "^1.19.0",
|
||||
"@heyputer/kv.js": "^0.2.1",
|
||||
"@heyputer/putility": "^1.0.0",
|
||||
@@ -57,7 +57,7 @@
|
||||
"mime-types": "^2.1.35",
|
||||
"murmurhash": "^2.0.1",
|
||||
"mysql2": "^3.21.1",
|
||||
"nodemailer": "^7.0.13",
|
||||
"nodemailer": "^8.0.7",
|
||||
"openai": "^6.34.0",
|
||||
"otpauth": "^9.2.4",
|
||||
"parse-domain": "^8.2.2",
|
||||
@@ -69,13 +69,12 @@
|
||||
"together-ai": "^0.33.0",
|
||||
"ua-parser-js": "^1.0.41",
|
||||
"uglify-js": "^3.17.4",
|
||||
"uuid": "^9.0.1",
|
||||
"uuid": "^14.0.0",
|
||||
"validator": "^13.15.35"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.0.0",
|
||||
"chai": "^4.3.7",
|
||||
"mocha": "^7.2.0",
|
||||
"nodemon": "^3.1.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^8.0.0",
|
||||
|
||||
@@ -417,7 +417,14 @@ export class FSService extends PuterService {
|
||||
const pathReservedInBatch = reservedPaths.has(normalizedInput.path);
|
||||
|
||||
if (pathReservedInBatch || existingEntry) {
|
||||
if (normalizedInput.dedupeName) {
|
||||
if (normalizedInput.overwrite) {
|
||||
if (pathReservedInBatch) {
|
||||
throw new HttpError(
|
||||
409,
|
||||
`Batch contains duplicate target path: ${normalizedInput.path}`,
|
||||
);
|
||||
}
|
||||
} else if (normalizedInput.dedupeName) {
|
||||
const dedupedPath = await this.#findDedupedPath(
|
||||
normalizedInput.path,
|
||||
reservedPaths,
|
||||
|
||||
@@ -345,6 +345,36 @@ export class FSEntryStore extends PuterStore {
|
||||
});
|
||||
}
|
||||
|
||||
async invalidateEntryCacheById(id: number): Promise<void> {
|
||||
if (typeof id !== 'number' || !Number.isFinite(id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rows = (await this.clients.db.read(
|
||||
`SELECT ${this.#selectFsentriesColumns()} FROM fsentries WHERE id = ? LIMIT 1`,
|
||||
[id],
|
||||
)) as unknown as FSEntryRow[];
|
||||
const row = rows[0];
|
||||
|
||||
if (row) {
|
||||
const entry = this.#mapFSEntryRow(row);
|
||||
await this.#invalidateEntryCache(entry);
|
||||
return;
|
||||
}
|
||||
|
||||
const cached = await this.#readEntryFromCache(
|
||||
`prodfsv2:fsentry:id:${id}`,
|
||||
);
|
||||
if (cached) {
|
||||
await this.#invalidateEntryCache(cached);
|
||||
return;
|
||||
}
|
||||
|
||||
await this.publishCacheKeys({
|
||||
keys: [`prodfsv2:fsentry:id:${id}`],
|
||||
});
|
||||
}
|
||||
|
||||
#chunk<T>(values: T[], size: number): T[][] {
|
||||
if (values.length === 0) {
|
||||
return [];
|
||||
|
||||
@@ -218,6 +218,7 @@ export class SubdomainStore extends PuterStore {
|
||||
};
|
||||
await this.#refreshCache(row);
|
||||
await this.#invalidatePrefixListsForUser(userId);
|
||||
await this.#invalidateRootDirEntry(row.root_dir_id);
|
||||
|
||||
return row;
|
||||
}
|
||||
@@ -268,6 +269,15 @@ export class SubdomainStore extends PuterStore {
|
||||
for (const uid of affectedUsers) {
|
||||
await this.#invalidatePrefixListsForUser(uid);
|
||||
}
|
||||
// FSEntry rows embed a `subdomains_agg` JSON of associated subdomains,
|
||||
// so any rename / root_dir reassignment must drop the stale entry
|
||||
// caches on both the old and new root_dir_id.
|
||||
const affectedRootDirIds = new Set(
|
||||
[before?.root_dir_id, after?.root_dir_id].filter((v) => v != null),
|
||||
);
|
||||
for (const id of affectedRootDirIds) {
|
||||
await this.#invalidateRootDirEntry(id);
|
||||
}
|
||||
return after;
|
||||
}
|
||||
|
||||
@@ -293,6 +303,7 @@ export class SubdomainStore extends PuterStore {
|
||||
if (row.user_id != null) {
|
||||
await this.#invalidatePrefixListsForUser(row.user_id);
|
||||
}
|
||||
await this.#invalidateRootDirEntry(row.root_dir_id);
|
||||
}
|
||||
return affected;
|
||||
}
|
||||
@@ -317,6 +328,25 @@ export class SubdomainStore extends PuterStore {
|
||||
});
|
||||
}
|
||||
|
||||
// FSEntryStore caches each row with an embedded `subdomains_agg` JSON
|
||||
// (uuid + subdomain) keyed on `root_dir_id`. Without this, a deleted or
|
||||
// renamed subdomain keeps showing up under its old folder in the GUI
|
||||
// (website badge, "associated websites" popover) until the entry's
|
||||
// independent TTL expires.
|
||||
async #invalidateRootDirEntry(rootDirId) {
|
||||
if (rootDirId == null) return;
|
||||
const id =
|
||||
typeof rootDirId === 'number' ? rootDirId : Number(rootDirId);
|
||||
if (!Number.isFinite(id)) return;
|
||||
const fsEntry = this.stores?.fsEntry;
|
||||
if (!fsEntry?.invalidateEntryCacheById) return;
|
||||
try {
|
||||
await fsEntry.invalidateEntryCacheById(id);
|
||||
} catch {
|
||||
/* best-effort */
|
||||
}
|
||||
}
|
||||
|
||||
async #invalidatePrefixListsForUser(userId) {
|
||||
if (userId == null) return;
|
||||
const trackerKey = this.#prefixListTrackerKey(userId);
|
||||
|
||||
@@ -47,9 +47,9 @@
|
||||
"dependencies": {
|
||||
"file-type": "21.3.3",
|
||||
"json-colorizer": "^3.0.1",
|
||||
"mocha": "7.2.0",
|
||||
"music-metadata": "11.12.3",
|
||||
"nodemailer": "8.0.7",
|
||||
"string-template": "^1.0.0",
|
||||
"uuid": "^9.0.1"
|
||||
"uuid": "^14.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -716,7 +716,7 @@ const upload = async function (items, dirPath, options = {}) {
|
||||
size: file.size,
|
||||
contentType: file.type || 'application/octet-stream',
|
||||
overwrite: overwriteEnabled,
|
||||
dedupeName: options.dedupeName ?? true,
|
||||
dedupeName: overwriteEnabled? false: options.dedupeName ?? true,
|
||||
createMissingParents: shouldCreateMissingParents,
|
||||
app_uid: options.appUID,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user