feat: expand backend admin marketplace and scaling
فشلت بعض الفحوصات
/ deploy (push) Failing after 1m22s

هذا الالتزام موجود في:
2026-05-14 16:17:12 +03:00
الأصل 0e76a4a9fc
التزام 5bd5e19a89
158 ملفات معدلة مع 19563 إضافات و3315 حذوفات

عرض الملف

@@ -1,4 +1,133 @@
import { Injectable } 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 { TextToMusicDto } from './dto/text-to-music.dto';
@Injectable()
export class MediaService {}
export class MediaService {
constructor(
private readonly configService: ConfigService,
private readonly storageService: ManagedStorageService,
) {}
async generateMusicFromText(userId: string, dto: TextToMusicDto) {
const enabled = this.configService.get<boolean>('aiMusic.enabled', { infer: true });
if (!enabled) {
throw new ServiceUnavailableException('AI music generation is disabled');
}
const apiKey = this.configService.get<string>('aiMusic.apiKey', { infer: true }) ?? '';
const projectId = this.configService.get<string>('aiMusic.projectId', { infer: true }) ?? '';
const location = this.configService.get<string>('aiMusic.location', { infer: true }) ?? '';
const model = this.configService.get<string>('aiMusic.model', { infer: true }) ?? 'lyria-002';
if (!projectId || !location) {
throw new ServiceUnavailableException('AI music settings are not configured');
}
let url = `https://${location}-aiplatform.googleapis.com/v1/projects/${projectId}/locations/${location}/publishers/google/models/${model}:predict`;
let authorizationHeader: string | null = null;
if (apiKey) {
url = `${url}?key=${encodeURIComponent(apiKey)}`;
} else {
const auth = new GoogleAuth({
scopes: ['https://www.googleapis.com/auth/cloud-platform'],
});
const client = await auth.getClient();
const accessTokenRaw = await client.getAccessToken();
const accessToken =
typeof accessTokenRaw === 'string' ? accessTokenRaw : accessTokenRaw?.token ?? '';
if (!accessToken) {
throw new ServiceUnavailableException('Failed to authenticate with Google Cloud');
}
authorizationHeader = `Bearer ${accessToken}`;
}
const requestBody: Record<string, unknown> = {
instances: [{ prompt: dto.prompt }],
parameters: {
sampleCount: 1,
durationSeconds: dto.durationSeconds ?? 12,
},
};
if (typeof dto.seed === 'number') {
(requestBody.parameters as Record<string, unknown>).seed = dto.seed;
}
const response = await fetch(url, {
method: 'POST',
headers: {
...(authorizationHeader ? { Authorization: authorizationHeader } : {}),
'Content-Type': 'application/json',
},
body: JSON.stringify(requestBody),
});
const result = (await response.json()) as {
error?: { message?: string };
predictions?: Array<{
bytesBase64Encoded?: string;
mimeType?: string;
audio?: { bytesBase64Encoded?: string; mimeType?: string };
}>;
};
if (!response.ok) {
throw new BadGatewayException(
result?.error?.message ?? 'Google AI returned an unexpected error',
);
}
const first = result?.predictions?.[0];
const audioBase64 = first?.bytesBase64Encoded ?? first?.audio?.bytesBase64Encoded ?? '';
const mimeType = first?.mimeType ?? first?.audio?.mimeType ?? 'audio/wav';
if (!audioBase64) {
throw new BadGatewayException('Google AI did not return audio content');
}
const extension = this.resolveAudioExtension(mimeType);
const buffer = Buffer.from(audioBase64, 'base64');
const audioUrl = await this.storageService.saveFile({
folderSegments: ['ai-music'],
extension: `.${extension}`,
buffer,
contentType: mimeType,
fileNamePrefix: `ai-${userId}`,
});
return {
prompt: dto.prompt,
durationSeconds: dto.durationSeconds ?? 12,
mimeType,
sizeBytes: buffer.length,
audioUrl,
waveformPeaks: generateWaveformPeaksFromBuffer(buffer),
};
}
private resolveAudioExtension(mimeType: string): string {
if (mimeType.includes('mpeg') || mimeType.includes('mp3')) {
return 'mp3';
}
if (mimeType.includes('ogg')) {
return 'ogg';
}
if (mimeType.includes('aac')) {
return 'aac';
}
if (mimeType.includes('wav')) {
return 'wav';
}
return 'wav';
}
}