From 379da2d4e92c560a294484f3d56098d11db6f0ff Mon Sep 17 00:00:00 2001 From: Daniel Salazar Date: Tue, 19 May 2026 12:01:49 -0700 Subject: [PATCH 1/2] fix: app creation dupped names limit --- src/backend/drivers/apps/AppDriver.js | 7 +++++-- src/backend/stores/app/AppStore.js | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/backend/drivers/apps/AppDriver.js b/src/backend/drivers/apps/AppDriver.js index 0a3712c95d..f0321a91eb 100644 --- a/src/backend/drivers/apps/AppDriver.js +++ b/src/backend/drivers/apps/AppDriver.js @@ -156,11 +156,14 @@ export class AppDriver extends PuterDriver { // Name conflict handling if (await this.appStore.existsByName(fields.name)) { if (options?.dedupe_name) { + const existingCount = await this.appStore.countByNamePrefix( + fields.name, + ); let candidate; - let n = 1; + let n = existingCount + 1; do { candidate = `${fields.name}-${++n}`; - if (n > 50) + if (n > 10) throw new HttpError(400, 'Failed to dedupe app name', { legacyCode: 'bad_request', }); diff --git a/src/backend/stores/app/AppStore.js b/src/backend/stores/app/AppStore.js index 753bf71f56..812eb699a0 100644 --- a/src/backend/stores/app/AppStore.js +++ b/src/backend/stores/app/AppStore.js @@ -198,6 +198,24 @@ export class AppStore extends PuterStore { return rows.length > 0; } + async getCountByPrefix(prefix) { + const rows = await this.clients.db.read( + this.clients.db.case({ + sqlite: 'SELECT COUNT(*) as c FROM `apps` WHERE `name` = ? OR `name` GLOB ?', + otherwise: + 'SELECT COUNT(*) as c FROM `apps` WHERE `name` = ? OR `name` REGEXP ?', + }), + [ + prefix, + this.clients.db.case({ + sqlite: `${prefix}-[0-9]*`, + otherwise: `^${prefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}-[0-9]+$`, + }), + ], + ); + return rows.length > 0 ? Number(rows[0].c) : 0; + } + async existsByIndexUrl(indexUrl) { const rows = await this.clients.db.read( 'SELECT `id` FROM `apps` WHERE `index_url` = ? LIMIT 1', From 054ac764ff35379932850b4494e0acb636d86ccf Mon Sep 17 00:00:00 2001 From: Daniel Salazar Date: Tue, 19 May 2026 12:07:31 -0700 Subject: [PATCH 2/2] fix: app dupped names --- package-lock.json | 2 +- src/backend/drivers/apps/AppDriver.js | 14 +++++++------- src/backend/stores/app/AppStore.js | 18 ------------------ 3 files changed, 8 insertions(+), 26 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6981e5308c..01009bdddc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19199,7 +19199,7 @@ }, "src/puter-js": { "name": "@heyputer/puter.js", - "version": "2.3.2", + "version": "2.3.4", "license": "Apache-2.0", "dependencies": { "@heyputer/kv.js": "^0.2.1", diff --git a/src/backend/drivers/apps/AppDriver.js b/src/backend/drivers/apps/AppDriver.js index f0321a91eb..9cd38cf647 100644 --- a/src/backend/drivers/apps/AppDriver.js +++ b/src/backend/drivers/apps/AppDriver.js @@ -156,17 +156,17 @@ export class AppDriver extends PuterDriver { // Name conflict handling if (await this.appStore.existsByName(fields.name)) { if (options?.dedupe_name) { - const existingCount = await this.appStore.countByNamePrefix( - fields.name, - ); let candidate; - let n = existingCount + 1; + let i = 0; do { - candidate = `${fields.name}-${++n}`; - if (n > 10) + const randString = Math.random().toString(36).slice(2, 6); + candidate = `${fields.name}-${randString}`; + + if (i >= 3) throw new HttpError(400, 'Failed to dedupe app name', { - legacyCode: 'bad_request', + legacyCode: 'app_name_already_in_use', }); + i++; } while (await this.appStore.existsByName(candidate)); fields.name = candidate; } else { diff --git a/src/backend/stores/app/AppStore.js b/src/backend/stores/app/AppStore.js index 812eb699a0..753bf71f56 100644 --- a/src/backend/stores/app/AppStore.js +++ b/src/backend/stores/app/AppStore.js @@ -198,24 +198,6 @@ export class AppStore extends PuterStore { return rows.length > 0; } - async getCountByPrefix(prefix) { - const rows = await this.clients.db.read( - this.clients.db.case({ - sqlite: 'SELECT COUNT(*) as c FROM `apps` WHERE `name` = ? OR `name` GLOB ?', - otherwise: - 'SELECT COUNT(*) as c FROM `apps` WHERE `name` = ? OR `name` REGEXP ?', - }), - [ - prefix, - this.clients.db.case({ - sqlite: `${prefix}-[0-9]*`, - otherwise: `^${prefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}-[0-9]+$`, - }), - ], - ); - return rows.length > 0 ? Number(rows[0].c) : 0; - } - async existsByIndexUrl(indexUrl) { const rows = await this.clients.db.read( 'SELECT `id` FROM `apps` WHERE `index_url` = ? LIMIT 1',