54dbba9ea2
- 新增 ProtectedLink/LinkVisitRecord,/link/{slug} 门禁页 + 一次性令牌跳转
- 复用邮箱验证体系,验证码邮件区分下载/链接模板
- 控制台下载资源与受保护链接合并为「资源访问管理」单页(页签切换)
- 引用扫描支持 /link/ 链接,角色模板/设置/文档同步更新
- 版本 1.3.0
246 lines
6.6 KiB
TypeScript
246 lines
6.6 KiB
TypeScript
import { axiosInstance } from '@halo-dev/api-client';
|
||
import type {
|
||
DownloadResource,
|
||
DownloadResourceList,
|
||
DownloadResourceSpec,
|
||
DownloadRecord,
|
||
LinkVisitRecord,
|
||
ListResult,
|
||
ProtectedLink,
|
||
ProtectedLinkList,
|
||
ProtectedLinkSpec,
|
||
ReferencesMap,
|
||
} from '@/types';
|
||
|
||
export const API_BASE = '/apis/console.api.sharelink.halo.run/v1alpha1';
|
||
|
||
// 后端资源 VO 为扁平结构,这里统一归一化为 Halo 风格结构供组件使用
|
||
interface ResourceVo {
|
||
name: string;
|
||
slug: string;
|
||
displayName: string;
|
||
description?: string;
|
||
attachmentName?: string;
|
||
requireEmailVerify?: boolean;
|
||
enabled?: boolean;
|
||
downloadUrl?: string;
|
||
creationTimestamp?: string;
|
||
stats?: {
|
||
downloadCount?: number;
|
||
downloaderCount?: number;
|
||
referenceCount?: number;
|
||
};
|
||
}
|
||
|
||
function normalizeResource(vo: ResourceVo): DownloadResource {
|
||
return {
|
||
metadata: { name: vo.name, creationTimestamp: vo.creationTimestamp },
|
||
spec: {
|
||
slug: vo.slug,
|
||
displayName: vo.displayName,
|
||
description: vo.description,
|
||
attachmentName: vo.attachmentName,
|
||
requireEmailVerify: vo.requireEmailVerify,
|
||
enabled: vo.enabled,
|
||
},
|
||
status: { downloadCount: vo.stats?.downloadCount ?? 0 },
|
||
stats: {
|
||
downloaderCount: vo.stats?.downloaderCount ?? 0,
|
||
referenceCount: vo.stats?.referenceCount ?? 0,
|
||
},
|
||
};
|
||
}
|
||
|
||
export async function listResources(): Promise<DownloadResourceList> {
|
||
const { data } = await axiosInstance.get<ResourceVo[]>(
|
||
`${API_BASE}/download-resources`
|
||
);
|
||
const items = (data ?? []).map(normalizeResource);
|
||
return { items, total: items.length };
|
||
}
|
||
|
||
// 后端创建/更新均接收扁平的 spec 字段(ResourceRequest)
|
||
export async function createResource(
|
||
spec: DownloadResourceSpec
|
||
): Promise<DownloadResource> {
|
||
const { data } = await axiosInstance.post<ResourceVo>(
|
||
`${API_BASE}/download-resources`,
|
||
spec
|
||
);
|
||
return normalizeResource(data);
|
||
}
|
||
|
||
export async function getResource(name: string): Promise<DownloadResource> {
|
||
const { data } = await axiosInstance.get<ResourceVo>(
|
||
`${API_BASE}/download-resources/${name}`
|
||
);
|
||
return normalizeResource(data);
|
||
}
|
||
|
||
export async function updateResource(
|
||
name: string,
|
||
spec: DownloadResourceSpec
|
||
): Promise<DownloadResource> {
|
||
const { data } = await axiosInstance.put<ResourceVo>(
|
||
`${API_BASE}/download-resources/${name}`,
|
||
spec
|
||
);
|
||
return normalizeResource(data);
|
||
}
|
||
|
||
export async function deleteResource(name: string): Promise<void> {
|
||
await axiosInstance.delete(`${API_BASE}/download-resources/${name}`);
|
||
}
|
||
|
||
export async function listRecords(params: {
|
||
resourceSlug?: string;
|
||
page?: number;
|
||
size?: number;
|
||
}): Promise<ListResult<DownloadRecord>> {
|
||
const { data } = await axiosInstance.get<ListResult<DownloadRecord>>(
|
||
`${API_BASE}/download-records`,
|
||
{ params }
|
||
);
|
||
return data;
|
||
}
|
||
|
||
export async function deleteRecord(name: string): Promise<void> {
|
||
await axiosInstance.delete(`${API_BASE}/download-records/${name}`);
|
||
}
|
||
|
||
// CSV 导出走浏览器直接导航(console 端点接受 session cookie)
|
||
export function recordsExportUrl(resourceSlug?: string): string {
|
||
const query = resourceSlug
|
||
? `?resourceSlug=${encodeURIComponent(resourceSlug)}`
|
||
: '';
|
||
return `${API_BASE}/download-records/-/export${query}`;
|
||
}
|
||
|
||
export async function getReferences(): Promise<ReferencesMap> {
|
||
const { data } = await axiosInstance.get<ReferencesMap>(
|
||
`${API_BASE}/references`
|
||
);
|
||
return data;
|
||
}
|
||
|
||
export async function refreshReferences(): Promise<ReferencesMap> {
|
||
const { data } = await axiosInstance.post<ReferencesMap>(
|
||
`${API_BASE}/references/-/refresh`
|
||
);
|
||
return data;
|
||
}
|
||
|
||
// ---------- 受保护链接 ----------
|
||
|
||
interface LinkVo {
|
||
name: string;
|
||
slug: string;
|
||
displayName: string;
|
||
description?: string;
|
||
targetUrl?: string;
|
||
requireEmailVerify?: boolean;
|
||
enabled?: boolean;
|
||
visitUrl?: string;
|
||
creationTimestamp?: string;
|
||
stats?: {
|
||
visitCount?: number;
|
||
visitorCount?: number;
|
||
referenceCount?: number;
|
||
};
|
||
}
|
||
|
||
function normalizeLink(vo: LinkVo): ProtectedLink {
|
||
return {
|
||
metadata: { name: vo.name, creationTimestamp: vo.creationTimestamp },
|
||
spec: {
|
||
slug: vo.slug,
|
||
displayName: vo.displayName,
|
||
description: vo.description,
|
||
targetUrl: vo.targetUrl,
|
||
requireEmailVerify: vo.requireEmailVerify,
|
||
enabled: vo.enabled,
|
||
},
|
||
status: { visitCount: vo.stats?.visitCount ?? 0 },
|
||
stats: {
|
||
visitorCount: vo.stats?.visitorCount ?? 0,
|
||
referenceCount: vo.stats?.referenceCount ?? 0,
|
||
},
|
||
};
|
||
}
|
||
|
||
export async function listLinks(): Promise<ProtectedLinkList> {
|
||
const { data } = await axiosInstance.get<LinkVo[]>(
|
||
`${API_BASE}/protected-links`
|
||
);
|
||
const items = (data ?? []).map(normalizeLink);
|
||
return { items, total: items.length };
|
||
}
|
||
|
||
export async function createLink(spec: ProtectedLinkSpec): Promise<ProtectedLink> {
|
||
const { data } = await axiosInstance.post<LinkVo>(
|
||
`${API_BASE}/protected-links`,
|
||
spec
|
||
);
|
||
return normalizeLink(data);
|
||
}
|
||
|
||
export async function getLink(name: string): Promise<ProtectedLink> {
|
||
const { data } = await axiosInstance.get<LinkVo>(
|
||
`${API_BASE}/protected-links/${name}`
|
||
);
|
||
return normalizeLink(data);
|
||
}
|
||
|
||
export async function updateLink(
|
||
name: string,
|
||
spec: ProtectedLinkSpec
|
||
): Promise<ProtectedLink> {
|
||
const { data } = await axiosInstance.put<LinkVo>(
|
||
`${API_BASE}/protected-links/${name}`,
|
||
spec
|
||
);
|
||
return normalizeLink(data);
|
||
}
|
||
|
||
export async function deleteLink(name: string): Promise<void> {
|
||
await axiosInstance.delete(`${API_BASE}/protected-links/${name}`);
|
||
}
|
||
|
||
export async function listLinkRecords(params: {
|
||
linkSlug?: string;
|
||
page?: number;
|
||
size?: number;
|
||
}): Promise<ListResult<LinkVisitRecord>> {
|
||
const { data } = await axiosInstance.get<ListResult<LinkVisitRecord>>(
|
||
`${API_BASE}/link-visit-records`,
|
||
{ params }
|
||
);
|
||
return data;
|
||
}
|
||
|
||
export async function deleteLinkRecord(name: string): Promise<void> {
|
||
await axiosInstance.delete(`${API_BASE}/link-visit-records/${name}`);
|
||
}
|
||
|
||
// CSV 导出走浏览器直接导航(console 端点接受 session cookie)
|
||
export function linkRecordsExportUrl(linkSlug?: string): string {
|
||
const query = linkSlug
|
||
? `?linkSlug=${encodeURIComponent(linkSlug)}`
|
||
: '';
|
||
return `${API_BASE}/link-visit-records/-/export${query}`;
|
||
}
|
||
|
||
export async function getLinkReferences(): Promise<ReferencesMap> {
|
||
const { data } = await axiosInstance.get<ReferencesMap>(
|
||
`${API_BASE}/link-references`
|
||
);
|
||
return data;
|
||
}
|
||
|
||
export async function refreshLinkReferences(): Promise<ReferencesMap> {
|
||
const { data } = await axiosInstance.post<ReferencesMap>(
|
||
`${API_BASE}/link-references/-/refresh`
|
||
);
|
||
return data;
|
||
}
|