feat: rename revokeInvite to deleteInvite and update related functionality

This commit is contained in:
shuaiplus
2026-06-29 11:47:05 +08:00
parent 4378e1b430
commit f82dcc3c17
15 changed files with 71 additions and 39 deletions
+7 -5
View File
@@ -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 });
}
+2 -2
View File
@@ -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);
+3 -4
View File
@@ -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<boolean> {
const now = new Date().toISOString();
export async function deleteInvite(db: D1Database, code: string): Promise<boolean> {
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;
}
+3 -3
View File
@@ -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<boolean> {
return revokeStoredInvite(this.db, code);
async deleteInvite(code: string): Promise<boolean> {
return deleteStoredInvite(this.db, code);
}
async deleteAllInvites(): Promise<number> {
+1 -1
View File
@@ -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),
+4 -6
View File
@@ -16,7 +16,7 @@ interface AdminPageProps {
onDeleteAllInvites: () => Promise<void>;
onToggleUserStatus: (userId: string, currentStatus: 'active' | 'banned') => Promise<void>;
onDeleteUser: (userId: string) => Promise<void>;
onRevokeInvite: (code: string) => Promise<void>;
onDeleteInvite: (code: string) => Promise<void>;
}
export default function AdminPage(props: AdminPageProps) {
@@ -184,11 +184,9 @@ export default function AdminPage(props: AdminPageProps) {
>
<Clipboard size={14} className="btn-icon" /> {t('txt_copy_link')}
</button>
{invite.status === 'active' && (
<button type="button" className="btn btn-danger" onClick={() => void props.onRevokeInvite(invite.code)}>
<Trash2 size={14} className="btn-icon" /> {t('txt_revoke')}
</button>
)}
<button type="button" className="btn btn-danger" onClick={() => void props.onDeleteInvite(invite.code)}>
<Trash2 size={14} className="btn-icon" /> {t('txt_delete')}
</button>
</div>
</td>
</tr>
+2 -2
View File
@@ -140,7 +140,7 @@ export interface AppMainRoutesProps {
onDeleteAllInvites: () => Promise<void>;
onToggleUserStatus: (userId: string, status: 'active' | 'banned') => Promise<void>;
onDeleteUser: (userId: string) => Promise<void>;
onRevokeInvite: (code: string) => Promise<void>;
onDeleteInvite: (code: string) => Promise<void>;
onLoadAuditLogs: (filters: AuditLogFilters) => Promise<AuditLogListResult>;
onLoadAuditLogSettings: () => Promise<AuditLogSettings>;
onSaveAuditLogSettings: (settings: AuditLogSettings) => Promise<AuditLogSettings>;
@@ -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}
/>
</Suspense>
</div>
+19 -9
View File
@@ -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() {
+2 -2
View File
@@ -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<void> {
export async function deleteInvite(authedFetch: AuthedFetch, code: string): Promise<void> {
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<void> {
+3 -5
View File
@@ -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) => {
+5
View File
@@ -582,8 +582,12 @@ const en: Record<string, string> = {
"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<string, string> = {
"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",
+5
View File
@@ -582,8 +582,12 @@ const es: Record<string, string> = {
"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<string, string> = {
"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",
+5
View File
@@ -582,8 +582,12 @@ const ru: Record<string, string> = {
"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<string, string> = {
"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",
+5
View File
@@ -582,8 +582,12 @@ const zhCN: Record<string, string> = {
"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<string, string> = {
"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": "删除用户",
+5
View File
@@ -582,8 +582,12 @@ const zhTW: Record<string, string> = {
"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<string, string> = {
"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": "刪除使用者",