215 lines
6.7 KiB
TypeScript
215 lines
6.7 KiB
TypeScript
import {
|
|
Controller,
|
|
Get,
|
|
Post,
|
|
Put,
|
|
Delete,
|
|
Patch,
|
|
Param,
|
|
Query,
|
|
Body,
|
|
UploadedFile,
|
|
UseInterceptors,
|
|
NotFoundException,
|
|
Logger,
|
|
} from '@nestjs/common';
|
|
import { FileInterceptor } from '@nestjs/platform-express';
|
|
import { ConfigService } from '@nestjs/config';
|
|
import { diskStorage } from 'multer';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import { AppVersionRepository } from '../../../infrastructure/repositories/app-version.repository';
|
|
import { Platform } from '../../../domain/entities/app-version.entity';
|
|
import { CreateVersionDto } from '../../../application/dtos/create-version.dto';
|
|
import { UpdateVersionDto } from '../../../application/dtos/update-version.dto';
|
|
|
|
const UPLOAD_DIR = '/data/versions';
|
|
|
|
@Controller('api/v1/versions')
|
|
export class VersionController {
|
|
private readonly logger = new Logger(VersionController.name);
|
|
private readonly downloadBaseUrl: string;
|
|
|
|
constructor(
|
|
private readonly versionRepo: AppVersionRepository,
|
|
private readonly config: ConfigService,
|
|
) {
|
|
this.downloadBaseUrl = this.config.get<string>(
|
|
'DOWNLOAD_BASE_URL',
|
|
'https://it0api.szaiai.com/downloads/versions',
|
|
);
|
|
// Ensure upload directories exist
|
|
for (const p of [Platform.ANDROID, Platform.IOS]) {
|
|
const dir = path.join(UPLOAD_DIR, p.toLowerCase());
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
}
|
|
}
|
|
|
|
@Get()
|
|
async list(
|
|
@Query('platform') platform?: string,
|
|
@Query('includeDisabled') includeDisabled?: string,
|
|
) {
|
|
const filter: { platform?: Platform; includeDisabled?: boolean } = {};
|
|
if (platform) {
|
|
filter.platform = platform.toUpperCase() as Platform;
|
|
}
|
|
if (includeDisabled === 'true') {
|
|
filter.includeDisabled = true;
|
|
}
|
|
return this.versionRepo.findAll(filter);
|
|
}
|
|
|
|
@Get(':id')
|
|
async getById(@Param('id') id: string) {
|
|
const version = await this.versionRepo.findById(id);
|
|
if (!version) throw new NotFoundException('Version not found');
|
|
return version;
|
|
}
|
|
|
|
@Post()
|
|
async create(@Body() dto: CreateVersionDto) {
|
|
return this.versionRepo.create({
|
|
...dto,
|
|
releaseDate: dto.releaseDate ? new Date(dto.releaseDate) : undefined,
|
|
});
|
|
}
|
|
|
|
@Put(':id')
|
|
async update(@Param('id') id: string, @Body() dto: UpdateVersionDto) {
|
|
const existing = await this.versionRepo.findById(id);
|
|
if (!existing) throw new NotFoundException('Version not found');
|
|
return this.versionRepo.update(id, {
|
|
...dto,
|
|
releaseDate: dto.releaseDate ? new Date(dto.releaseDate) : undefined,
|
|
});
|
|
}
|
|
|
|
@Delete(':id')
|
|
async delete(@Param('id') id: string) {
|
|
const version = await this.versionRepo.findById(id);
|
|
if (!version) throw new NotFoundException('Version not found');
|
|
|
|
// Delete the associated file if it exists
|
|
if (version.downloadUrl) {
|
|
const platformDir = version.platform.toLowerCase();
|
|
const filename = path.basename(new URL(version.downloadUrl).pathname);
|
|
const filePath = path.join(UPLOAD_DIR, platformDir, filename);
|
|
if (fs.existsSync(filePath)) {
|
|
fs.unlinkSync(filePath);
|
|
this.logger.log(`Deleted file: ${filePath}`);
|
|
}
|
|
}
|
|
|
|
await this.versionRepo.delete(id);
|
|
return { deleted: true };
|
|
}
|
|
|
|
@Patch(':id/toggle')
|
|
async toggle(@Param('id') id: string, @Body('isEnabled') isEnabled: boolean) {
|
|
const existing = await this.versionRepo.findById(id);
|
|
if (!existing) throw new NotFoundException('Version not found');
|
|
return this.versionRepo.update(id, { isEnabled });
|
|
}
|
|
|
|
@Post('upload')
|
|
@UseInterceptors(
|
|
FileInterceptor('file', {
|
|
storage: diskStorage({
|
|
destination: (req, _file, cb) => {
|
|
const platform = (req.body.platform || 'ANDROID').toLowerCase();
|
|
const dir = path.join(UPLOAD_DIR, platform);
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
cb(null, dir);
|
|
},
|
|
filename: (_req, file, cb) => {
|
|
// Preserve original filename with timestamp prefix to avoid collisions
|
|
const timestamp = Date.now();
|
|
const safeName = file.originalname.replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
cb(null, `${timestamp}_${safeName}`);
|
|
},
|
|
}),
|
|
limits: { fileSize: 500 * 1024 * 1024 }, // 500 MB
|
|
}),
|
|
)
|
|
async upload(
|
|
@UploadedFile() file: Express.Multer.File,
|
|
@Body('platform') platform: string,
|
|
@Body('versionName') versionName: string,
|
|
@Body('buildNumber') buildNumber: string,
|
|
@Body('changelog') changelog?: string,
|
|
@Body('isForceUpdate') isForceUpdate?: string,
|
|
@Body('minOsVersion') minOsVersion?: string,
|
|
@Body('releaseDate') releaseDate?: string,
|
|
) {
|
|
const platformEnum = (platform || 'ANDROID').toUpperCase() as Platform;
|
|
const platformDir = platformEnum.toLowerCase();
|
|
const downloadUrl = `${this.downloadBaseUrl}/${platformDir}/${file.filename}`;
|
|
|
|
return this.versionRepo.create({
|
|
platform: platformEnum,
|
|
versionName,
|
|
buildNumber,
|
|
changelog: changelog || undefined,
|
|
downloadUrl,
|
|
fileSize: file.size,
|
|
isForceUpdate: isForceUpdate === 'true',
|
|
isEnabled: true,
|
|
minOsVersion: minOsVersion || undefined,
|
|
releaseDate: releaseDate ? new Date(releaseDate) : undefined,
|
|
});
|
|
}
|
|
|
|
@Post('parse')
|
|
@UseInterceptors(
|
|
FileInterceptor('file', {
|
|
storage: diskStorage({
|
|
destination: '/tmp',
|
|
filename: (_req, file, cb) => {
|
|
cb(null, `parse_${Date.now()}_${file.originalname}`);
|
|
},
|
|
}),
|
|
limits: { fileSize: 500 * 1024 * 1024 },
|
|
}),
|
|
)
|
|
async parsePackage(
|
|
@UploadedFile() file: Express.Multer.File,
|
|
@Body('platform') platform: string,
|
|
) {
|
|
try {
|
|
// Dynamic import for app-info-parser (CommonJS module)
|
|
const AppInfoParser = (await import('app-info-parser')).default;
|
|
const parser = new AppInfoParser(file.path);
|
|
const info = await parser.parse();
|
|
|
|
let result: { versionName?: string; versionCode?: string; minSdkVersion?: string } = {};
|
|
|
|
if (platform?.toUpperCase() === 'IOS') {
|
|
result = {
|
|
versionName: info.CFBundleShortVersionString,
|
|
versionCode: info.CFBundleVersion,
|
|
minSdkVersion: info.MinimumOSVersion,
|
|
};
|
|
} else {
|
|
result = {
|
|
versionName: info.versionName,
|
|
versionCode: String(info.versionCode),
|
|
minSdkVersion: info.usesSdk?.minSdkVersion
|
|
? String(info.usesSdk.minSdkVersion)
|
|
: undefined,
|
|
};
|
|
}
|
|
|
|
return result;
|
|
} catch (err) {
|
|
this.logger.warn(`Failed to parse package: ${(err as Error).message}`);
|
|
return { versionName: null, versionCode: null, minSdkVersion: null };
|
|
} finally {
|
|
// Clean up temp file
|
|
if (fs.existsSync(file.path)) {
|
|
fs.unlinkSync(file.path);
|
|
}
|
|
}
|
|
}
|
|
}
|