diff --git a/src/handlers/admin.ts b/src/handlers/admin.ts index cc216c5..e3f4384 100644 --- a/src/handlers/admin.ts +++ b/src/handlers/admin.ts @@ -249,7 +249,7 @@ export async function handleAdminListInvites( } // DELETE /api/admin/invites/:code -export async function handleAdminRevokeInvite( +export async function handleAdminDeleteInvite( request: Request, env: Env, actorUser: User, @@ -260,12 +260,14 @@ export async function handleAdminRevokeInvite( } const storage = new StorageService(env.DB); - const revoked = await storage.revokeInvite(code); - if (!revoked) { - return errorResponse('Invite not found or already inactive', 404); + const deleted = await storage.deleteInvite(code); + if (!deleted) { + return errorResponse('Invite not found', 404); } - await writeAuditLog(storage, actorUser.id, 'admin.invite.revoke', 'invite', null, null, request); + await writeAuditLog(storage, actorUser.id, 'admin.invite.delete', 'invite', null, { + code, + }, request); return new Response(null, { status: 204 }); } diff --git a/src/router-admin.ts b/src/router-admin.ts index 71e9d8b..c1774b3 100644 --- a/src/router-admin.ts +++ b/src/router-admin.ts @@ -4,7 +4,7 @@ import { handleAdminCreateInvite, handleAdminListInvites, handleAdminDeleteAllInvites, - handleAdminRevokeInvite, + handleAdminDeleteInvite, handleAdminSetUserStatus, handleAdminDeleteUser, handleAdminListAuditLogs, @@ -52,7 +52,7 @@ export async function handleAdminRoute( const adminInviteMatch = path.match(/^\/api\/admin\/invites\/([^/]+)$/i); if (adminInviteMatch && method === 'DELETE') { const inviteCode = decodeURIComponent(adminInviteMatch[1]); - return handleAdminRevokeInvite(request, env, actorUser, inviteCode); + return handleAdminDeleteInvite(request, env, actorUser, inviteCode); } const adminUserStatusMatch = path.match(/^\/api\/admin\/users\/([a-f0-9-]+)\/status$/i); diff --git a/src/services/storage-admin-repo.ts b/src/services/storage-admin-repo.ts index 9019c9d..fd29188 100644 --- a/src/services/storage-admin-repo.ts +++ b/src/services/storage-admin-repo.ts @@ -151,11 +151,10 @@ export async function revertInviteUsed(db: D1Database, code: string, userId: str return (result.meta.changes ?? 0) > 0; } -export async function revokeInvite(db: D1Database, code: string): Promise { - const now = new Date().toISOString(); +export async function deleteInvite(db: D1Database, code: string): Promise { const result = await db - .prepare("UPDATE invites SET status = 'revoked', updated_at = ? WHERE code = ? AND status = 'active'") - .bind(now, code) + .prepare('DELETE FROM invites WHERE code = ?') + .bind(code) .run(); return (result.meta.changes ?? 0) > 0; } diff --git a/src/services/storage.ts b/src/services/storage.ts index a6ed61f..cb295d6 100644 --- a/src/services/storage.ts +++ b/src/services/storage.ts @@ -24,6 +24,7 @@ import { clearAuditLogs as clearStoredAuditLogs, assignInviteUsedBy as assignStoredInviteUsedBy, createInvite as createStoredInvite, + deleteInvite as deleteStoredInvite, deleteAllInvites as deleteStoredInvites, getInvite as findStoredInvite, listAuditLogs as listStoredAuditLogs, @@ -32,7 +33,6 @@ import { pruneAuditLogs as pruneStoredAuditLogs, pruneAuditLogsToMax as pruneStoredAuditLogsToMax, revertInviteUsed as revertStoredInviteUsed, - revokeInvite as revokeStoredInvite, } from './storage-admin-repo'; import { bulkDeleteFolders as deleteStoredFolders, @@ -329,8 +329,8 @@ export class StorageService { return revertStoredInviteUsed(this.db, code, userId); } - async revokeInvite(code: string): Promise { - return revokeStoredInvite(this.db, code); + async deleteInvite(code: string): Promise { + return deleteStoredInvite(this.db, code); } async deleteAllInvites(): Promise { diff --git a/webapp/src/App.tsx b/webapp/src/App.tsx index 4541a71..282a818 100644 --- a/webapp/src/App.tsx +++ b/webapp/src/App.tsx @@ -2039,7 +2039,7 @@ export default function App() { onDeleteAllInvites: adminActions.deleteAllInvites, onToggleUserStatus: adminActions.toggleUserStatus, onDeleteUser: adminActions.deleteUser, - onRevokeInvite: adminActions.revokeInvite, + onDeleteInvite: adminActions.deleteInvite, onLoadAuditLogs: (filters: AuditLogFilters) => listAuditLogs(authedFetch, filters), onLoadAuditLogSettings: () => getAuditLogSettings(authedFetch), onSaveAuditLogSettings: (settings: AuditLogSettings) => saveAuditLogSettings(authedFetch, settings), diff --git a/webapp/src/components/AdminPage.tsx b/webapp/src/components/AdminPage.tsx index 10c19bd..844cf53 100644 --- a/webapp/src/components/AdminPage.tsx +++ b/webapp/src/components/AdminPage.tsx @@ -16,7 +16,7 @@ interface AdminPageProps { onDeleteAllInvites: () => Promise; onToggleUserStatus: (userId: string, currentStatus: 'active' | 'banned') => Promise; onDeleteUser: (userId: string) => Promise; - onRevokeInvite: (code: string) => Promise; + onDeleteInvite: (code: string) => Promise; } export default function AdminPage(props: AdminPageProps) { @@ -184,11 +184,9 @@ export default function AdminPage(props: AdminPageProps) { > {t('txt_copy_link')} - {invite.status === 'active' && ( - - )} + diff --git a/webapp/src/components/AppMainRoutes.tsx b/webapp/src/components/AppMainRoutes.tsx index a4e6db0..6557b87 100644 --- a/webapp/src/components/AppMainRoutes.tsx +++ b/webapp/src/components/AppMainRoutes.tsx @@ -140,7 +140,7 @@ export interface AppMainRoutesProps { onDeleteAllInvites: () => Promise; onToggleUserStatus: (userId: string, status: 'active' | 'banned') => Promise; onDeleteUser: (userId: string) => Promise; - onRevokeInvite: (code: string) => Promise; + onDeleteInvite: (code: string) => Promise; onLoadAuditLogs: (filters: AuditLogFilters) => Promise; onLoadAuditLogSettings: () => Promise; onSaveAuditLogSettings: (settings: AuditLogSettings) => Promise; @@ -416,7 +416,7 @@ export default function AppMainRoutes(props: AppMainRoutesProps) { onDeleteAllInvites={props.onDeleteAllInvites} onToggleUserStatus={props.onToggleUserStatus} onDeleteUser={props.onDeleteUser} - onRevokeInvite={props.onRevokeInvite} + onDeleteInvite={props.onDeleteInvite} /> diff --git a/webapp/src/hooks/useAdminActions.ts b/webapp/src/hooks/useAdminActions.ts index 6f59c66..28dc524 100644 --- a/webapp/src/hooks/useAdminActions.ts +++ b/webapp/src/hooks/useAdminActions.ts @@ -1,5 +1,5 @@ import { useMemo } from 'preact/hooks'; -import { createInvite, deleteAllInvites, deleteUser, revokeInvite, setUserStatus } from '@/lib/api/admin'; +import { createInvite, deleteAllInvites, deleteInvite, deleteUser, setUserStatus } from '@/lib/api/admin'; import { t } from '@/lib/i18n'; import type { AppConfirmState } from '@/components/AppGlobalOverlays'; import type { AuthedFetch } from '@/lib/api/shared'; @@ -45,14 +45,24 @@ export default function useAdminActions(options: UseAdminActionsOptions) { } }, - async revokeInvite(code: string) { - try { - await revokeInvite(authedFetch, code); - await refetchInvites(); - onNotify('success', t('txt_invite_revoked')); - } catch (error) { - onNotify('error', error instanceof Error ? error.message : t('txt_revoke_invite_failed')); - } + async deleteInvite(code: string) { + onSetConfirm({ + title: t('txt_delete_invite'), + message: t('txt_delete_invite_confirm_message'), + danger: true, + onConfirm: () => { + onSetConfirm(null); + void (async () => { + try { + await deleteInvite(authedFetch, code); + await refetchInvites(); + onNotify('success', t('txt_invite_deleted')); + } catch (error) { + onNotify('error', error instanceof Error ? error.message : t('txt_delete_invite_failed')); + } + })(); + }, + }); }, async deleteAllInvites() { diff --git a/webapp/src/lib/api/admin.ts b/webapp/src/lib/api/admin.ts index e7e4f61..6fdda43 100644 --- a/webapp/src/lib/api/admin.ts +++ b/webapp/src/lib/api/admin.ts @@ -24,9 +24,9 @@ export async function createInvite(authedFetch: AuthedFetch, hours: number): Pro if (!resp.ok) throw new Error('Create invite failed'); } -export async function revokeInvite(authedFetch: AuthedFetch, code: string): Promise { +export async function deleteInvite(authedFetch: AuthedFetch, code: string): Promise { const resp = await authedFetch(`/api/admin/invites/${encodeURIComponent(code)}`, { method: 'DELETE' }); - if (!resp.ok) throw new Error('Revoke invite failed'); + if (!resp.ok) throw new Error('Delete invite failed'); } export async function deleteAllInvites(authedFetch: AuthedFetch): Promise { diff --git a/webapp/src/lib/demo.ts b/webapp/src/lib/demo.ts index ae0b4c3..8f7b4aa 100644 --- a/webapp/src/lib/demo.ts +++ b/webapp/src/lib/demo.ts @@ -1141,11 +1141,9 @@ export function createDemoMainRoutesProps(base: AppMainRoutesProps, notify: Noti state.setUsers((prev) => prev.filter((user) => user.id !== userId)); notify('success', t('txt_user_deleted')); }, - onRevokeInvite: async (code) => { - state.setInvites((prev) => prev.map((invite) => ( - invite.code === code ? { ...invite, status: 'inactive' } : invite - ))); - notify('success', t('txt_invite_revoked')); + onDeleteInvite: async (code) => { + state.setInvites((prev) => prev.filter((invite) => invite.code !== code)); + notify('success', t('txt_invite_deleted')); }, onLoadAuditLogSettings: async () => ({ retentionDays: 90, maxEntries: null }), onSaveAuditLogSettings: async (settings) => { diff --git a/webapp/src/lib/i18n/locales/en.ts b/webapp/src/lib/i18n/locales/en.ts index fd14b8a..cbfeab6 100644 --- a/webapp/src/lib/i18n/locales/en.ts +++ b/webapp/src/lib/i18n/locales/en.ts @@ -582,8 +582,12 @@ const en: Record = { "txt_identity_details": "Identity Details", "txt_ie_browser": "IE Browser", "txt_create_invite_failed": "Failed to create invite", + "txt_delete_invite": "Delete invite", + "txt_delete_invite_confirm_message": "Delete this invite code? This cannot be undone.", + "txt_delete_invite_failed": "Failed to delete invite", "txt_invite_code_required": "Invite Code (Required)", "txt_invite_created": "Invite created", + "txt_invite_deleted": "Invite deleted", "txt_invite_revoked": "Invite revoked", "txt_revoke_invite_failed": "Failed to revoke invite", "txt_invite_validity_hours": "Invite validity (hours)", @@ -1156,6 +1160,7 @@ const en: Record = { "txt_log_action_admin_backup_settings_repair": "Repair backup settings", "txt_log_action_admin_backup_settings_update": "Update backup settings", "txt_log_action_admin_invite_create": "Create invite", + "txt_log_action_admin_invite_delete": "Delete invite", "txt_log_action_admin_invite_delete_all": "Clear invites", "txt_log_action_admin_invite_revoke": "Revoke invite", "txt_log_action_admin_user_delete": "Delete user", diff --git a/webapp/src/lib/i18n/locales/es.ts b/webapp/src/lib/i18n/locales/es.ts index c644be3..19dffe6 100644 --- a/webapp/src/lib/i18n/locales/es.ts +++ b/webapp/src/lib/i18n/locales/es.ts @@ -582,8 +582,12 @@ const es: Record = { "txt_identity_details": "Detalles de identidad", "txt_ie_browser": "Navegador Internet Explorer", "txt_create_invite_failed": "Error al crear invitación", + "txt_delete_invite": "Eliminar invitación", + "txt_delete_invite_confirm_message": "¿Eliminar este código de invitación? Esta acción no se puede deshacer.", + "txt_delete_invite_failed": "Error al eliminar invitación", "txt_invite_code_required": "Código de invitación (obligatorio)", "txt_invite_created": "Invitación creada", + "txt_invite_deleted": "Invitación eliminada", "txt_invite_revoked": "Invitación revocada", "txt_revoke_invite_failed": "Error al revocar invitación", "txt_invite_validity_hours": "Validez de la invitación en horas", @@ -1156,6 +1160,7 @@ const es: Record = { "txt_log_action_admin_backup_settings_repair": "Repair backup settings", "txt_log_action_admin_backup_settings_update": "Update backup settings", "txt_log_action_admin_invite_create": "Create invite", + "txt_log_action_admin_invite_delete": "Delete invite", "txt_log_action_admin_invite_delete_all": "Clear invites", "txt_log_action_admin_invite_revoke": "Revoke invite", "txt_log_action_admin_user_delete": "Delete user", diff --git a/webapp/src/lib/i18n/locales/ru.ts b/webapp/src/lib/i18n/locales/ru.ts index 80039b6..e61de66 100644 --- a/webapp/src/lib/i18n/locales/ru.ts +++ b/webapp/src/lib/i18n/locales/ru.ts @@ -582,8 +582,12 @@ const ru: Record = { "txt_identity_details": "Данные личности", "txt_ie_browser": "IE-браузер", "txt_create_invite_failed": "Не удалось создать приглашение", + "txt_delete_invite": "Удалить приглашение", + "txt_delete_invite_confirm_message": "Удалить этот пригласительный код? Это действие нельзя отменить.", + "txt_delete_invite_failed": "Не удалось удалить приглашение", "txt_invite_code_required": "Пригласительный код (обязательно)", "txt_invite_created": "Приглашение создано", + "txt_invite_deleted": "Приглашение удалено", "txt_invite_revoked": "Приглашение отозвано", "txt_revoke_invite_failed": "Не удалось отозвать приглашение", "txt_invite_validity_hours": "Срок действия приглашения (часы)", @@ -1156,6 +1160,7 @@ const ru: Record = { "txt_log_action_admin_backup_settings_repair": "Repair backup settings", "txt_log_action_admin_backup_settings_update": "Update backup settings", "txt_log_action_admin_invite_create": "Create invite", + "txt_log_action_admin_invite_delete": "Delete invite", "txt_log_action_admin_invite_delete_all": "Clear invites", "txt_log_action_admin_invite_revoke": "Revoke invite", "txt_log_action_admin_user_delete": "Delete user", diff --git a/webapp/src/lib/i18n/locales/zh-CN.ts b/webapp/src/lib/i18n/locales/zh-CN.ts index 044e177..514e8e1 100644 --- a/webapp/src/lib/i18n/locales/zh-CN.ts +++ b/webapp/src/lib/i18n/locales/zh-CN.ts @@ -582,8 +582,12 @@ const zhCN: Record = { "txt_identity_details": "身份详情", "txt_ie_browser": "IE 浏览器", "txt_create_invite_failed": "创建邀请码失败", + "txt_delete_invite": "删除邀请码", + "txt_delete_invite_confirm_message": "确定删除该邀请码吗?删除后无法恢复。", + "txt_delete_invite_failed": "删除邀请码失败", "txt_invite_code_required": "邀请码(必填)", "txt_invite_created": "邀请码已创建", + "txt_invite_deleted": "邀请码已删除", "txt_invite_revoked": "邀请码已撤销", "txt_revoke_invite_failed": "撤销邀请码失败", "txt_invite_validity_hours": "邀请码有效期(小时)", @@ -1156,6 +1160,7 @@ const zhCN: Record = { "txt_log_action_admin_backup_settings_repair": "修复备份设置", "txt_log_action_admin_backup_settings_update": "更新备份设置", "txt_log_action_admin_invite_create": "创建邀请", + "txt_log_action_admin_invite_delete": "删除邀请", "txt_log_action_admin_invite_delete_all": "清空邀请", "txt_log_action_admin_invite_revoke": "撤销邀请", "txt_log_action_admin_user_delete": "删除用户", diff --git a/webapp/src/lib/i18n/locales/zh-TW.ts b/webapp/src/lib/i18n/locales/zh-TW.ts index b321d28..a2752b5 100644 --- a/webapp/src/lib/i18n/locales/zh-TW.ts +++ b/webapp/src/lib/i18n/locales/zh-TW.ts @@ -582,8 +582,12 @@ const zhTW: Record = { "txt_identity_details": "身份詳情", "txt_ie_browser": "IE 瀏覽器", "txt_create_invite_failed": "創建邀請碼失敗", + "txt_delete_invite": "刪除邀請碼", + "txt_delete_invite_confirm_message": "確定刪除此邀請碼嗎?刪除後無法復原。", + "txt_delete_invite_failed": "刪除邀請碼失敗", "txt_invite_code_required": "邀請碼(必填)", "txt_invite_created": "邀請碼已創建", + "txt_invite_deleted": "邀請碼已刪除", "txt_invite_revoked": "邀請碼已撤銷", "txt_revoke_invite_failed": "撤銷邀請碼失敗", "txt_invite_validity_hours": "邀請碼有效期(小時)", @@ -1156,6 +1160,7 @@ const zhTW: Record = { "txt_log_action_admin_backup_settings_repair": "修復備份設定", "txt_log_action_admin_backup_settings_update": "更新備份設定", "txt_log_action_admin_invite_create": "建立邀請", + "txt_log_action_admin_invite_delete": "刪除邀請", "txt_log_action_admin_invite_delete_all": "清空邀請", "txt_log_action_admin_invite_revoke": "撤銷邀請", "txt_log_action_admin_user_delete": "刪除使用者",