feat: add expired share cleanup functionality

This commit is contained in:
shiyu
2025-09-14 16:27:46 +08:00
parent 7b25e6d3b6
commit 0732b611a9
6 changed files with 54 additions and 4 deletions

View File

@@ -83,6 +83,18 @@ async def get_my_shares(current_user: User = Depends(get_current_active_user)):
return [ShareInfo.from_orm(s) for s in shares]
@router.delete("/expired")
async def delete_expired_shares(
current_user: User = Depends(get_current_active_user),
):
"""
删除当前用户的所有已过期分享。
"""
user_account = await UserAccount.get(id=current_user.id)
deleted_count = await share_service.delete_expired_shares(user=user_account)
return success({"deleted_count": deleted_count})
@router.delete("/{share_id}")
async def delete_share(
share_id: int,

View File

@@ -90,6 +90,16 @@ class ShareService:
raise HTTPException(status_code=404, detail="分享链接不存在")
await share.delete()
@staticmethod
async def delete_expired_shares(user: UserAccount) -> int:
"""
删除当前用户所有已过期的分享链接,返回删除数量。
条件expires_at 非空 且 小于等于当前时间UTC
"""
now = datetime.now(timezone.utc)
deleted_count = await ShareLink.filter(user=user, expires_at__lte=now).delete()
return deleted_count
@staticmethod
async def get_shared_item_details(share: ShareLink, sub_path: str = ""):
"""
@@ -122,4 +132,4 @@ class ShareService:
raise e
share_service = ShareService()
share_service = ShareService()

View File

@@ -23,10 +23,15 @@ export interface ShareCreatePayload {
password?: string;
}
export interface ClearExpiredResult {
deleted_count: number;
}
export const shareApi = {
create: (payload: ShareCreatePayload) => request<ShareInfoWithPassword>('/shares', { method: 'POST', json: payload }),
list: () => request<ShareInfo[]>('/shares'),
remove: (shareId: number) => request<void>(`/shares/${shareId}`, { method: 'DELETE' }),
clearExpired: () => request<ClearExpiredResult>(`/shares/expired`, { method: 'DELETE' }),
get: (token: string) => request<ShareInfo>(`/s/${token}`),
verifyPassword: (token: string, password: string) => request<void>(`/s/${token}/verify`, { method: 'POST', json: { password } }),
listDir: (token: string, path: string = '/', password?: string) => {
@@ -40,4 +45,4 @@ export const shareApi = {
const url = `${API_BASE_URL}/s/${token}/download?path=${encodeURIComponent(path)}`;
return password ? `${url}&password=${encodeURIComponent(password)}` : url;
},
};
};

View File

@@ -53,6 +53,9 @@ export const en = {
'Cancel failed': 'Cancel failed',
'Load failed': 'Load failed',
'Are you sure to cancel share?': 'Are you sure to cancel share?',
'Clear expired shares': 'Clear expired shares',
'Confirm clear expired shares?': 'Confirm clear expired shares?',
'Cleared {count} expired shares': 'Cleared {count} expired shares',
'Share Name': 'Share Name',
'Share Content': 'Share Content',

View File

@@ -25,7 +25,7 @@ export const zh = {
'Account Settings': '账户设置',
'Language': '语言',
'Chinese': '中文',
'English': '英文',
'English': 'English',
'Full Name': '昵称',
'Email': '邮箱',
'Change Password': '修改密码',
@@ -57,6 +57,9 @@ export const zh = {
'Cancel failed': '取消失败',
'Load failed': '加载失败',
'Are you sure to cancel share?': '确认取消分享?',
'Clear expired shares': '清空过期分享',
'Confirm clear expired shares?': '确认清空过期分享?',
'Cleared {count} expired shares': '已清理 {count} 个过期分享',
'Share Name': '分享名称',
'Share Content': '分享内容',
'Created At': '创建时间',

View File

@@ -44,6 +44,16 @@ const SharePage = memo(function SharePage() {
}
};
const handleClearExpired = async () => {
try {
const res = await shareApi.clearExpired();
message.success(t('Cleared {count} expired shares', { count: String(res.deleted_count) }));
fetchList();
} catch (e: any) {
message.error(e.message || t('Clear failed'));
}
};
const columns = [
{
title: t('Share Name'),
@@ -100,7 +110,14 @@ const SharePage = memo(function SharePage() {
return (
<PageCard
title={t('My Shares')}
extra={<Button onClick={fetchList} loading={loading}>{t('Refresh')}</Button>}
extra={
<Space>
<Button onClick={fetchList} loading={loading}>{t('Refresh')}</Button>
<Popconfirm title={t('Confirm clear expired shares?')} onConfirm={handleClearExpired}>
<Button danger>{t('Clear expired shares')}</Button>
</Popconfirm>
</Space>
}
>
<Table
rowKey="id"