fix: cascade soft-delete node bindings and rollback on config failure
This commit is contained in:
1 parent
eb312d238d
commit
4f33edff9c
3 files changed
+45
-6
No files matched your search
@@ -84,11 +84,26 @@ func (s *Service) Update(l *models.Listener) error {
|
||||
}
|
||||
|
||||
func (s *Service) Delete(id uint) error {
|
||||
var previous models.Listener
|
||||
if err := s.db.First(&previous, id).Error; err != nil {
|
||||
return fmt.Errorf("failed to load node %d: %w", id, err)
|
||||
}
|
||||
|
||||
// Soft-delete join bindings first so credential regeneration stays consistent.
|
||||
if err := s.db.Where("listener_id = ?", id).Delete(&models.ListenerUser{}).Error; err != nil {
|
||||
return fmt.Errorf("failed to delete node bindings: %w", err)
|
||||
}
|
||||
if err := s.db.Delete(&models.Listener{}, id).Error; err != nil {
|
||||
return fmt.Errorf("failed to delete node: %w", err)
|
||||
}
|
||||
|
||||
return s.RegenerateConfig()
|
||||
if err := s.RegenerateConfig(); err != nil {
|
||||
// Best-effort restore so a config write failure does not leave a half-deleted node.
|
||||
_ = s.db.Unscoped().Model(&models.Listener{}).Where("id = ?", id).Update("deleted_at", nil).Error
|
||||
_ = s.db.Unscoped().Model(&models.ListenerUser{}).Where("listener_id = ?", id).Update("deleted_at", nil).Error
|
||||
return fmt.Errorf("failed to regenerate config after delete: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegenerateConfig regenerates the complete Mihomo configuration through the
|
||||
|
||||
@@ -17,6 +17,6 @@ export interface Listener {
|
||||
export const fetchListeners = () => client.get<Listener[]>('/nodes').then(r => r.data);
|
||||
export const createListener = (payload: Partial<Listener>) => client.post<Listener>('/nodes', payload).then(r => r.data);
|
||||
export const updateListener = (id: number, payload: Partial<Listener>) => client.put<Listener>(`/nodes/${id}`, payload).then(r => r.data);
|
||||
export const deleteListener = (id: number) => client.delete(`/nodes/${id}`);
|
||||
export const reloadListener = (id: number) => client.post(`/nodes/${id}/reload`);
|
||||
export const deleteListener = (id: number) => client.delete(`/nodes/${id}`).then((r) => r.data);
|
||||
export const reloadListener = (id: number) => client.post(`/nodes/${id}/reload`).then((r) => r.data);
|
||||
export const exportNodeURI = (id: number) => client.get<{ name: string; protocol: string; uris: string[] }>(`/nodes/${id}/uri`).then(r => r.data);
|
||||
@@ -5,10 +5,34 @@ export interface ProxyUser {
|
||||
username: string;
|
||||
password?: string;
|
||||
enabled: boolean;
|
||||
traffic_limit?: number;
|
||||
traffic_used?: number;
|
||||
created_at?: string;
|
||||
}
|
||||
|
||||
export const fetchUsers = () => client.get<ProxyUser[]>('/users').then(r => r.data);
|
||||
export const createUser = (payload: Partial<ProxyUser>) => client.post<ProxyUser>('/users', payload).then(r => r.data);
|
||||
export const updateUser = (id: number, payload: Partial<ProxyUser>) => client.put<ProxyUser>(`/users/${id}`, payload).then(r => r.data);
|
||||
export interface BoundNode {
|
||||
id: number;
|
||||
name: string;
|
||||
protocol: string;
|
||||
port: string;
|
||||
bind_address: string;
|
||||
enabled: boolean;
|
||||
tls?: boolean;
|
||||
udp?: boolean;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
export const fetchUsers = () => client.get<ProxyUser[]>('/users').then((r) => r.data);
|
||||
export const createUser = (payload: Partial<ProxyUser>) =>
|
||||
client.post<ProxyUser>('/users', payload).then((r) => r.data);
|
||||
export const updateUser = (id: number, payload: Partial<ProxyUser>) =>
|
||||
client.put<ProxyUser>(`/users/${id}`, payload).then((r) => r.data);
|
||||
export const deleteUser = (id: number) => client.delete(`/users/${id}`);
|
||||
|
||||
/** List nodes currently bound to a proxy user. */
|
||||
export const fetchUserNodes = (userId: number) =>
|
||||
client.get<BoundNode[]>(`/users/${userId}/nodes`).then((r) => r.data);
|
||||
|
||||
/** Replace the set of nodes bound to a proxy user. */
|
||||
export const bindUserNodes = (userId: number, listenerIds: number[]) =>
|
||||
client.post(`/users/${userId}/nodes`, { listener_ids: listenerIds }).then((r) => r.data);
|
||||
Reference in new issue
Block a user