Skip to content

Commit bec0be2

Browse files
authored
Merge pull request #814 from esune/feat/delete-credentials
Add explicit user confirmation to delete credential type
2 parents 8df5ca6 + 16f4114 commit bec0be2

1 file changed

Lines changed: 96 additions & 10 deletions

File tree

server/vcr-server/agent_webhooks/management/commands/delete_credential_type.py

Lines changed: 96 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,22 @@ class Command(BaseCommand):
1414

1515
def add_arguments(self, parser):
1616
parser.add_argument('credential_type_id', type=str)
17+
parser.add_argument(
18+
'--force',
19+
action='store_true',
20+
help='Skip confirmation prompt and proceed with deletion'
21+
)
1722

1823
def handle(self, *args, **options):
1924
credential_type_id = options['credential_type_id']
2025

21-
# Track affected topics for targeted reindexing
22-
affected_topics = []
26+
# Pre-flight check and confirmation before opening Solr queue
27+
affected_topics = self._check_and_confirm_deletion(*args, **options)
2328

29+
# If we get here, user confirmed deletion - proceed with operations
2430
queue = SolrQueue()
2531
with queue:
26-
affected_topics = self.delete_credential_type(*args, **options)
32+
self._perform_deletion(affected_topics, *args, **options)
2733

2834
# Wait for queue to drain while still in context
2935
eject = 20
@@ -45,10 +51,8 @@ def handle(self, *args, **options):
4551
self.stdout.write("Performing targeted search index refresh ...")
4652
self._refresh_affected_indexes(credential_type_id, affected_topics)
4753

48-
def delete_credential_type(self, *args, **options):
49-
start_time = time.perf_counter()
50-
51-
# get Credential Type ID from input parameters
54+
def _check_and_confirm_deletion(self, *args, **options):
55+
"""Check credential type exists and get user confirmation"""
5256
credential_type_id = options['credential_type_id']
5357
self.stdout.write("Deleting credential_type_id: " + credential_type_id)
5458

@@ -60,7 +64,7 @@ def delete_credential_type(self, *args, **options):
6064
credential_type = CredentialType.objects.get(id=credential_type_id)
6165
except CredentialType.DoesNotExist:
6266
self.stdout.write(" ... credential_type_id not found in OrgBook.")
63-
return list(affected_topics)
67+
raise SystemExit(1)
6468

6569
# Find all credentials for this credential type
6670
credentials = Credential.objects.filter(
@@ -71,10 +75,92 @@ def delete_credential_type(self, *args, **options):
7175
if credentials.exists():
7276
topic_ids = set(credentials.values_list('topic_id', flat=True))
7377
affected_topics.update(topic_ids)
78+
79+
# Display information about the operation
80+
self.stdout.write("\n" + "="*60)
81+
self.stdout.write("CREDENTIAL TYPE DELETION CONFIRMATION")
82+
self.stdout.write("="*60)
83+
84+
# Use description if available, otherwise fall back to raw_data.type
85+
display_name = credential_type.description
86+
if (not display_name and hasattr(credential_type, 'raw_data') and
87+
credential_type.raw_data):
88+
try:
89+
import json
90+
91+
# Parse JSON string to get the type attribute
92+
raw_data_obj = json.loads(credential_type.raw_data)
93+
display_name = raw_data_obj.get('type')
94+
except (json.JSONDecodeError, AttributeError, TypeError, KeyError):
95+
display_name = None
96+
97+
# Final fallback to ID if no display name found
98+
if not display_name:
99+
display_name = f"Credential Type ID {credential_type_id}"
100+
101+
self.stdout.write(f"Credential Type: {display_name}")
102+
self.stdout.write(f"ID: {credential_type_id}")
103+
self.stdout.write(
104+
f"Found {credentials.count()} credentials to delete"
105+
)
106+
self.stdout.write(f"Topics affected: {len(affected_topics)}")
107+
self.stdout.write("="*60)
108+
109+
if affected_topics:
110+
self.stdout.write(
111+
f"WARNING: This operation will affect "
112+
f"{len(affected_topics)} topics and trigger search index "
113+
f"refresh."
114+
)
115+
else:
74116
self.stdout.write(
75-
f" ... found {len(topic_ids)} topics affected by "
76-
f"credential deletion"
117+
"No topics will be affected by this operation."
77118
)
119+
120+
self.stdout.write(
121+
"\nThis action cannot be undone. All credentials of this type "
122+
"will be permanently deleted."
123+
)
124+
125+
# Ask for user confirmation unless --force flag is used
126+
if not options.get('force', False):
127+
try:
128+
confirmation = input(
129+
"\nDo you want to continue? (y/N): "
130+
).strip()
131+
except (EOFError, KeyboardInterrupt):
132+
self.stdout.write("\n\nOperation cancelled by user.")
133+
raise SystemExit(0)
134+
135+
if confirmation.lower() not in ['y', 'yes']:
136+
self.stdout.write(
137+
"Operation cancelled. No changes were made."
138+
)
139+
raise SystemExit(0)
140+
else:
141+
self.stdout.write(
142+
"\n--force flag provided, skipping confirmation."
143+
)
144+
145+
self.stdout.write("\nProceeding with credential type deletion...")
146+
self.stdout.write("="*60 + "\n")
147+
148+
return list(affected_topics)
149+
150+
def _perform_deletion(self, affected_topics, *args, **options):
151+
"""Perform the actual credential type deletion"""
152+
start_time = time.perf_counter()
153+
credential_type_id = options['credential_type_id']
154+
155+
# Re-fetch the credential type and related data (since confirmation
156+
# happened earlier)
157+
credential_type = CredentialType.objects.get(id=credential_type_id)
158+
credentials = Credential.objects.filter(
159+
credential_type=credential_type
160+
)
161+
162+
# Convert to set for efficient updates (we received it as a list)
163+
affected_topics = set(affected_topics)
78164

79165
# delete credentials from wallet first
80166
if credentials.exists():

0 commit comments

Comments
 (0)