refactor(repo): consolidate openflare-server to root and move subprojects to internal/apps

- Merge all files inside openflare-server to the repository root directory.
- Relocate agent, relay, and flared subprojects from internal/ to internal/apps/.
- Combine docker-compose files and update build context paths to root.
- Update GitHub workflows and Dockerfiles to refer to new directories and package names.
- Rewrite Go package imports across all files.
- Resolve database renew test race condition and clean up docs.
This commit is contained in:
ryan
2026-06-19 14:23:29 +08:00
parent 19d476ed7f
commit 63cd906cfc
1064 changed files with 366 additions and 1397 deletions
@@ -0,0 +1,40 @@
import type {InternalAxiosRequestConfig} from 'axios';
import {BaseService} from '../core/base.service';
import type {FileStatsResponse, ListUploadsResponse} from './types';
export class AdminUploadService extends BaseService {
protected static readonly basePath = '/api/v1/admin/uploads';
static async listUploads(
page = 1,
pageSize = 20,
keyword?: string,
type?: string,
extension?: string,
): Promise<ListUploadsResponse> {
const params: Record<string, string | number> = { page, page_size: pageSize };
if (keyword) params.keyword = keyword;
if (type) params.type = type;
if (extension) params.extension = extension;
return this.get<ListUploadsResponse>('', params);
}
static async getFileStats(): Promise<FileStatsResponse> {
return this.get<FileStatsResponse>('/stats');
}
static async deleteFile(id: string): Promise<void> {
return this.delete<void>(`/${id}`);
}
static getDownloadUrl(id: string): string {
return `${this.basePath}/download/${id}`;
}
static async batchDownload(ids: string[]): Promise<Blob> {
return this.post<Blob>('/download/batch', { ids }, {
responseType: 'blob',
} as InternalAxiosRequestConfig);
}
}
+5
View File
@@ -0,0 +1,5 @@
export { UploadService } from './upload.service';
export { AdminUploadService } from './admin-upload.service';
export { formatFileSize, getFileUrl } from './utils';
export type { ImageQuality } from './utils';
export type { UploadImageResponse, Upload, ListUploadsResponse, FileStatsResponse } from './types';
+91
View File
@@ -0,0 +1,91 @@
/**
* 文件上传元数据
*/
export interface UploadMetadata {
width?: number
height?: number
duration?: number
original_mime?: string
user_agent?: string
client_ip?: string
bucket?: string
extra?: Record<string, unknown>
}
/**
* 上传记录
*/
export interface Upload {
id: string
user_id: string
file_name: string
file_path: string
file_size: number
mime_type: string
extension: string
hash: string
type: string
status: string
access_mode: number
metadata: UploadMetadata
created_at: string
updated_at: string
}
/**
* 上传接口响应(原 UploadImageResponse 兼容)
*/
export interface UploadImageResponse {
/** 上传记录 ID */
id: string
}
/**
* 文件列表查询参数
*/
export interface ListUploadsQuery {
page?: number
page_size?: number
type?: string
extension?: string
keyword?: string
}
/**
* 文件列表分页响应
*/
export interface ListUploadsResponse {
total: number
page: number
page_size: number
items: Upload[]
}
/**
* 近 7 天新增文件趋势项
*/
export interface TrendItem {
date: string
count: number
size: number
}
/**
* 文件分类/类型统计项
*/
export interface DistributionItem {
name: string
count: number
size: number
}
/**
* 文件统计信息响应
*/
export interface FileStatsResponse {
total_count: number
total_size: number
trend: TrendItem[]
categories: DistributionItem[]
types: DistributionItem[]
}
@@ -0,0 +1,78 @@
import type {InternalAxiosRequestConfig} from 'axios';
import {BaseService} from '../core/base.service';
import type {ListUploadsResponse, Upload, UploadImageResponse} from './types';
export class UploadService extends BaseService {
protected static readonly basePath = '/api/v1/upload';
static async uploadFile(
file: File,
type: string = 'generic',
metadata?: Record<string, unknown>,
accessMode?: number,
): Promise<Upload> {
const formData = new FormData();
formData.append('file', file);
formData.append('type', type);
if (metadata) {
formData.append('metadata', JSON.stringify(metadata));
}
if (accessMode !== undefined) {
formData.append('access_mode', String(accessMode));
}
return this.post<Upload>('', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
} as InternalAxiosRequestConfig);
}
static async uploadBase64Image(
base64: string,
type: string = 'generic',
filename: string = 'image.png',
accessMode?: number,
): Promise<UploadImageResponse> {
const response = await fetch(base64);
const blob = await response.blob();
const mimeType = base64.match(/data:([^;]+);/)?.[1] || 'image/png';
const file = new File([blob], filename, { type: mimeType });
const result = await this.uploadFile(file, type, undefined, accessMode);
return { id: result.id };
}
static async listMyUploads(
page = 1,
pageSize = 20,
keyword?: string,
type?: string,
extension?: string,
): Promise<ListUploadsResponse> {
const params: Record<string, string | number> = { page, page_size: pageSize };
if (keyword) params.keyword = keyword;
if (type) params.type = type;
if (extension) params.extension = extension;
return this.get<ListUploadsResponse>('/my', params);
}
static async deleteMyFile(id: string): Promise<void> {
return this.delete<void>(`/${id}`);
}
static async updateMyFile(id: string, fileName: string, accessMode?: number): Promise<Upload> {
return this.put<Upload>(`/${id}`, {
file_name: fileName,
access_mode: accessMode,
});
}
static async batchDownloadMyFiles(ids: string[]): Promise<Blob> {
return this.post<Blob>('/download/batch', { ids }, {
responseType: 'blob',
} as InternalAxiosRequestConfig);
}
static getDownloadUrl(id: string): string {
return `${this.basePath}/download/${id}`;
}
}
+18
View File
@@ -0,0 +1,18 @@
export type ImageQuality = 'low' | 'medium' | 'high' | 'origin';
export function getFileUrl(
id: string | number | null | undefined,
quality: ImageQuality = 'origin',
): string | null {
if (!id) return null;
if (quality === 'origin') return `/f/${id}`;
return `/f/${id}?quality=${quality}`;
}
export function formatFileSize(bytes: number): string {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
}