fix(version-service+gateway+app): fix APK download 404 and SHA-256 false failure

Three coordinated fixes to make in-app APK download work end-to-end:

1. version-service/main.ts: serve uploaded files as static assets via
   NestExpressApplication.useStaticAssets('/data/versions', prefix:
   '/downloads/versions'), so GET /downloads/versions/{platform}/{file}
   returns the actual APK stored in the Docker volume.

2. kong.yml: add /downloads/versions route to Kong so requests from
   the Flutter app can reach version-service through the API gateway.
   Previously only /api/v1/versions and /api/app/version were routed;
   the download URL returned by the check endpoint was unreachable (404).

3. download_manager.dart: skip SHA-256 verification when sha256Expected
   is empty string. The check endpoint always returns sha256:"" because
   version-service doesn't store file hashes. The previous code compared
   actual_hash == "" which always failed, causing the downloaded file to
   be deleted after a successful download.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-03-06 06:04:27 -08:00
parent 0f328b9794
commit e6f864d409
3 changed files with 23 additions and 10 deletions

View File

@ -118,18 +118,22 @@ class DownloadManager {
await tempFile.rename(savePath);
debugPrint('Download completed');
_status = DownloadStatus.verifying;
// SHA-256
final isValid = await _verifySha256(file, sha256Expected);
if (!isValid) {
debugPrint('SHA-256 verification failed');
await file.delete();
_status = DownloadStatus.failed;
return null;
// SHA-256
if (sha256Expected.isNotEmpty) {
_status = DownloadStatus.verifying;
final isValid = await _verifySha256(file, sha256Expected);
if (!isValid) {
debugPrint('SHA-256 verification failed');
await file.delete();
_status = DownloadStatus.failed;
return null;
}
debugPrint('SHA-256 verified');
} else {
debugPrint('SHA-256 not provided, skipping verification');
}
debugPrint('SHA-256 verified');
_status = DownloadStatus.completed;
return file;
} on DioException catch (e) {

View File

@ -124,6 +124,10 @@ services:
paths:
- /api/app/version
strip_path: false
- name: app-version-download-route
paths:
- /downloads/versions
strip_path: false
- name: billing-service
url: http://billing-service:3010

View File

@ -1,4 +1,5 @@
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { VersionModule } from './version.module';
@ -13,9 +14,13 @@ process.on('uncaughtException', (error) => {
});
async function bootstrap() {
const app = await NestFactory.create(VersionModule);
const app = await NestFactory.create<NestExpressApplication>(VersionModule);
const config = app.get(ConfigService);
const port = config.get<number>('VERSION_SERVICE_PORT', 3009);
// Serve uploaded APK/IPA files as static assets
// Files are stored at /data/versions/{platform}/filename
// Accessible via GET /downloads/versions/{platform}/filename
app.useStaticAssets('/data/versions', { prefix: '/downloads/versions' });
await app.listen(port);
logger.log(`version-service running on port ${port}`);
}