diff --git a/app-version.example.json b/app-version.example.json new file mode 100644 index 0000000..74d15d0 --- /dev/null +++ b/app-version.example.json @@ -0,0 +1,10 @@ +{ + "android": { + "version_name": "1.1", + "version_code": 2, + "download_url": "http://121.127.37.212:3000/downloads/radiola-latest.apk", + "force_update": false, + "sha256": "", + "notes": "Тёмные цветовые темы, фикс таймера сна, авто-обновление." + } +} diff --git a/src/app-version/app-version.controller.ts b/src/app-version/app-version.controller.ts new file mode 100644 index 0000000..5a751c1 --- /dev/null +++ b/src/app-version/app-version.controller.ts @@ -0,0 +1,49 @@ +import { Controller, Get, Logger } from '@nestjs/common'; +import { ApiTags, ApiOperation } from '@nestjs/swagger'; +import * as fs from 'fs'; +import * as path from 'path'; + +interface PlatformVersion { + version_name: string; + version_code: number; + download_url: string; + force_update: boolean; + sha256?: string; + notes?: string; +} + +/** + * Манифест последней версии приложения для авто-обновления (как в nkVPN). + * Читается с диска при каждом запросе, поэтому релиз = заменить APK в /downloads + * и отредактировать data/app-version.json — без пересборки бэкенда. + * Путь к файлу — env APP_VERSION_FILE, иначе data/app-version.json. + */ +@ApiTags('app-version') +@Controller('app-version') +export class AppVersionController { + private readonly logger = new Logger(AppVersionController.name); + private readonly file = + process.env.APP_VERSION_FILE || + path.join(process.cwd(), 'data', 'app-version.json'); + + @Get() + @ApiOperation({ summary: 'Манифест последней версии приложения' }) + getVersion(): { android: PlatformVersion } { + try { + const raw = fs.readFileSync(this.file, 'utf-8'); + return JSON.parse(raw) as { android: PlatformVersion }; + } catch (e) { + this.logger.warn(`app-version.json недоступен: ${(e as Error).message}`); + // Безопасный фолбэк: version_code 0 ⇒ установленное приложение (code ≥ 1) + // никогда не увидит «обновление», т.е. при сбое файла апдейт не навязываем. + return { + android: { + version_name: '0', + version_code: 0, + download_url: '', + force_update: false, + }, + }; + } + } +} diff --git a/src/app-version/app-version.module.ts b/src/app-version/app-version.module.ts new file mode 100644 index 0000000..dfb432d --- /dev/null +++ b/src/app-version/app-version.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { AppVersionController } from './app-version.controller'; + +@Module({ + controllers: [AppVersionController], +}) +export class AppVersionModule {} diff --git a/src/app.module.ts b/src/app.module.ts index 73fbf61..90369f3 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -8,6 +8,7 @@ import { UsersModule } from './users/users.module'; import { NowPlayingModule } from './now-playing/now-playing.module'; import { HealthCheckModule } from './health-check/health-check.module'; import { ChartsModule } from './charts/charts.module'; +import { AppVersionModule } from './app-version/app-version.module'; @Module({ imports: [ @@ -20,6 +21,7 @@ import { ChartsModule } from './charts/charts.module'; NowPlayingModule, HealthCheckModule, ChartsModule, + AppVersionModule, ], }) export class AppModule {} diff --git a/src/main.ts b/src/main.ts index 44d296e..5b64f27 100644 --- a/src/main.ts +++ b/src/main.ts @@ -16,6 +16,11 @@ async function bootstrap() { immutable: true, }); + // Раздача APK приложения для авто-обновления (/downloads/radiola-latest.apk). + const downloadsDir = + process.env.DOWNLOADS_DIR || join(process.cwd(), 'data', 'downloads'); + app.useStaticAssets(downloadsDir, { prefix: '/downloads/' }); + app.useGlobalPipes( new ValidationPipe({ whitelist: true,