diff --git a/backend/app/api/admin_catalog.py b/backend/app/api/admin_catalog.py index d795953..9209de6 100644 --- a/backend/app/api/admin_catalog.py +++ b/backend/app/api/admin_catalog.py @@ -1,12 +1,14 @@ import secrets from fastapi import APIRouter, Depends, HTTPException, Query, status -from sqlalchemy import select +from sqlalchemy import delete, select from sqlalchemy.orm import Session from app.core.keygen import generate_key from app.core.config import get_settings from app.db.session import get_db from app.models.api_client import ApiClient +from app.models.permission_group_member import PermissionGroupMember +from app.models.permission_group_permission import PermissionGroupPermission from app.repositories.companies_repo import CompaniesRepository from app.repositories.modules_repo import ModulesRepository from app.repositories.permission_groups_repo import PermissionGroupsRepository @@ -195,6 +197,21 @@ def update_system( return SystemItem(id=row.id, system_key=row.system_key, name=row.name, status=row.status) +@router.delete("/systems/{system_key}") +def delete_system( + system_key: str, + db: Session = Depends(get_db), +) -> dict[str, int | str]: + repo = SystemsRepository(db) + row = repo.get_by_key(system_key) + if not row: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="system_not_found") + db.execute(delete(PermissionGroupPermission).where(PermissionGroupPermission.system == system_key)) + db.delete(row) + db.commit() + return {"deleted": 1, "result": "deleted"} + + @router.get("/modules") def list_modules( db: Session = Depends(get_db), @@ -253,6 +270,21 @@ def update_module( return ModuleItem(id=row.id, system_key=row.system_key, module_key=row.module_key, name=row.name, status=row.status) +@router.delete("/modules/{module_key}") +def delete_module( + module_key: str, + db: Session = Depends(get_db), +) -> dict[str, int | str]: + modules_repo = ModulesRepository(db) + row = modules_repo.get_by_key(module_key) + if not row: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="module_not_found") + db.execute(delete(PermissionGroupPermission).where(PermissionGroupPermission.module == module_key)) + db.delete(row) + db.commit() + return {"deleted": 1, "result": "deleted"} + + @router.get("/systems/{system_key}/groups") def list_system_groups( system_key: str, @@ -374,6 +406,30 @@ def update_company( return CompanyItem(id=row.id, company_key=row.company_key, name=row.name, status=row.status) +@router.delete("/companies/{company_key}") +def delete_company( + company_key: str, + db: Session = Depends(get_db), +) -> dict[str, int | str]: + companies_repo = CompaniesRepository(db) + sites_repo = SitesRepository(db) + company = companies_repo.get_by_key(company_key) + if not company: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="company_not_found") + company_sites, _ = sites_repo.list(company_id=company.id, limit=10000, offset=0) + company_site_keys = [s.site_key for s in company_sites] + if company_site_keys: + db.execute( + delete(PermissionGroupPermission).where( + PermissionGroupPermission.scope_type == "site", + PermissionGroupPermission.scope_id.in_(company_site_keys), + ) + ) + db.delete(company) + db.commit() + return {"deleted": 1, "result": "deleted"} + + @router.get("/companies/{company_key}/sites") def list_company_sites( company_key: str, @@ -478,6 +534,26 @@ def update_site( return SiteItem(id=row.id, site_key=row.site_key, company_key=company_key, name=row.name, status=row.status) +@router.delete("/sites/{site_key}") +def delete_site( + site_key: str, + db: Session = Depends(get_db), +) -> dict[str, int | str]: + sites_repo = SitesRepository(db) + row = sites_repo.get_by_key(site_key) + if not row: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="site_not_found") + db.execute( + delete(PermissionGroupPermission).where( + PermissionGroupPermission.scope_type == "site", + PermissionGroupPermission.scope_id == site_key, + ) + ) + db.delete(row) + db.commit() + return {"deleted": 1, "result": "deleted"} + + @router.get("/members") def list_members( db: Session = Depends(get_db), @@ -595,6 +671,21 @@ def update_member( ) +@router.delete("/members/{authentik_sub}") +def delete_member( + authentik_sub: str, + db: Session = Depends(get_db), +) -> dict[str, int | str]: + users_repo = UsersRepository(db) + row = users_repo.get_by_sub(authentik_sub) + if not row: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="user_not_found") + db.execute(delete(PermissionGroupMember).where(PermissionGroupMember.authentik_sub == authentik_sub)) + db.delete(row) + db.commit() + return {"deleted": 1, "result": "deleted"} + + @router.post("/members/{authentik_sub}/password/reset", response_model=MemberPasswordResetResponse) def reset_member_password( authentik_sub: str, @@ -759,6 +850,19 @@ def rotate_api_client_key( return ApiClientRotateKeyResponse(client_key=row.client_key, api_key=api_key) +@router.delete("/api-clients/{client_key}") +def delete_api_client( + client_key: str, + db: Session = Depends(get_db), +) -> dict[str, int | str]: + row = db.scalar(select(ApiClient).where(ApiClient.client_key == client_key)) + if not row: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="api_client_not_found") + db.delete(row) + db.commit() + return {"deleted": 1, "result": "deleted"} + + @router.get("/permission-groups") def list_permission_groups( db: Session = Depends(get_db), @@ -898,6 +1002,20 @@ def update_permission_group( return PermissionGroupItem(id=row.id, group_key=row.group_key, name=row.name, status=row.status) +@router.delete("/permission-groups/{group_key}") +def delete_permission_group( + group_key: str, + db: Session = Depends(get_db), +) -> dict[str, int | str]: + repo = PermissionGroupsRepository(db) + row = repo.get_by_key(group_key) + if not row: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="group_not_found") + db.delete(row) + db.commit() + return {"deleted": 1, "result": "deleted"} + + @router.post("/permission-groups/{group_key}/members/{authentik_sub}") def add_group_member( group_key: str, diff --git a/frontend/src/api/api-clients.js b/frontend/src/api/api-clients.js index 48eceb3..6ec6760 100644 --- a/frontend/src/api/api-clients.js +++ b/frontend/src/api/api-clients.js @@ -4,3 +4,4 @@ export const getApiClients = (params) => adminHttp.get('/admin/api-clients', { p export const createApiClient = (data) => adminHttp.post('/admin/api-clients', data) export const updateApiClient = (clientKey, data) => adminHttp.patch(`/admin/api-clients/${clientKey}`, data) export const rotateApiClientKey = (clientKey) => adminHttp.post(`/admin/api-clients/${clientKey}/rotate-key`) +export const deleteApiClient = (clientKey) => adminHttp.delete(`/admin/api-clients/${clientKey}`) diff --git a/frontend/src/api/companies.js b/frontend/src/api/companies.js index 732817c..a5d7268 100644 --- a/frontend/src/api/companies.js +++ b/frontend/src/api/companies.js @@ -3,4 +3,5 @@ import { adminHttp } from './http' export const getCompanies = () => adminHttp.get('/admin/companies') export const createCompany = (data) => adminHttp.post('/admin/companies', data) export const updateCompany = (companyKey, data) => adminHttp.patch(`/admin/companies/${companyKey}`, data) +export const deleteCompany = (companyKey) => adminHttp.delete(`/admin/companies/${companyKey}`) export const getCompanySites = (companyKey) => adminHttp.get(`/admin/companies/${companyKey}/sites`) diff --git a/frontend/src/api/members.js b/frontend/src/api/members.js index 5105673..d7a5b10 100644 --- a/frontend/src/api/members.js +++ b/frontend/src/api/members.js @@ -3,6 +3,7 @@ import { adminHttp } from './http' export const getMembers = () => adminHttp.get('/admin/members') export const upsertMember = (data) => adminHttp.post('/admin/members/upsert', data) export const updateMember = (authentikSub, data) => adminHttp.patch(`/admin/members/${authentikSub}`, data) +export const deleteMember = (authentikSub) => adminHttp.delete(`/admin/members/${authentikSub}`) export const resetMemberPassword = (authentikSub) => adminHttp.post(`/admin/members/${authentikSub}/password/reset`) export const getMemberPermissionGroups = (authentikSub) => adminHttp.get(`/admin/members/${authentikSub}/permission-groups`) export const setMemberPermissionGroups = (authentikSub, groupKeys) => diff --git a/frontend/src/api/modules.js b/frontend/src/api/modules.js index 2e48669..44ee34d 100644 --- a/frontend/src/api/modules.js +++ b/frontend/src/api/modules.js @@ -3,5 +3,6 @@ import { adminHttp } from './http' export const getModules = () => adminHttp.get('/admin/modules') export const createModule = (data) => adminHttp.post('/admin/modules', data) export const updateModule = (moduleKey, data) => adminHttp.patch(`/admin/modules/${moduleKey}`, data) +export const deleteModule = (moduleKey) => adminHttp.delete(`/admin/modules/${moduleKey}`) export const getModuleGroups = (moduleKey) => adminHttp.get(`/admin/modules/${moduleKey}/groups`) export const getModuleMembers = (moduleKey) => adminHttp.get(`/admin/modules/${moduleKey}/members`) diff --git a/frontend/src/api/permission-groups.js b/frontend/src/api/permission-groups.js index 9d5a238..b30d4f2 100644 --- a/frontend/src/api/permission-groups.js +++ b/frontend/src/api/permission-groups.js @@ -3,6 +3,7 @@ import { adminHttp } from './http' export const getPermissionGroups = () => adminHttp.get('/admin/permission-groups') export const createPermissionGroup = (data) => adminHttp.post('/admin/permission-groups', data) export const updatePermissionGroup = (groupKey, data) => adminHttp.patch(`/admin/permission-groups/${groupKey}`, data) +export const deletePermissionGroup = (groupKey) => adminHttp.delete(`/admin/permission-groups/${groupKey}`) export const getPermissionGroupPermissions = (groupKey) => adminHttp.get(`/admin/permission-groups/${groupKey}/permissions`) export const getPermissionGroupBindings = (groupKey) => adminHttp.get(`/admin/permission-groups/${groupKey}/bindings`) export const updatePermissionGroupBindings = (groupKey, data) => diff --git a/frontend/src/api/sites.js b/frontend/src/api/sites.js index 7cfa055..9556165 100644 --- a/frontend/src/api/sites.js +++ b/frontend/src/api/sites.js @@ -3,3 +3,4 @@ import { adminHttp } from './http' export const getSites = () => adminHttp.get('/admin/sites') export const createSite = (data) => adminHttp.post('/admin/sites', data) export const updateSite = (siteKey, data) => adminHttp.patch(`/admin/sites/${siteKey}`, data) +export const deleteSite = (siteKey) => adminHttp.delete(`/admin/sites/${siteKey}`) diff --git a/frontend/src/api/systems.js b/frontend/src/api/systems.js index 3e87b58..486a96f 100644 --- a/frontend/src/api/systems.js +++ b/frontend/src/api/systems.js @@ -3,5 +3,6 @@ import { adminHttp } from './http' export const getSystems = () => adminHttp.get('/admin/systems') export const createSystem = (data) => adminHttp.post('/admin/systems', data) export const updateSystem = (systemKey, data) => adminHttp.patch(`/admin/systems/${systemKey}`, data) +export const deleteSystem = (systemKey) => adminHttp.delete(`/admin/systems/${systemKey}`) export const getSystemGroups = (systemKey) => adminHttp.get(`/admin/systems/${systemKey}/groups`) export const getSystemMembers = (systemKey) => adminHttp.get(`/admin/systems/${systemKey}/members`) diff --git a/frontend/src/pages/admin/ApiClientsPage.vue b/frontend/src/pages/admin/ApiClientsPage.vue index 6408fc4..cd3a638 100644 --- a/frontend/src/pages/admin/ApiClientsPage.vue +++ b/frontend/src/pages/admin/ApiClientsPage.vue @@ -31,6 +31,7 @@ {{ row.status === 'active' ? '停用' : '啟用' }} + 刪除 @@ -101,7 +102,7 @@ import { ref, onMounted } from 'vue' import { ElMessage, ElMessageBox } from 'element-plus' import { Refresh } from '@element-plus/icons-vue' -import { createApiClient, getApiClients, rotateApiClientKey, updateApiClient } from '@/api/api-clients' +import { createApiClient, getApiClients, rotateApiClientKey, updateApiClient, deleteApiClient } from '@/api/api-clients' const items = ref([]) const loading = ref(false) @@ -278,5 +279,17 @@ async function toggleStatus(row) { } } +async function handleDelete(row) { + try { + await ElMessageBox.confirm(`確認刪除 API Client ${row.name}(${row.client_key})?`, '刪除確認', { type: 'warning' }) + await deleteApiClient(row.client_key) + ElMessage.success('刪除成功') + await load() + } catch (err) { + if (err === 'cancel') return + ElMessage.error(err.response?.data?.detail || '刪除失敗') + } +} + onMounted(load) diff --git a/frontend/src/pages/admin/CompaniesPage.vue b/frontend/src/pages/admin/CompaniesPage.vue index a473450..faa91b4 100644 --- a/frontend/src/pages/admin/CompaniesPage.vue +++ b/frontend/src/pages/admin/CompaniesPage.vue @@ -13,10 +13,11 @@ - + @@ -64,9 +65,9 @@ diff --git a/frontend/src/pages/admin/MembersPage.vue b/frontend/src/pages/admin/MembersPage.vue index a7d20da..8e9b051 100644 --- a/frontend/src/pages/admin/MembersPage.vue +++ b/frontend/src/pages/admin/MembersPage.vue @@ -24,6 +24,7 @@ @@ -71,12 +72,13 @@ diff --git a/frontend/src/pages/admin/ModulesPage.vue b/frontend/src/pages/admin/ModulesPage.vue index db5b1dd..bc6271f 100644 --- a/frontend/src/pages/admin/ModulesPage.vue +++ b/frontend/src/pages/admin/ModulesPage.vue @@ -29,6 +29,7 @@ 編輯 群組 會員 + 刪除 @@ -102,9 +103,9 @@ diff --git a/frontend/src/pages/admin/SystemsPage.vue b/frontend/src/pages/admin/SystemsPage.vue index 401d180..a37c1c8 100644 --- a/frontend/src/pages/admin/SystemsPage.vue +++ b/frontend/src/pages/admin/SystemsPage.vue @@ -26,6 +26,7 @@ 編輯 群組 會員 + 刪除 @@ -94,9 +95,9 @@