Skip to content

Commit 4a44479

Browse files
Merge branch 'main' into cursor/digest-trigger-hybrid
2 parents a1d9b2a + 8b8a100 commit 4a44479

6 files changed

Lines changed: 325 additions & 196 deletions

File tree

backend/src/main/java/com/dbaagent/controller/DigestPreferenceController.java

Lines changed: 72 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package com.dbaagent.controller;
22

3+
import com.dbaagent.dto.DigestPreferenceResponse;
4+
import com.dbaagent.model.DatabaseConnection;
35
import com.dbaagent.model.DigestDeliveryMethod;
46
import com.dbaagent.model.PersonaTag;
57
import com.dbaagent.model.UserDigestPreference;
8+
import com.dbaagent.repository.CredentialRepository;
69
import com.dbaagent.service.DigestPreferenceSeedService;
710
import com.dbaagent.service.security.AccessControlService;
811
import com.dbaagent.service.UserDigestPreferenceService;
@@ -13,8 +16,12 @@
1316
import org.springframework.security.access.prepost.PreAuthorize;
1417
import org.springframework.web.bind.annotation.*;
1518

19+
import java.util.HashMap;
1620
import java.util.List;
1721
import java.util.Map;
22+
import java.util.Objects;
23+
import java.util.Set;
24+
import java.util.stream.Collectors;
1825

1926
/**
2027
* REST controller for managing per-user digest preferences.
@@ -32,21 +39,23 @@ public class DigestPreferenceController {
3239
private final AccessControlService accessControlService;
3340
private final DigestPreferenceSeedService seedService;
3441
private final SlackDailyDigestService digestService;
42+
private final CredentialRepository credentialRepository;
3543

3644
/**
37-
* Get the current user's digest preferences.
45+
* Get the current user's digest preferences (includes connection display names).
3846
*/
3947
@GetMapping
40-
public ResponseEntity<List<UserDigestPreference>> getMyPreferences() {
48+
public ResponseEntity<List<DigestPreferenceResponse>> getMyPreferences() {
4149
String username = accessControlService.requireCurrentUsername();
42-
return ResponseEntity.ok(preferenceService.getPreferencesForUser(username));
50+
List<UserDigestPreference> prefs = preferenceService.getPreferencesForUser(username);
51+
return ResponseEntity.ok(toResponses(prefs));
4352
}
4453

4554
/**
4655
* Create a new digest preference for the current user.
4756
*/
4857
@PostMapping
49-
public ResponseEntity<UserDigestPreference> createPreference(@RequestBody CreatePreferenceRequest request) {
58+
public ResponseEntity<DigestPreferenceResponse> createPreference(@RequestBody CreatePreferenceRequest request) {
5059
String username = accessControlService.requireCurrentUsername();
5160

5261
DigestDeliveryMethod method;
@@ -80,14 +89,14 @@ public ResponseEntity<UserDigestPreference> createPreference(@RequestBody Create
8089
request.timezone
8190
);
8291

83-
return ResponseEntity.ok(preference);
92+
return ResponseEntity.ok(toResponse(preference));
8493
}
8594

8695
/**
8796
* Update an existing preference.
8897
*/
8998
@PutMapping("/{id}")
90-
public ResponseEntity<UserDigestPreference> updatePreference(
99+
public ResponseEntity<DigestPreferenceResponse> updatePreference(
91100
@PathVariable Long id,
92101
@RequestBody UpdatePreferenceRequest request) {
93102

@@ -112,14 +121,14 @@ public ResponseEntity<UserDigestPreference> updatePreference(
112121
request.timezone
113122
);
114123

115-
return ResponseEntity.ok(updated);
124+
return ResponseEntity.ok(toResponse(updated));
116125
}
117126

118127
/**
119128
* Enable or disable a preference.
120129
*/
121130
@PatchMapping("/{id}/enabled")
122-
public ResponseEntity<UserDigestPreference> setEnabled(
131+
public ResponseEntity<DigestPreferenceResponse> setEnabled(
123132
@PathVariable Long id,
124133
@RequestBody Map<String, Boolean> body) {
125134

@@ -138,7 +147,7 @@ public ResponseEntity<UserDigestPreference> setEnabled(
138147
}
139148

140149
UserDigestPreference updated = preferenceService.setEnabled(id, enabled);
141-
return ResponseEntity.ok(updated);
150+
return ResponseEntity.ok(toResponse(updated));
142151
}
143152

144153
/**
@@ -286,4 +295,58 @@ public record DigestStatusResponse(boolean perUserMode, long enabledPreferences,
286295
public ResponseEntity<Map<String, String>> handleBadRequest(IllegalArgumentException e) {
287296
return ResponseEntity.badRequest().body(Map.of("message", e.getMessage() != null ? e.getMessage() : "Bad request"));
288297
}
298+
299+
// ─────────────────────────────────────────────────────────────────────────
300+
// Response mapping (connection display names)
301+
// ─────────────────────────────────────────────────────────────────────────
302+
303+
private List<DigestPreferenceResponse> toResponses(List<UserDigestPreference> prefs) {
304+
Map<String, String> namesById = resolveConnectionNames(prefs);
305+
return prefs.stream()
306+
.map(pref -> toResponse(pref, namesById))
307+
.toList();
308+
}
309+
310+
private DigestPreferenceResponse toResponse(UserDigestPreference pref) {
311+
Map<String, String> namesById = resolveConnectionNames(List.of(pref));
312+
return toResponse(pref, namesById);
313+
}
314+
315+
private DigestPreferenceResponse toResponse(UserDigestPreference pref, Map<String, String> namesById) {
316+
String connectionName = null;
317+
if (pref.getConnectionId() != null) {
318+
connectionName = namesById.get(pref.getConnectionId());
319+
}
320+
return DigestPreferenceResponse.builder()
321+
.id(pref.getId())
322+
.username(pref.getUsername())
323+
.connectionId(pref.getConnectionId())
324+
.connectionName(connectionName)
325+
.enabled(pref.isEnabled())
326+
.personaTag(pref.getPersonaTag() != null ? pref.getPersonaTag().name() : null)
327+
.cronExpression(pref.getCronExpression())
328+
.deliveryMethod(pref.getDeliveryMethod() != null ? pref.getDeliveryMethod().name() : null)
329+
.timezone(pref.getTimezone())
330+
.createdAt(pref.getCreatedAt())
331+
.updatedAt(pref.getUpdatedAt())
332+
.build();
333+
}
334+
335+
private Map<String, String> resolveConnectionNames(List<UserDigestPreference> prefs) {
336+
Set<String> ids = prefs.stream()
337+
.map(UserDigestPreference::getConnectionId)
338+
.filter(Objects::nonNull)
339+
.filter(id -> !id.isBlank())
340+
.collect(Collectors.toSet());
341+
if (ids.isEmpty()) {
342+
return Map.of();
343+
}
344+
Map<String, String> names = new HashMap<>();
345+
for (DatabaseConnection conn : credentialRepository.findAllById(ids)) {
346+
if (conn.getId() != null && conn.getConnectionName() != null) {
347+
names.put(conn.getId(), conn.getConnectionName());
348+
}
349+
}
350+
return names;
351+
}
289352
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.dbaagent.dto;
2+
3+
import lombok.Builder;
4+
import lombok.Value;
5+
6+
import java.time.LocalDateTime;
7+
8+
/**
9+
* API view of a {@code UserDigestPreference} with a human-readable connection name.
10+
*/
11+
@Value
12+
@Builder
13+
public class DigestPreferenceResponse {
14+
Long id;
15+
String username;
16+
String connectionId;
17+
/** Display name for {@link #connectionId}; null when unknown or preference is connection-wide. */
18+
String connectionName;
19+
boolean enabled;
20+
String personaTag;
21+
String cronExpression;
22+
String deliveryMethod;
23+
String timezone;
24+
LocalDateTime createdAt;
25+
LocalDateTime updatedAt;
26+
}

docs/DIGEST_PREFERENCES.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ Note: Do not claim EMAIL or WhatsApp delivery is available. These are planned fo
8686

8787
| Endpoint | Method | Description |
8888
|----------|--------|-------------|
89-
| `/api/digest/preferences` | GET | Get current user's preferences |
89+
| `/api/digest/preferences` | GET | Get current user's preferences (includes `connectionName`) |
9090
| `/api/digest/preferences` | POST | Create a new preference |
9191
| `/api/digest/preferences/{id}` | PUT | Update a preference |
9292
| `/api/digest/preferences/{id}/enabled` | PATCH | Enable/disable |
@@ -134,16 +134,17 @@ To migrate from legacy singleton mode to per-user mode:
134134

135135
The digest preferences are accessible from:
136136

137-
1. **Digest Section** (sidebar): Click the bell icon (🔔) to open preferences panel
138-
2. **Preferences Panel**: Create, edit, enable/disable, delete preferences
137+
1. **Digest Section** (sidebar): Click the **gear** (⚙) to open the preferences panel — single entry point (no competing bell)
138+
2. **First-run**: Opening Digests with zero preferences (or none enabled) auto-opens the panel once (`localStorage` flag)
139+
3. **Preferences Panel**: Create, edit, enable/disable, delete preferences
139140

140141
### Preferences Panel Features
141142

142-
- View all your digest subscriptions
143+
- Pref cards show **connection display names** (API `connectionName`, with client-side fallback from the connections list; UUID only if name is missing)
143144
- Toggle digests on/off per connection
144-
- Change persona without recreating
145+
- Change persona and schedule inline (compact controls — no duplicate meta labels)
145146
- Quick schedule presets (8 AM, 9 AM, Noon, etc.) — stored on the preference and honored by the minute-tick scheduler
146-
- Seed for all your connections at once
147+
- Seed for all your connections at once (`POST /api/digest/preferences/seed/me`)
147148

148149
## Database Schema
149150

0 commit comments

Comments
 (0)