feat: add is_private to apps (#2546)
Docker Image CI / build-and-push-image (push) Has been cancelled
Maintain Release Merge PR / update-release-pr (push) Has been cancelled
release-please / release-please (push) Has been cancelled
test / test-backend (24.x) (push) Has been cancelled
test / API tests (node env, api-test) (24.x) (push) Has been cancelled
test / puterjs (node env, vitest) (24.x) (push) Has been cancelled

This commit is contained in:
Daniel Salazar
2026-02-25 13:26:16 -08:00
committed by GitHub
parent c4346df24e
commit aa04dfabb4
5 changed files with 41 additions and 23 deletions
@@ -276,6 +276,7 @@ export default class AppService extends BaseService {
'approved_for_opening_items',
'approved_for_incentive_program',
'godmode',
'is_private',
];
static WRITE_ALL_OWNER_PERMISSION = 'system:es:write-all-owners';
@@ -379,6 +380,7 @@ export default class AppService extends BaseService {
app.description = row.description;
app.godmode = as_bool(row.godmode);
app.icon = row.icon;
app.is_private = as_bool(row.is_private);
app.index_url = row.index_url;
app.maximize_on_start = as_bool(row.maximize_on_start);
app.metadata = row.metadata;
@@ -500,6 +502,7 @@ export default class AppService extends BaseService {
app.description = row.description;
app.godmode = as_bool(row.godmode);
app.icon = row.icon;
app.is_private = as_bool(row.is_private);
app.index_url = row.index_url;
app.maximize_on_start = as_bool(row.maximize_on_start);
app.metadata = row.metadata;
@@ -78,6 +78,7 @@ describe('AppService', () => {
approved_for_opening_items: 1,
background: 0,
godmode: 0,
is_private: 0,
maximize_on_start: 0,
protected: 0,
owner_user_id: 1,
@@ -253,6 +254,7 @@ describe('AppService', () => {
approved_for_opening_items: 0,
background: '0',
godmode: 1,
is_private: '1',
maximize_on_start: '1',
protected: 0,
});
@@ -266,6 +268,7 @@ describe('AppService', () => {
expect(result.approved_for_opening_items).toBe(false);
expect(result.background).toBe(false);
expect(result.godmode).toBe(true);
expect(result.is_private).toBe(true);
expect(result.maximize_on_start).toBe(true);
expect(result.protected).toBe(false);
});
@@ -829,6 +832,7 @@ describe('AppService', () => {
index_url: 'https://example.com',
approved_for_listing: true, // read_only field
godmode: true, // read_only field
is_private: true, // read_only field
},
});
@@ -836,6 +840,7 @@ describe('AppService', () => {
const writeCall = mockDbWrite.write.mock.calls[0];
expect(writeCall[0]).not.toContain('approved_for_listing');
expect(writeCall[0]).not.toContain('godmode');
expect(writeCall[0]).not.toContain('is_private');
});
it('should handle name conflict with dedupe_name option', async () => {
@@ -1253,12 +1258,14 @@ describe('AppService', () => {
title: 'Updated',
approved_for_listing: true,
godmode: true,
is_private: true,
},
});
const writeCall = mockDbWrite.write.mock.calls[0];
expect(writeCall[0]).not.toContain('approved_for_listing');
expect(writeCall[0]).not.toContain('godmode');
expect(writeCall[0]).not.toContain('is_private');
});
it('should handle name change with conflict', async () => {
+4
View File
@@ -121,6 +121,10 @@ module.exports = {
protected: {
type: 'flag',
},
is_private: {
type: 'flag',
read_only: true,
},
// OPERATIONS
last_review: {
@@ -173,6 +173,9 @@ class SqliteDatabaseAccessService extends BaseDatabaseAccessService {
[41, [
'0045_user_oidc_providers.sql',
]],
[42, [
'0046_is-private-apps.sql',
]],
];
// Database upgrade logic
@@ -218,31 +221,31 @@ class SqliteDatabaseAccessService extends BaseDatabaseAccessService {
const basename = path_.basename(filename);
const contents = fs.readFileSync(filename, 'utf8');
switch ( path_.extname(filename) ) {
case '.sql':
{
const stmts = contents.split(/;\s*\n/);
for ( let i = 0; i < stmts.length; i++ ) {
if ( stmts[i].trim() === '' ) continue;
const stmt = `${stmts[i] };`;
try {
this.db.exec(stmt);
} catch ( e ) {
throw new CompositeError(`failed to apply: ${basename} at line ${i}`, e);
case '.sql':
{
const stmts = contents.split(/;\s*\n/);
for ( let i = 0; i < stmts.length; i++ ) {
if ( stmts[i].trim() === '' ) continue;
const stmt = `${stmts[i] };`;
try {
this.db.exec(stmt);
} catch ( e ) {
throw new CompositeError(`failed to apply: ${basename} at line ${i}`, e);
}
}
break;
}
break;
}
case '.js':
try {
await this.run_js_migration_({
filename, contents,
});
} catch ( e ) {
throw new CompositeError(`failed to apply: ${basename}`, e);
}
break;
default:
throw new Error(`unrecognized migration type: ${filename}`);
case '.js':
try {
await this.run_js_migration_({
filename, contents,
});
} catch ( e ) {
throw new CompositeError(`failed to apply: ${basename}`, e);
}
break;
default:
throw new Error(`unrecognized migration type: ${filename}`);
}
}
@@ -0,0 +1 @@
ALTER TABLE apps ADD COLUMN "is_private" tinyint(1) DEFAULT '0';