-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathUserDigestPreference.java
More file actions
151 lines (134 loc) · 4.81 KB
/
Copy pathUserDigestPreference.java
File metadata and controls
151 lines (134 loc) · 4.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package com.dbaagent.model;
import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.Builder;
import java.time.LocalDateTime;
/**
* Per-user digest preferences for role-aware, personalized digests.
*
* <p>Each row represents a single digest subscription for a user. A user can have
* multiple subscriptions (e.g., different connections, different delivery methods).
*
* <p>When no preferences exist for a user, the system falls back to the singleton
* {@link SlackDigestConfig} behavior for backward compatibility.
*
* <h3>Preference Resolution</h3>
* <ol>
* <li>Look for enabled {@code UserDigestPreference} rows matching the user/connection</li>
* <li>If found, use those preferences (cron, persona, delivery method)</li>
* <li>If not found, fall back to global singleton behavior</li>
* </ol>
*
* <h3>Persona Tags</h3>
* <p>The {@code personaTag} is optional and allows content prioritization beyond the
* user's RBAC role. For example, an ADMIN with a DBA persona will see performance
* metrics emphasized over schema changes in their digest.
*/
@Entity
@Table(
name = "user_digest_preference",
uniqueConstraints = {
@UniqueConstraint(
name = "uk_user_digest_pref_user_conn_method",
columnNames = {"username", "connection_id", "delivery_method"}
)
},
indexes = {
@Index(name = "idx_user_digest_pref_username", columnList = "username"),
@Index(name = "idx_user_digest_pref_conn", columnList = "connection_id"),
@Index(name = "idx_user_digest_pref_enabled", columnList = "enabled, username")
}
)
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserDigestPreference {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* Username of the user this preference belongs to.
* References users.username (not enforced by FK to allow soft-deleted users).
*/
@Column(name = "username", nullable = false, length = 255)
private String username;
/**
* Optional connection ID to scope this preference.
* When null, this preference applies to all connections the user has access to.
*/
@Column(name = "connection_id", length = 36)
private String connectionId;
/**
* Whether digest delivery is enabled for this preference.
* Allows users to pause digests without deleting preferences.
*/
@Column(nullable = false)
@Builder.Default
private boolean enabled = true;
/**
* Optional persona tag for content prioritization.
* When null, content is prioritized based on the user's RBAC role alone.
*/
@Column(name = "persona_tag", length = 32)
@Enumerated(EnumType.STRING)
private PersonaTag personaTag;
/**
* Optional cron expression for per-user schedule override.
* When null, uses the global SlackDigestConfig.cronExpression.
* Format: "second minute hour day-of-month month day-of-week"
*/
@Column(name = "cron_expression", length = 100)
private String cronExpression;
/**
* Delivery method for this digest preference.
* Each method requires different prerequisites (e.g., SLACK_DM needs SlackUserLink).
*/
@Column(name = "delivery_method", nullable = false, length = 32)
@Enumerated(EnumType.STRING)
@Builder.Default
private DigestDeliveryMethod deliveryMethod = DigestDeliveryMethod.SLACK_DM;
/**
* Optional timezone for schedule interpretation.
* When null or invalid, the digest tick evaluates the cron in UTC.
* Format: IANA timezone ID (e.g., "America/New_York", "Europe/London").
*/
@Column(name = "timezone", length = 64)
private String timezone;
@Column(name = "created_at", nullable = false, updatable = false)
@Builder.Default
private LocalDateTime createdAt = LocalDateTime.now();
@Column(name = "updated_at", nullable = false)
@Builder.Default
private LocalDateTime updatedAt = LocalDateTime.now();
@PrePersist
void onCreate() {
LocalDateTime now = LocalDateTime.now();
if (createdAt == null) {
createdAt = now;
}
updatedAt = now;
}
@PreUpdate
void onUpdate() {
updatedAt = LocalDateTime.now();
}
/**
* Returns the effective cron expression, falling back to default if not set.
*/
@Transient
public String getEffectiveCronExpression(String globalDefault) {
return cronExpression != null && !cronExpression.isBlank()
? cronExpression
: globalDefault;
}
/**
* Check if this preference applies to a specific connection.
*/
@Transient
public boolean appliesTo(String targetConnectionId) {
return connectionId == null || connectionId.equals(targetConnectionId);
}
}