diff --git a/api/routes/share.py b/api/routes/share.py index 540eec6..192e1b5 100644 --- a/api/routes/share.py +++ b/api/routes/share.py @@ -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, diff --git a/services/share.py b/services/share.py index 88e77e0..d7e7e52 100644 --- a/services/share.py +++ b/services/share.py @@ -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() \ No newline at end of file +share_service = ShareService() diff --git a/web/src/api/share.ts b/web/src/api/share.ts index db89eae..5bc1c67 100644 --- a/web/src/api/share.ts +++ b/web/src/api/share.ts @@ -23,10 +23,15 @@ export interface ShareCreatePayload { password?: string; } +export interface ClearExpiredResult { + deleted_count: number; +} + export const shareApi = { create: (payload: ShareCreatePayload) => request('/shares', { method: 'POST', json: payload }), list: () => request('/shares'), remove: (shareId: number) => request(`/shares/${shareId}`, { method: 'DELETE' }), + clearExpired: () => request(`/shares/expired`, { method: 'DELETE' }), get: (token: string) => request(`/s/${token}`), verifyPassword: (token: string, password: string) => request(`/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; }, -}; \ No newline at end of file +}; diff --git a/web/src/i18n/locales/en.ts b/web/src/i18n/locales/en.ts index 13972c5..e306017 100644 --- a/web/src/i18n/locales/en.ts +++ b/web/src/i18n/locales/en.ts @@ -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', diff --git a/web/src/i18n/locales/zh.ts b/web/src/i18n/locales/zh.ts index f62a649..66932d2 100644 --- a/web/src/i18n/locales/zh.ts +++ b/web/src/i18n/locales/zh.ts @@ -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': '创建时间', diff --git a/web/src/pages/SharePage.tsx b/web/src/pages/SharePage.tsx index 35e077d..23870c2 100644 --- a/web/src/pages/SharePage.tsx +++ b/web/src/pages/SharePage.tsx @@ -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 ( {t('Refresh')}} + extra={ + + + + + + + } >