Harden media health checks and duration extraction
فشلت بعض الفحوصات
Deploy To Ghaymah / deploy (push) Has been cancelled
فشلت بعض الفحوصات
Deploy To Ghaymah / deploy (push) Has been cancelled
هذا الالتزام موجود في:
@@ -1,12 +1,9 @@
|
||||
import {
|
||||
BadGatewayException,
|
||||
Injectable,
|
||||
ServiceUnavailableException,
|
||||
} from '@nestjs/common';
|
||||
import { BadGatewayException, Injectable, ServiceUnavailableException } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { GoogleAuth } from 'google-auth-library';
|
||||
import { generateWaveformPeaksFromBuffer } from '../../common/utils/waveform.util';
|
||||
import { ManagedStorageService } from '../../infrastructure/storage/managed-storage.service';
|
||||
import { MediaProbeService } from '../../infrastructure/storage/media-probe.service';
|
||||
import { TextToMusicDto } from './dto/text-to-music.dto';
|
||||
|
||||
@Injectable()
|
||||
@@ -14,29 +11,84 @@ export class MediaService {
|
||||
constructor(
|
||||
private readonly configService: ConfigService,
|
||||
private readonly storageService: ManagedStorageService,
|
||||
private readonly mediaProbeService: MediaProbeService,
|
||||
) {}
|
||||
|
||||
async getMediaHealth() {
|
||||
const [storageHealth, ffmpeg, ffprobe] = await Promise.all([
|
||||
this.storageService.getHealth(),
|
||||
this.mediaProbeService.checkFfmpeg(),
|
||||
this.mediaProbeService.checkFfprobe(),
|
||||
]);
|
||||
const storageProvider =
|
||||
this.configService.get<string>('storage.provider', { infer: true }) ?? 'local';
|
||||
const publicBaseUrl = this.configService.get<string>('publicBaseUrl', { infer: true }) ?? '';
|
||||
const warnings: string[] = [];
|
||||
const imageProcessingEnabled =
|
||||
this.configService.get<boolean>('imageProcessing.enabled', { infer: true }) ?? false;
|
||||
const videoProcessingEnabled =
|
||||
this.configService.get<boolean>('videoProcessing.enabled', { infer: true }) ?? false;
|
||||
const videoHlsGenerationEnabled =
|
||||
this.configService.get<boolean>('videoProcessing.generateHls', { infer: true }) ?? true;
|
||||
const videoThumbnailGenerationEnabled =
|
||||
this.configService.get<boolean>('videoProcessing.generateThumbnails', { infer: true }) ??
|
||||
true;
|
||||
const audioProcessingEnabled =
|
||||
this.configService.get<boolean>('audioProcessing.enabled', { infer: true }) ?? false;
|
||||
|
||||
if (storageProvider === 'local') {
|
||||
warnings.push(
|
||||
'Local storage requires persistent volume mounted to /app/uploads in production',
|
||||
);
|
||||
}
|
||||
if (!publicBaseUrl) {
|
||||
warnings.push('PUBLIC_BASE_URL is not configured');
|
||||
}
|
||||
if ((imageProcessingEnabled || videoProcessingEnabled) && !ffmpeg.available) {
|
||||
warnings.push('ffmpeg is not available; image/video processing may fail');
|
||||
}
|
||||
if (!ffprobe.available) {
|
||||
warnings.push('ffprobe is not available; duration extraction may fail');
|
||||
}
|
||||
if (storageProvider === 's3' && !(storageHealth.isS3Configured ?? storageHealth.s3Configured)) {
|
||||
warnings.push('S3 provider selected but missing required env variables');
|
||||
}
|
||||
|
||||
const storageWritable =
|
||||
storageProvider !== 'local' || storageHealth.uploadPathWritable !== false;
|
||||
const status = !storageWritable ? 'error' : warnings.length ? 'warning' : 'ok';
|
||||
|
||||
return {
|
||||
storage: await this.storageService.getHealth(),
|
||||
status,
|
||||
storage: storageHealth,
|
||||
processing: {
|
||||
imageProcessingEnabled:
|
||||
this.configService.get<boolean>('imageProcessing.enabled', { infer: true }) ?? false,
|
||||
videoProcessingEnabled:
|
||||
this.configService.get<boolean>('videoProcessing.enabled', { infer: true }) ?? false,
|
||||
videoHlsGenerationEnabled:
|
||||
this.configService.get<boolean>('videoProcessing.generateHls', { infer: true }) ?? true,
|
||||
videoThumbnailGenerationEnabled:
|
||||
this.configService.get<boolean>('videoProcessing.generateThumbnails', { infer: true }) ??
|
||||
true,
|
||||
ffmpegPath:
|
||||
this.configService.get<string>('videoProcessing.ffmpegPath', { infer: true }) ?? 'ffmpeg',
|
||||
imageProcessingEnabled,
|
||||
videoProcessingEnabled,
|
||||
videoHlsGenerationEnabled,
|
||||
videoThumbnailGenerationEnabled,
|
||||
audioProcessingEnabled,
|
||||
ffmpegPath: ffmpeg.path,
|
||||
ffmpegAvailable: ffmpeg.available,
|
||||
ffmpegVersion: ffmpeg.version,
|
||||
ffprobePath: ffprobe.path,
|
||||
ffprobeAvailable: ffprobe.available,
|
||||
ffprobeVersion: ffprobe.version,
|
||||
ffmpeg,
|
||||
ffprobe,
|
||||
},
|
||||
serving: {
|
||||
rangeRequests: true,
|
||||
immutableCacheSeconds: 31536000,
|
||||
hlsManifestCacheSeconds: 300,
|
||||
},
|
||||
staticServing: {
|
||||
uploadsPublicPath:
|
||||
storageHealth.uploadsPublicPath ?? storageHealth.publicPath ?? '/uploads',
|
||||
rangeRequestsExpected: true,
|
||||
cacheHeadersExpected: true,
|
||||
hlsMimeExpected: 'application/vnd.apple.mpegurl',
|
||||
},
|
||||
warnings,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -67,7 +119,7 @@ export class MediaService {
|
||||
const client = await auth.getClient();
|
||||
const accessTokenRaw = await client.getAccessToken();
|
||||
const accessToken =
|
||||
typeof accessTokenRaw === 'string' ? accessTokenRaw : accessTokenRaw?.token ?? '';
|
||||
typeof accessTokenRaw === 'string' ? accessTokenRaw : (accessTokenRaw?.token ?? '');
|
||||
|
||||
if (!accessToken) {
|
||||
throw new ServiceUnavailableException('Failed to authenticate with Google Cloud');
|
||||
|
||||
المرجع في مشكلة جديدة
حظر مستخدم