Skip to content

Commit

Permalink
refactor: use metadata to combine update info & custom config
Browse files Browse the repository at this point in the history
  • Loading branch information
nicotsx committed Dec 23, 2024
1 parent 0b4c30d commit 9be2fae
Show file tree
Hide file tree
Showing 16 changed files with 57 additions and 69 deletions.
4 changes: 2 additions & 2 deletions packages/backend/src/app.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ export class AppController {
const apps = await this.marketplaceService.getAvailableApps();

const installedApps = await this.appsService.getInstalledApps();
const updatesAvailable = installedApps.filter(({ app, updateInfo }) => {
return Number(app.version) < Number(updateInfo?.latestVersion ?? 0) && app.status !== 'updating';
const updatesAvailable = installedApps.filter(({ app, metadata }) => {
return Number(app.version) < Number(metadata?.latestVersion ?? 0) && app.status !== 'updating';
});

return { version, userSettings, user: req.user as UserDto, apps, updatesAvailable: updatesAvailable.length };
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ export default async () => {
["./modules/links/dto/links.dto"]: await import("./modules/links/dto/links.dto"),
["./modules/system/dto/system.dto"]: await import("./modules/system/dto/system.dto")
};
return { "@nestjs/swagger": { "models": [[import("./modules/user/dto/user.dto"), { "UserDto": {} }], [import("./modules/marketplace/dto/marketplace.dto"), { "AppInfoSimpleDto": {}, "AppInfoDto": {}, "UpdateInfoDto": {}, "SearchAppsQueryDto": {}, "SearchAppsDto": {}, "AppDetailsDto": {}, "PullDto": {}, "AllAppStoresDto": {}, "UpdateAppStoreBodyDto": {}, "CreateAppStoreBodyDto": {} }], [import("./app.dto"), { "UserSettingsDto": {}, "PartialUserSettingsDto": {}, "AppContextDto": {}, "UserContextDto": {}, "AcknowledgeWelcomeBody": {} }], [import("./modules/queue/queue.entity"), { "Queue": {} }], [import("./modules/auth/dto/auth.dto"), { "LoginBody": {}, "VerifyTotpBody": {}, "LoginDto": {}, "RegisterBody": {}, "RegisterDto": {}, "ChangeUsernameBody": {}, "ChangePasswordBody": {}, "GetTotpUriBody": {}, "GetTotpUriDto": {}, "SetupTotpBody": {}, "DisableTotpBody": {}, "ResetPasswordBody": {}, "ResetPasswordDto": {}, "CheckResetPasswordRequestDto": {} }], [import("./modules/app-lifecycle/dto/app-lifecycle.dto"), { "AppFormBody": {}, "UninstallAppBody": {}, "UpdateAppBody": {} }], [import("./modules/apps/dto/app.dto"), { "AppDto": {}, "MyAppsDto": {}, "GuestAppsDto": {}, "GetAppDto": {} }], [import("./modules/backups/dto/backups.dto"), { "BackupDto": {}, "RestoreAppBackupDto": {}, "GetAppBackupsDto": {}, "GetAppBackupsQueryDto": {}, "DeleteAppBackupBodyDto": {} }], [import("./modules/links/dto/links.dto"), { "LinkBodyDto": {}, "EditLinkBodyDto": {}, "LinksDto": {} }], [import("./modules/system/dto/system.dto"), { "LoadDto": {} }]], "controllers": [[import("./app.controller"), { "AppController": { "userContext": { type: t["./app.dto"].UserContextDto }, "appContext": { type: t["./app.dto"].AppContextDto }, "updateUserSettings": {}, "acknowledgeWelcome": {} } }], [import("./modules/auth/auth.controller"), { "AuthController": { "login": { type: t["./modules/auth/dto/auth.dto"].LoginDto }, "verifyTotp": { type: t["./modules/auth/dto/auth.dto"].LoginDto }, "register": { type: t["./modules/auth/dto/auth.dto"].RegisterDto }, "logout": {}, "changeUsername": {}, "changePassword": {}, "getTotpUri": { type: t["./modules/auth/dto/auth.dto"].GetTotpUriDto }, "setupTotp": {}, "disableTotp": {}, "resetPassword": { type: t["./modules/auth/dto/auth.dto"].ResetPasswordDto }, "cancelResetPassword": {}, "checkResetPasswordRequest": { type: t["./modules/auth/dto/auth.dto"].CheckResetPasswordRequestDto } } }], [import("./modules/i18n/i18n.controller"), { "I18nController": { "getTranslation": { type: Object } } }], [import("./core/health/health.controller"), { "HealthController": { "check": { type: Object } } }], [import("./modules/marketplace/marketplace.controller"), { "MarketplaceController": { "searchApps": { type: t["./modules/marketplace/dto/marketplace.dto"].SearchAppsDto }, "getImage": {}, "pullAppStore": { type: t["./modules/marketplace/dto/marketplace.dto"].PullDto }, "createAppStore": {}, "getAllAppStores": { type: t["./modules/marketplace/dto/marketplace.dto"].AllAppStoresDto }, "getEnabledAppStores": { type: t["./modules/marketplace/dto/marketplace.dto"].AllAppStoresDto }, "updateAppStore": {}, "deleteAppStore": {} } }], [import("./modules/apps/apps.controller"), { "AppsController": { "getInstalledApps": { type: t["./modules/apps/dto/app.dto"].MyAppsDto }, "getGuestApps": { type: t["./modules/apps/dto/app.dto"].GuestAppsDto }, "getApp": { type: t["./modules/apps/dto/app.dto"].GetAppDto } } }], [import("./modules/backups/backups.controller"), { "BackupsController": { "backupApp": {}, "restoreAppBackup": {}, "getAppBackups": { type: t["./modules/backups/dto/backups.dto"].GetAppBackupsDto }, "deleteAppBackup": {} } }], [import("./modules/app-lifecycle/app-lifecycle.controller"), { "AppLifecycleController": { "installApp": {}, "startApp": {}, "stopApp": {}, "restartApp": {}, "uninstallApp": {}, "resetApp": {}, "updateApp": {}, "updateAllApps": {}, "updateAppConfig": {} } }], [import("./modules/links/links.controller"), { "LinksController": { "getLinks": { type: t["./modules/links/dto/links.dto"].LinksDto }, "createLink": {}, "editLink": {}, "deleteLink": {} } }], [import("./modules/system/system.controller"), { "SystemController": { "systemLoad": { type: t["./modules/system/dto/system.dto"].LoadDto }, "downloadLocalCertificate": {} } }]] } };
return { "@nestjs/swagger": { "models": [[import("./modules/user/dto/user.dto"), { "UserDto": {} }], [import("./modules/marketplace/dto/marketplace.dto"), { "AppInfoSimpleDto": {}, "AppInfoDto": {}, "MetadataDto": {}, "SearchAppsQueryDto": {}, "SearchAppsDto": {}, "AppDetailsDto": {}, "PullDto": {}, "AllAppStoresDto": {}, "UpdateAppStoreBodyDto": {}, "CreateAppStoreBodyDto": {} }], [import("./app.dto"), { "UserSettingsDto": {}, "PartialUserSettingsDto": {}, "AppContextDto": {}, "UserContextDto": {}, "AcknowledgeWelcomeBody": {} }], [import("./modules/queue/queue.entity"), { "Queue": {} }], [import("./modules/auth/dto/auth.dto"), { "LoginBody": {}, "VerifyTotpBody": {}, "LoginDto": {}, "RegisterBody": {}, "RegisterDto": {}, "ChangeUsernameBody": {}, "ChangePasswordBody": {}, "GetTotpUriBody": {}, "GetTotpUriDto": {}, "SetupTotpBody": {}, "DisableTotpBody": {}, "ResetPasswordBody": {}, "ResetPasswordDto": {}, "CheckResetPasswordRequestDto": {} }], [import("./modules/app-lifecycle/dto/app-lifecycle.dto"), { "AppFormBody": {}, "UninstallAppBody": {}, "UpdateAppBody": {} }], [import("./modules/apps/dto/app.dto"), { "AppDto": {}, "MyAppsDto": {}, "GuestAppsDto": {}, "GetAppDto": {} }], [import("./modules/backups/dto/backups.dto"), { "BackupDto": {}, "RestoreAppBackupDto": {}, "GetAppBackupsDto": {}, "GetAppBackupsQueryDto": {}, "DeleteAppBackupBodyDto": {} }], [import("./modules/links/dto/links.dto"), { "LinkBodyDto": {}, "EditLinkBodyDto": {}, "LinksDto": {} }], [import("./modules/system/dto/system.dto"), { "LoadDto": {} }]], "controllers": [[import("./app.controller"), { "AppController": { "userContext": { type: t["./app.dto"].UserContextDto }, "appContext": { type: t["./app.dto"].AppContextDto }, "updateUserSettings": {}, "acknowledgeWelcome": {} } }], [import("./modules/auth/auth.controller"), { "AuthController": { "login": { type: t["./modules/auth/dto/auth.dto"].LoginDto }, "verifyTotp": { type: t["./modules/auth/dto/auth.dto"].LoginDto }, "register": { type: t["./modules/auth/dto/auth.dto"].RegisterDto }, "logout": {}, "changeUsername": {}, "changePassword": {}, "getTotpUri": { type: t["./modules/auth/dto/auth.dto"].GetTotpUriDto }, "setupTotp": {}, "disableTotp": {}, "resetPassword": { type: t["./modules/auth/dto/auth.dto"].ResetPasswordDto }, "cancelResetPassword": {}, "checkResetPasswordRequest": { type: t["./modules/auth/dto/auth.dto"].CheckResetPasswordRequestDto } } }], [import("./modules/i18n/i18n.controller"), { "I18nController": { "getTranslation": { type: Object } } }], [import("./core/health/health.controller"), { "HealthController": { "check": { type: Object } } }], [import("./modules/marketplace/marketplace.controller"), { "MarketplaceController": { "searchApps": { type: t["./modules/marketplace/dto/marketplace.dto"].SearchAppsDto }, "getImage": {}, "pullAppStore": { type: t["./modules/marketplace/dto/marketplace.dto"].PullDto }, "createAppStore": {}, "getAllAppStores": { type: t["./modules/marketplace/dto/marketplace.dto"].AllAppStoresDto }, "getEnabledAppStores": { type: t["./modules/marketplace/dto/marketplace.dto"].AllAppStoresDto }, "updateAppStore": {}, "deleteAppStore": {} } }], [import("./modules/apps/apps.controller"), { "AppsController": { "getInstalledApps": { type: t["./modules/apps/dto/app.dto"].MyAppsDto }, "getGuestApps": { type: t["./modules/apps/dto/app.dto"].GuestAppsDto }, "getApp": { type: t["./modules/apps/dto/app.dto"].GetAppDto } } }], [import("./modules/backups/backups.controller"), { "BackupsController": { "backupApp": {}, "restoreAppBackup": {}, "getAppBackups": { type: t["./modules/backups/dto/backups.dto"].GetAppBackupsDto }, "deleteAppBackup": {} } }], [import("./modules/app-lifecycle/app-lifecycle.controller"), { "AppLifecycleController": { "installApp": {}, "startApp": {}, "stopApp": {}, "restartApp": {}, "uninstallApp": {}, "resetApp": {}, "updateApp": {}, "updateAllApps": {}, "updateAppConfig": {} } }], [import("./modules/links/links.controller"), { "LinksController": { "getLinks": { type: t["./modules/links/dto/links.dto"].LinksDto }, "createLink": {}, "editLink": {}, "deleteLink": {} } }], [import("./modules/system/system.controller"), { "SystemController": { "systemLoad": { type: t["./modules/system/dto/system.dto"].LoadDto }, "downloadLocalCertificate": {} } }]] } };
};
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ export class AppLifecycleService {

async updateAllApps() {
const installedApps = await this.appsService.getInstalledApps();
const availableUpdates = installedApps.filter(({ app, updateInfo }) => Number(app.version) < Number(updateInfo.latestVersion));
const availableUpdates = installedApps.filter(({ app, metadata }) => Number(app.version) < Number(metadata.latestVersion));

const updatePromises = availableUpdates.map(async ({ app }) => {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export class AppStoreFilesManager {

if (parsedConfig.success && parsedConfig.data.available) {
const description = (await this.filesystem.readTextFile(path.join(appRepoDir, 'metadata', 'description.md'))) ?? '';
return { ...parsedConfig.data, id: namespacedId, description, userConfig: false };
return { ...parsedConfig.data, id: namespacedId, description };
}
}
} catch (error) {
Expand Down
6 changes: 2 additions & 4 deletions packages/backend/src/modules/apps/app-files-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,8 @@ export class AppFilesManager {

if (parsedConfig.success && parsedConfig.data.available) {
const description = (await this.filesystem.readTextFile(path.join(appInstalledDir, 'metadata', 'description.md'))) ?? '';
const userCompose = await this.getUserComposeFile(id);
const userEnv = await this.getUserEnv(id);
const userConfig = userCompose.content != null || userEnv.content != null;
return { ...parsedConfig.data, id, description, userConfig };

return { ...parsedConfig.data, id, description };
}
}
} catch (error) {
Expand Down
13 changes: 11 additions & 2 deletions packages/backend/src/modules/apps/apps.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class AppsService {
this.logger.debug(`App ${app.id} not found in app files`);
return null;
}
return { app, info: appInfo, updateInfo };
return { app, info: appInfo, metadata: updateInfo };
});
}),
);
Expand Down Expand Up @@ -66,6 +66,10 @@ export class AppsService {

let info = await this.appFilesManager.getInstalledAppInfo(id);

const userCompose = await this.appFilesManager.getUserComposeFile(id);
const userEnv = await this.appFilesManager.getUserEnv(id);
const hasCustomConfig = Boolean(userCompose.content) || Boolean(userEnv.content);

if (!info) {
info = await this.marketplaceService.getAppInfoFromAppStore(id);
}
Expand All @@ -74,6 +78,11 @@ export class AppsService {
throw new TranslatableError('APP_ERROR_APP_NOT_FOUND');
}

return { app, info, updateInfo };
const metadata = {
hasCustomConfig,
...updateInfo,
};

return { app, info, metadata };
}
}
6 changes: 3 additions & 3 deletions packages/backend/src/modules/apps/dto/app.dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { APP_STATUS } from '@/core/database/drizzle/types';
import { AppInfoDto, AppInfoSimpleDto, UpdateInfoDto } from '@/modules/marketplace/dto/marketplace.dto';
import { AppInfoDto, AppInfoSimpleDto, MetadataDto } from '@/modules/marketplace/dto/marketplace.dto';
import { createZodDto } from 'nestjs-zod';
import { z } from 'zod';

Expand Down Expand Up @@ -27,7 +27,7 @@ export class MyAppsDto extends createZodDto(
z.object({
app: AppDto.schema,
info: AppInfoSimpleDto.schema,
updateInfo: UpdateInfoDto.schema,
metadata: MetadataDto.schema,
}),
),
}),
Expand All @@ -48,6 +48,6 @@ export class GetAppDto extends createZodDto(
z.object({
app: AppDto.schema.nullish(),
info: AppInfoDto.schema,
updateInfo: UpdateInfoDto.schema,
metadata: MetadataDto.schema,
}),
) {}
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,10 @@ export const appInfoSchema = z.object({
.refine((v) => v < Date.now())
.optional()
.default(0),
userConfig: z.boolean().default(false),
});

// Derived types
export type AppInfoInput = z.input<typeof appInfoSchema>;
export type AppInfo = z.output<typeof appInfoSchema>;
export type FormField = z.output<typeof formFieldSchema>;

Expand All @@ -101,14 +101,14 @@ export class AppInfoSimpleDto extends createZodDto(
created_at: true,
supported_architectures: true,
available: true,
userConfig: true,
}),
) {}

export class AppInfoDto extends createZodDto(appInfoSchema) {}

export class UpdateInfoDto extends createZodDto(
export class MetadataDto extends createZodDto(
z.object({
hasCustomConfig: z.boolean().optional(),
latestVersion: z.number(),
minTipiVersion: z.string().optional(),
latestDockerVersion: z.string().optional(),
Expand Down Expand Up @@ -137,7 +137,7 @@ export class SearchAppsDto extends createZodDto(
export class AppDetailsDto extends createZodDto(
z.object({
info: AppInfoDto.schema,
updateInfo: UpdateInfoDto.schema,
metadata: MetadataDto.schema,
}),
) {}

Expand Down
34 changes: 10 additions & 24 deletions packages/backend/src/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -1665,10 +1665,6 @@
},
"available": {
"type": "boolean"
},
"userConfig": {
"type": "boolean",
"default": false
}
},
"required": [
Expand Down Expand Up @@ -2118,10 +2114,6 @@
},
"available": {
"type": "boolean"
},
"userConfig": {
"type": "boolean",
"default": false
}
},
"required": [
Expand All @@ -2131,9 +2123,12 @@
"available"
]
},
"updateInfo": {
"metadata": {
"type": "object",
"properties": {
"hasCustomConfig": {
"type": "boolean"
},
"latestVersion": {
"type": "number"
},
Expand All @@ -2152,7 +2147,7 @@
"required": [
"app",
"info",
"updateInfo"
"metadata"
]
}
}
Expand Down Expand Up @@ -2464,10 +2459,6 @@
"minimum": 0,
"exclusiveMinimum": false,
"default": 0
},
"userConfig": {
"type": "boolean",
"default": false
}
},
"required": [
Expand Down Expand Up @@ -2792,10 +2783,6 @@
"minimum": 0,
"exclusiveMinimum": false,
"default": 0
},
"userConfig": {
"type": "boolean",
"default": false
}
},
"required": [
Expand All @@ -2809,9 +2796,12 @@
"source"
]
},
"updateInfo": {
"metadata": {
"type": "object",
"properties": {
"hasCustomConfig": {
"type": "boolean"
},
"latestVersion": {
"type": "number"
},
Expand All @@ -2829,7 +2819,7 @@
},
"required": [
"info",
"updateInfo"
"metadata"
]
},
"SearchAppsDto": {
Expand Down Expand Up @@ -2895,10 +2885,6 @@
},
"available": {
"type": "boolean"
},
"userConfig": {
"type": "boolean",
"default": false
}
},
"required": [
Expand Down
5 changes: 2 additions & 3 deletions packages/backend/src/tests/utils/create-app-in-store.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import fs from 'node:fs';
import path from 'node:path';
import { DATA_DIR } from '@/common/constants';
import type { AppInfo } from '@/modules/marketplace/dto/marketplace.dto';
import type { AppInfo, AppInfoInput } from '@/modules/marketplace/dto/marketplace.dto';
import { faker } from '@faker-js/faker';

export const createAppInStore = async (storeId: number, app: Partial<AppInfo> = {}) => {
const appInfo: AppInfo = {
const appInfo: AppInfoInput = {
id: faker.string.uuid(),
name: faker.lorem.words(2),
port: faker.number.int({ min: 1000, max: 9999 }),
Expand All @@ -29,7 +29,6 @@ export const createAppInStore = async (storeId: number, app: Partial<AppInfo> =
force_expose: false,
generate_vapid_keys: false,
form_fields: [],
userConfig: false,
...app,
};

Expand Down
11 changes: 4 additions & 7 deletions packages/frontend/src/api-client/types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ export type AppContextDto = {
created_at?: number;
supported_architectures?: Array<'arm64' | 'amd64'>;
available: boolean;
userConfig?: boolean;
}>;
updatesAvailable: number;
};
Expand Down Expand Up @@ -220,9 +219,9 @@ export type GetAppDto = {
min_tipi_version?: string;
created_at?: number;
updated_at?: number;
userConfig?: boolean;
};
updateInfo: {
metadata: {
hasCustomConfig?: boolean;
latestVersion: number;
minTipiVersion?: string;
latestDockerVersion?: string;
Expand Down Expand Up @@ -344,7 +343,6 @@ export type GuestAppsDto = {
min_tipi_version?: string;
created_at?: number;
updated_at?: number;
userConfig?: boolean;
};
}>;
};
Expand Down Expand Up @@ -442,9 +440,9 @@ export type MyAppsDto = {
created_at?: number;
supported_architectures?: Array<'arm64' | 'amd64'>;
available: boolean;
userConfig?: boolean;
};
updateInfo: {
metadata: {
hasCustomConfig?: boolean;
latestVersion: number;
minTipiVersion?: string;
latestDockerVersion?: string;
Expand Down Expand Up @@ -524,7 +522,6 @@ export type SearchAppsDto = {
created_at?: number;
supported_architectures?: Array<'arm64' | 'amd64'>;
available: boolean;
userConfig?: boolean;
}>;
nextCursor?: string;
total: number;
Expand Down
Loading

0 comments on commit 9be2fae

Please sign in to comment.