Skip to content

Commit 5abaa66

Browse files
committed
Add seagull settings and preferences object.
1 parent 7d32881 commit 5abaa66

File tree

7 files changed

+232
-1
lines changed

7 files changed

+232
-1
lines changed

user/full_user.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ type FullUser struct {
6262
DeletedUserID string `json:"deletedUserId,omitempty" bson:"deletedUserId,omitempty"`
6363
Attributes map[string][]string `json:"-"`
6464
Profile *UserProfile `json:"-"`
65+
Settings *Settings `json:"-"`
66+
Preferences *Preferences `json:"-"`
6567
FirstName string `json:"firstName,omitempty"`
6668
LastName string `json:"lastName,omitempty"`
6769
}

user/keycloak_client.go

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,12 @@ type keycloakUser struct {
5656

5757
type keycloakUserAttributes struct {
5858
TermsAcceptedDate []string `json:"terms_and_conditions,omitempty"`
59-
Profile *UserProfile `json:"profile"`
59+
Profile *UserProfile `json:"profile,omitempty"`
60+
Preferences *Preferences `json:"preferences,omitempty"`
61+
Settings *Settings `json:"settings,omitempty"`
6062
}
6163

64+
// keycloakClient is a wrapper around gocloak for simplified access to keycloak
6265
type keycloakClient struct {
6366
cfg *KeycloakConfig
6467
adminToken *oauth2.Token
@@ -202,6 +205,14 @@ func (c *keycloakClient) UpdateUser(ctx context.Context, user *keycloakUser) err
202205
profileAttrs := user.Attributes.Profile.ToAttributes()
203206
maps.Copy(attrs, profileAttrs)
204207
}
208+
if user.Attributes.Settings != nil {
209+
settingsAttrs := user.Attributes.Settings.ToAttributes()
210+
maps.Copy(attrs, settingsAttrs)
211+
}
212+
if user.Attributes.Preferences != nil {
213+
prefsAttrs := user.Attributes.Preferences.ToAttributes()
214+
maps.Copy(attrs, prefsAttrs)
215+
}
205216

206217
gocloakUser.Attributes = &attrs
207218
if err := c.keycloak.UpdateUser(ctx, token.AccessToken, c.cfg.Realm, gocloakUser); err != nil {
@@ -237,6 +248,54 @@ func (c *keycloakClient) DeleteUserProfile(ctx context.Context, id string) error
237248
return c.UpdateUser(ctx, user)
238249
}
239250

251+
func (c *keycloakClient) UpdateUserPreferences(ctx context.Context, id string, p *Preferences) error {
252+
user, err := c.GetUserById(ctx, id)
253+
if err != nil {
254+
return err
255+
}
256+
if user == nil {
257+
return ErrUserNotFound
258+
}
259+
user.Attributes.Preferences = p
260+
return c.UpdateUser(ctx, user)
261+
}
262+
263+
func (c *keycloakClient) DeleteUserPreferences(ctx context.Context, id string) error {
264+
user, err := c.GetUserById(ctx, id)
265+
if err != nil {
266+
return err
267+
}
268+
if user == nil {
269+
return ErrUserNotFound
270+
}
271+
user.Attributes.Preferences = nil
272+
return c.UpdateUser(ctx, user)
273+
}
274+
275+
func (c *keycloakClient) UpdateUserSettings(ctx context.Context, id string, s *Settings) error {
276+
user, err := c.GetUserById(ctx, id)
277+
if err != nil {
278+
return err
279+
}
280+
if user == nil {
281+
return ErrUserNotFound
282+
}
283+
user.Attributes.Settings = s
284+
return c.UpdateUser(ctx, user)
285+
}
286+
287+
func (c *keycloakClient) DeleteUserSettings(ctx context.Context, id string) error {
288+
user, err := c.GetUserById(ctx, id)
289+
if err != nil {
290+
return err
291+
}
292+
if user == nil {
293+
return ErrUserNotFound
294+
}
295+
user.Attributes.Settings = nil
296+
return c.UpdateUser(ctx, user)
297+
}
298+
240299
func (c *keycloakClient) UpdateUserPassword(ctx context.Context, id, password string) error {
241300
token, err := c.getAdminToken(ctx)
242301
if err != nil {
@@ -528,6 +587,12 @@ func newKeycloakUser(gocloakUser *gocloak.User) *keycloakUser {
528587
if prof, ok := profileFromAttributes(attrs); ok {
529588
user.Attributes.Profile = prof
530589
}
590+
if prefs, ok := preferencesFromAttributes(attrs); ok {
591+
user.Attributes.Preferences = prefs
592+
}
593+
if settings, ok := settingsFromAttributes(attrs); ok {
594+
user.Attributes.Settings = settings
595+
}
531596
}
532597

533598
if gocloakUser.RealmRoles != nil {
@@ -556,6 +621,8 @@ func newUserFromKeycloakUser(keycloakUser *keycloakUser) *FullUser {
556621
IsMigrated: true,
557622
Enabled: keycloakUser.Enabled,
558623
Profile: attrs.Profile,
624+
Settings: attrs.Settings,
625+
Preferences: attrs.Preferences,
559626
}
560627

561628
// All non-custodial users have a password and it's important to set the hash to a non-empty value.
@@ -591,6 +658,12 @@ func userToKeycloakUser(u *FullUser) *keycloakUser {
591658
if u.Profile != nil {
592659
keycloakUser.Attributes.Profile = u.Profile
593660
}
661+
if u.Preferences != nil {
662+
keycloakUser.Attributes.Preferences = u.Preferences
663+
}
664+
if u.Settings != nil {
665+
keycloakUser.Attributes.Settings = u.Settings
666+
}
594667

595668
return keycloakUser
596669
}

user/keycloak_user_accessor.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"golang.org/x/oauth2"
1010
)
1111

12+
// keycloakUserAccessor implements UserAccessor by using keycloak
1213
type keycloakUserAccessor struct {
1314
cfg *KeycloakConfig
1415
adminToken *oauth2.Token
@@ -227,3 +228,19 @@ func (m *keycloakUserAccessor) UpdateUserProfile(ctx context.Context, userId str
227228
func (m *keycloakUserAccessor) DeleteUserProfile(ctx context.Context, userId string) error {
228229
return m.keycloakClient.DeleteUserProfile(ctx, userId)
229230
}
231+
232+
func (m *keycloakUserAccessor) UpdateUserPreferences(ctx context.Context, userId string, p *Preferences) error {
233+
return m.keycloakClient.UpdateUserPreferences(ctx, userId, p)
234+
}
235+
236+
func (m *keycloakUserAccessor) DeleteUserPreferences(ctx context.Context, userId string) error {
237+
return m.keycloakClient.DeleteUserPreferences(ctx, userId)
238+
}
239+
240+
func (m *keycloakUserAccessor) UpdateUserSettings(ctx context.Context, userId string, s *Settings) error {
241+
return m.keycloakClient.UpdateUserSettings(ctx, userId, s)
242+
}
243+
244+
func (m *keycloakUserAccessor) DeleteUserSettings(ctx context.Context, userId string) error {
245+
return m.keycloakClient.DeleteUserSettings(ctx, userId)
246+
}

user/preferences.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package user
2+
3+
import (
4+
"fmt"
5+
"strconv"
6+
"time"
7+
)
8+
9+
type Preferences struct {
10+
SeenShareDataBannerDate *time.Time `json:"seenShareDataBannerDate"`
11+
SeenShareDataBannerCount int `json:"seenShareDataBannerCount"`
12+
DismissedShareDataBannerTime *time.Time `json:"dismissedShareDataBannerTime"`
13+
DismissedDonateYourDataBannerTime *time.Time `json:"dismissedDonateYourDataBannerTime"`
14+
DismissedDexcomConnectBannerTime *time.Time `json:"dismissedDexcomConnectBannerTime"`
15+
DismissedUpdateTypeBannerTime *time.Time `json:"dismissedUpdateTypeBannerTime"`
16+
}
17+
18+
func (p *Preferences) ToAttributes() map[string][]string {
19+
attributes := map[string][]string{}
20+
21+
if p.SeenShareDataBannerDate != nil {
22+
addAttribute(attributes, "preferences.seenShareDataBannerDate", p.SeenShareDataBannerDate.Format(time.RFC3339))
23+
}
24+
addAttribute(attributes, "preferences.seenShareDataBannerCount", fmt.Sprintf("%d", p.SeenShareDataBannerCount))
25+
if p.DismissedShareDataBannerTime != nil {
26+
addAttribute(attributes, "preferences.dismissedShareDataBannerTime", p.DismissedShareDataBannerTime.Format(time.RFC3339))
27+
}
28+
if p.DismissedDonateYourDataBannerTime != nil {
29+
addAttribute(attributes, "preferences.dismissedDonateYourDataBannerTime", p.DismissedDonateYourDataBannerTime.Format(time.RFC3339))
30+
}
31+
if p.DismissedDexcomConnectBannerTime != nil {
32+
addAttribute(attributes, "preferences.dismissedDexcomConnectBannerTime", p.DismissedDexcomConnectBannerTime.Format(time.RFC3339))
33+
}
34+
if p.DismissedUpdateTypeBannerTime != nil {
35+
addAttribute(attributes, "preferences.dismissedUpdateTypeBannerTime", p.DismissedUpdateTypeBannerTime.Format(time.RFC3339))
36+
}
37+
return attributes
38+
}
39+
40+
func preferencesFromAttributes(attributes map[string][]string) (prefs *Preferences, ok bool) {
41+
if !containsAnyAttributeKeys(attributes, "preferences.seenShareDataBannerDate", "preferences.seenShareDataBannerCount", "preferences.dismissedShareDataBannerTime", "preferences.dismissedDonateYourDataBannerTime", "preferences.dismissedDexcomConnectBannerTime", "preferences.dismissedUpdateTypeBannerTime") {
42+
return nil, false
43+
}
44+
45+
prefs = &Preferences{}
46+
seenShareDataBannerDate, err := time.Parse(time.RFC3339, getAttribute(attributes, "preferences.seenShareDataBannerDate"))
47+
if err == nil {
48+
prefs.SeenShareDataBannerDate = &seenShareDataBannerDate
49+
}
50+
seenShareDataBannerCount, err := strconv.Atoi(getAttribute(attributes, "preferences.seenShareDataBannerCount"))
51+
if err == nil {
52+
prefs.SeenShareDataBannerCount = seenShareDataBannerCount
53+
}
54+
dismissedShareDataBannerTime, err := time.Parse(time.RFC3339, getAttribute(attributes, "preferences.dismissedShareDataBannerTime"))
55+
if err == nil {
56+
prefs.DismissedShareDataBannerTime = &dismissedShareDataBannerTime
57+
}
58+
dismissedDonateYourDataBannerTime, err := time.Parse(time.RFC3339, getAttribute(attributes, "preferences.dismissedDonateYourDataBannerTime"))
59+
if err == nil {
60+
prefs.DismissedDonateYourDataBannerTime = &dismissedDonateYourDataBannerTime
61+
}
62+
dismissedDexcomConnectBannerTime, err := time.Parse(time.RFC3339, getAttribute(attributes, "preferences.dismissedDexcomConnectBannerTime"))
63+
if err == nil {
64+
prefs.DismissedDexcomConnectBannerTime = &dismissedDexcomConnectBannerTime
65+
}
66+
dismissedUpdateTypeBannerTime, err := time.Parse(time.RFC3339, getAttribute(attributes, "preferences.dismissedUpdateTypeBannerTime"))
67+
if err == nil {
68+
prefs.DismissedUpdateTypeBannerTime = &dismissedUpdateTypeBannerTime
69+
}
70+
71+
return prefs, true
72+
}

user/profile.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ const (
1616
// of a time.Time is to ignore timezones when marshaling.
1717
type Date string
1818

19+
// UserProfile represents user specific non keycloak attributes, formerly in seagull.
20+
// It is named somewhat redundantly UserProfile instead of Profile because that type
21+
// is already in use.
1922
type UserProfile struct {
2023
FullName string `json:"fullName"`
2124
Patient *PatientProfile `json:"patient,omitempty"`

user/settings.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package user
2+
3+
import (
4+
"fmt"
5+
"strconv"
6+
)
7+
8+
type Settings struct {
9+
SiteChangeSource string `json:"siteChangeSource"`
10+
BGTarget *settingsBGTarget `json:"bgTarget,omitempty"`
11+
Units *settingsUnits `json:"units,omitempty"`
12+
}
13+
14+
type settingsBGTarget struct {
15+
High float64 `json:"high"`
16+
Low float64 `json:"low"`
17+
}
18+
19+
type settingsUnits struct {
20+
BG string `json:"bg"`
21+
}
22+
23+
func (s *Settings) ToAttributes() map[string][]string {
24+
attributes := map[string][]string{}
25+
26+
addAttribute(attributes, "settings.siteChangeSource", s.SiteChangeSource)
27+
if s.BGTarget != nil {
28+
addAttribute(attributes, "settings.bgTarget.high", fmt.Sprintf("%.1f", s.BGTarget.High))
29+
addAttribute(attributes, "settings.bgTarget.low", fmt.Sprintf("%.1f", s.BGTarget.Low))
30+
}
31+
if s.Units != nil {
32+
addAttribute(attributes, "settings.units.bg", s.Units.BG)
33+
}
34+
return attributes
35+
}
36+
37+
func settingsFromAttributes(attributes map[string][]string) (settings *Settings, ok bool) {
38+
if !containsAnyAttributeKeys(attributes, "settings.siteChangeSource", "settings.bgTarget.high", "settings.bgTarget.low", "settings.units.bg") {
39+
return nil, false
40+
}
41+
42+
settings = &Settings{}
43+
settings.SiteChangeSource = getAttribute(attributes, "settings.siteChangeSource")
44+
if containsAnyAttributeKeys(attributes, "settings.bgTarget.high", "settings.bgTarget.low") {
45+
low, lowErr := strconv.ParseFloat(getAttribute(attributes, "settings.bgTarget.low"), 64)
46+
high, highErr := strconv.ParseFloat(getAttribute(attributes, "settings.bgTarget.high"), 64)
47+
if lowErr == nil && highErr == nil {
48+
settings.BGTarget = &settingsBGTarget{
49+
Low: low,
50+
High: high,
51+
}
52+
}
53+
}
54+
if containsAnyAttributeKeys(attributes, "settings.units.bg") {
55+
settings.Units = &settingsUnits{
56+
BG: getAttribute(attributes, "settings.units.bg"),
57+
}
58+
}
59+
return settings, true
60+
}

user/user_accessor.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ type UserAccessor interface {
3636
RemoveTokensForUser(ctx context.Context, userId string) error
3737
UpdateUserProfile(ctx context.Context, id string, p *UserProfile) error
3838
DeleteUserProfile(ctx context.Context, id string) error
39+
UpdateUserPreferences(ctx context.Context, id string, p *Preferences) error
40+
DeleteUserPreferences(ctx context.Context, id string) error
41+
UpdateUserSettings(ctx context.Context, id string, s *Settings) error
42+
DeleteUserSettings(ctx context.Context, id string) error
3943
}
4044

4145
type TokenIntrospectionResult struct {

0 commit comments

Comments
 (0)