Files
tools-show/server/src/modules/gitlab-storage/gitlab-storage.service.ts
dlandy 40be11adbf init
2026-03-27 10:18:26 +08:00

151 lines
4.9 KiB
TypeScript

import { HttpStatus, Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import type { ToolArtifact } from '@prisma/client';
import { createReadStream, existsSync } from 'fs';
import { mkdir, writeFile } from 'fs/promises';
import { dirname, resolve } from 'path';
import { Readable } from 'stream';
import { ERROR_CODES } from '../../common/constants/error-codes';
import { AppException } from '../../common/exceptions/app.exception';
export interface ArtifactDownloadStream {
stream: NodeJS.ReadableStream;
fileName: string;
mimeType?: string;
fileSize: number;
}
export interface ArtifactUploadInput {
toolId: string;
version: string;
fileName: string;
mimeType?: string;
buffer: Buffer;
}
export interface ArtifactUploadResult {
gitlabProjectId: number;
gitlabPackageName: string;
gitlabPackageVersion: string;
gitlabFilePath: string;
}
@Injectable()
export class GitlabStorageService {
constructor(private readonly configService: ConfigService) {}
async getArtifactStream(artifact: ToolArtifact): Promise<ArtifactDownloadStream> {
const gitlabApiBase = this.configService.get<string>('GITLAB_API_BASE');
const gitlabToken = this.configService.get<string>('GITLAB_TOKEN');
if (gitlabApiBase && gitlabToken && artifact.gitlabProjectId > 0) {
return this.downloadFromGitlab(artifact, gitlabApiBase, gitlabToken);
}
return this.readFromLocalStorage(artifact);
}
async uploadArtifact(input: ArtifactUploadInput): Promise<ArtifactUploadResult> {
const gitlabApiBase = this.configService.get<string>('GITLAB_API_BASE');
const gitlabToken = this.configService.get<string>('GITLAB_TOKEN');
const projectId = Number(this.configService.get<string>('GITLAB_PROJECT_ID', '0'));
const packagePrefix = this.configService.get<string>('GITLAB_PACKAGE_NAME_PREFIX', 'toolsshow');
const packageName = `${packagePrefix}/${input.toolId}`;
if (gitlabApiBase && gitlabToken && projectId > 0) {
const url = `${gitlabApiBase}/projects/${encodeURIComponent(
String(projectId),
)}/packages/generic/${encodeURIComponent(packageName)}/${encodeURIComponent(
input.version,
)}/${encodeURIComponent(input.fileName)}`;
const response = await fetch(url, {
method: 'PUT',
headers: {
'PRIVATE-TOKEN': gitlabToken,
'Content-Type': input.mimeType ?? 'application/octet-stream',
},
body: input.buffer as unknown as BodyInit,
});
if (!response.ok) {
throw new AppException(
ERROR_CODES.GITLAB_UPLOAD_FAILED,
'failed to upload artifact to GitLab',
HttpStatus.BAD_GATEWAY,
);
}
return {
gitlabProjectId: projectId,
gitlabPackageName: packageName,
gitlabPackageVersion: input.version,
gitlabFilePath: `${packageName}/${input.version}/${input.fileName}`,
};
}
const localRelativePath = `storage/uploads/${input.toolId}/${input.version}/${input.fileName}`;
const localAbsolutePath = resolve(process.cwd(), localRelativePath);
await mkdir(dirname(localAbsolutePath), { recursive: true });
await writeFile(localAbsolutePath, input.buffer);
return {
gitlabProjectId: 0,
gitlabPackageName: packageName,
gitlabPackageVersion: input.version,
gitlabFilePath: localRelativePath.replace(/\\/g, '/'),
};
}
private async downloadFromGitlab(
artifact: ToolArtifact,
gitlabApiBase: string,
gitlabToken: string,
): Promise<ArtifactDownloadStream> {
const url = `${gitlabApiBase}/projects/${encodeURIComponent(
String(artifact.gitlabProjectId),
)}/packages/generic/${encodeURIComponent(artifact.gitlabPackageName)}/${encodeURIComponent(
artifact.gitlabPackageVersion,
)}/${encodeURIComponent(artifact.fileName)}`;
const response = await fetch(url, {
headers: {
'PRIVATE-TOKEN': gitlabToken,
},
});
if (!response.ok || !response.body) {
throw new AppException(
ERROR_CODES.GITLAB_DOWNLOAD_FAILED,
'failed to download artifact from GitLab',
HttpStatus.BAD_GATEWAY,
);
}
return {
stream: Readable.fromWeb(response.body as any),
fileName: artifact.fileName,
mimeType: artifact.mimeType ?? undefined,
fileSize: artifact.fileSizeBytes,
};
}
private readFromLocalStorage(artifact: ToolArtifact): ArtifactDownloadStream {
const filePath = resolve(process.cwd(), artifact.gitlabFilePath);
if (!existsSync(filePath)) {
throw new AppException(
ERROR_CODES.GITLAB_DOWNLOAD_FAILED,
`artifact file not found: ${artifact.gitlabFilePath}`,
HttpStatus.NOT_FOUND,
);
}
return {
stream: createReadStream(filePath),
fileName: artifact.fileName,
mimeType: artifact.mimeType ?? undefined,
fileSize: artifact.fileSizeBytes,
};
}
}