99import com .dbaagent .repository .ConnectionInitHistoryRepository ;
1010import com .dbaagent .repository .ConnectionInitStatusRepository ;
1111import com .dbaagent .repository .SchemaDocumentationRepository ;
12+ import com .dbaagent .service .ConnectionPinService ;
1213import com .dbaagent .service .ConnectionService ;
1314import com .dbaagent .service .scheduler .BrainInitSchedulerService ;
1415import com .dbaagent .service .scheduler .BrainJobsService ;
@@ -45,6 +46,7 @@ public class ConnectionController {
4546 private final AccessControlService accessControlService ;
4647 private final ConnectionAccessService connectionAccessService ;
4748 private final com .dbaagent .repository .ConnectionAccessGrantRepository connectionAccessGrantRepository ;
49+ private final ConnectionPinService connectionPinService ;
4850
4951 @ PostMapping ("/test" )
5052 public ResponseEntity <Map <String , Object >> testConnection (@ RequestBody ConnectionRequest request ) {
@@ -429,8 +431,10 @@ public ResponseEntity<List<ConnectionSummaryResponse>> getAllConnections() {
429431 String username = accessControlService .getCurrentUsername ();
430432 boolean isAdmin = accessControlService .isCurrentUserAdmin ();
431433 List <DatabaseConnection > connections = credentialService .getConnectionsForUser (username , isAdmin );
434+ // One lookup for the whole list rather than one per row.
435+ String pinnedId = connectionPinService .pinnedConnectionId (username ).orElse (null );
432436 List <ConnectionSummaryResponse > decryptedConnections = connections .stream ()
433- .map (conn -> toSummary (conn , username , isAdmin ))
437+ .map (conn -> toSummary (conn , username , isAdmin , pinnedId ))
434438 .toList ();
435439 return ResponseEntity .ok (decryptedConnections );
436440 } catch (org .springframework .web .server .ResponseStatusException e ) {
@@ -440,13 +444,62 @@ public ResponseEntity<List<ConnectionSummaryResponse>> getAllConnections() {
440444 }
441445 }
442446
447+ /**
448+ * Pin this connection as the caller's default, replacing any connection they had
449+ * pinned before.
450+ *
451+ * <p>Gated on {@code assertCanUseConnection} rather than
452+ * {@code assertCanManageConnectionConfig}: choosing which database you land on is a
453+ * personal preference, not a change to the connection, and a shared connection is
454+ * config-read-only for its recipients. Requiring manage rights would mean the people
455+ * who most want a default — the ones who were granted exactly one connection —
456+ * could not set one.
457+ */
458+ @ PutMapping ("/{id}/pin" )
459+ public ResponseEntity <Map <String , Object >> pinConnection (@ PathVariable String id ) {
460+ Map <String , Object > response = new HashMap <>();
461+ try {
462+ accessControlService .assertCanUseConnection (id );
463+ connectionPinService .pin (accessControlService .requireCurrentUsername (), id );
464+ response .put ("success" , true );
465+ response .put ("pinned" , true );
466+ return ResponseEntity .ok (response );
467+ } catch (org .springframework .web .server .ResponseStatusException e ) {
468+ throw e ;
469+ } catch (Exception e ) {
470+ response .put ("success" , false );
471+ response .put ("message" , "Failed to pin connection: " + e .getMessage ());
472+ return ResponseEntity .status (HttpStatus .INTERNAL_SERVER_ERROR ).body (response );
473+ }
474+ }
475+
476+ /** Clear the caller's default, if this connection is the one currently pinned. */
477+ @ DeleteMapping ("/{id}/pin" )
478+ public ResponseEntity <Map <String , Object >> unpinConnection (@ PathVariable String id ) {
479+ Map <String , Object > response = new HashMap <>();
480+ try {
481+ accessControlService .assertCanUseConnection (id );
482+ connectionPinService .unpin (accessControlService .requireCurrentUsername (), id );
483+ response .put ("success" , true );
484+ response .put ("pinned" , false );
485+ return ResponseEntity .ok (response );
486+ } catch (org .springframework .web .server .ResponseStatusException e ) {
487+ throw e ;
488+ } catch (Exception e ) {
489+ response .put ("success" , false );
490+ response .put ("message" , "Failed to unpin connection: " + e .getMessage ());
491+ return ResponseEntity .status (HttpStatus .INTERNAL_SERVER_ERROR ).body (response );
492+ }
493+ }
494+
443495 @ DeleteMapping ("/{id}" )
444496 public ResponseEntity <Map <String , Object >> deleteConnection (@ PathVariable String id ) {
445497 Map <String , Object > response = new HashMap <>();
446498 try {
447499 accessControlService .assertCanManageConnectionConfig (id );
448500 connectionService .closeConnectionPool (id );
449501 connectionAccessService .deleteAllGrantsForConnection (id );
502+ connectionPinService .clearPinsForConnection (id );
450503 credentialService .deleteConnection (id );
451504 response .put ("success" , true );
452505 response .put ("message" , "Connection deleted successfully" );
@@ -781,7 +834,7 @@ public ResponseEntity<?> runBrainJob(@PathVariable String id, @PathVariable Stri
781834 }
782835 }
783836
784- private ConnectionSummaryResponse toSummary (DatabaseConnection conn , String username , boolean isAdmin ) {
837+ private ConnectionSummaryResponse toSummary (DatabaseConnection conn , String username , boolean isAdmin , String pinnedConnectionId ) {
785838 ConnectionSummaryResponse summary = new ConnectionSummaryResponse ();
786839 try {
787840 ConnectionRequest decrypted = credentialService .getDecryptedConnection (conn .getId ());
@@ -821,6 +874,7 @@ private ConnectionSummaryResponse toSummary(DatabaseConnection conn, String user
821874 summary .setAccessLevel (resolved .getEffectiveAccess ().name ());
822875 summary .setCanManageConfig (resolved .canManageConfig ());
823876 summary .setCanManageContent (resolved .canManageContent ());
877+ summary .setPinned (conn .getId () != null && conn .getId ().equals (pinnedConnectionId ));
824878 return summary ;
825879 }
826880
0 commit comments