Skip to content

Commit

Permalink
Add seagull settings and preferences object.
Browse files Browse the repository at this point in the history
  • Loading branch information
lostlevels committed Mar 22, 2024
1 parent 7d32881 commit 5abaa66
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 1 deletion.
2 changes: 2 additions & 0 deletions user/full_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ type FullUser struct {
DeletedUserID string `json:"deletedUserId,omitempty" bson:"deletedUserId,omitempty"`
Attributes map[string][]string `json:"-"`
Profile *UserProfile `json:"-"`
Settings *Settings `json:"-"`
Preferences *Preferences `json:"-"`
FirstName string `json:"firstName,omitempty"`
LastName string `json:"lastName,omitempty"`
}
Expand Down
75 changes: 74 additions & 1 deletion user/keycloak_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,12 @@ type keycloakUser struct {

type keycloakUserAttributes struct {
TermsAcceptedDate []string `json:"terms_and_conditions,omitempty"`
Profile *UserProfile `json:"profile"`
Profile *UserProfile `json:"profile,omitempty"`
Preferences *Preferences `json:"preferences,omitempty"`
Settings *Settings `json:"settings,omitempty"`
}

// keycloakClient is a wrapper around gocloak for simplified access to keycloak
type keycloakClient struct {
cfg *KeycloakConfig
adminToken *oauth2.Token
Expand Down Expand Up @@ -202,6 +205,14 @@ func (c *keycloakClient) UpdateUser(ctx context.Context, user *keycloakUser) err
profileAttrs := user.Attributes.Profile.ToAttributes()
maps.Copy(attrs, profileAttrs)
}
if user.Attributes.Settings != nil {
settingsAttrs := user.Attributes.Settings.ToAttributes()
maps.Copy(attrs, settingsAttrs)
}
if user.Attributes.Preferences != nil {
prefsAttrs := user.Attributes.Preferences.ToAttributes()
maps.Copy(attrs, prefsAttrs)
}

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

func (c *keycloakClient) UpdateUserPreferences(ctx context.Context, id string, p *Preferences) error {
user, err := c.GetUserById(ctx, id)
if err != nil {
return err
}
if user == nil {
return ErrUserNotFound
}
user.Attributes.Preferences = p
return c.UpdateUser(ctx, user)
}

func (c *keycloakClient) DeleteUserPreferences(ctx context.Context, id string) error {
user, err := c.GetUserById(ctx, id)
if err != nil {
return err
}
if user == nil {
return ErrUserNotFound
}
user.Attributes.Preferences = nil
return c.UpdateUser(ctx, user)
}

func (c *keycloakClient) UpdateUserSettings(ctx context.Context, id string, s *Settings) error {
user, err := c.GetUserById(ctx, id)
if err != nil {
return err
}
if user == nil {
return ErrUserNotFound
}
user.Attributes.Settings = s
return c.UpdateUser(ctx, user)
}

func (c *keycloakClient) DeleteUserSettings(ctx context.Context, id string) error {
user, err := c.GetUserById(ctx, id)
if err != nil {
return err
}
if user == nil {
return ErrUserNotFound
}
user.Attributes.Settings = nil
return c.UpdateUser(ctx, user)
}

func (c *keycloakClient) UpdateUserPassword(ctx context.Context, id, password string) error {
token, err := c.getAdminToken(ctx)
if err != nil {
Expand Down Expand Up @@ -528,6 +587,12 @@ func newKeycloakUser(gocloakUser *gocloak.User) *keycloakUser {
if prof, ok := profileFromAttributes(attrs); ok {
user.Attributes.Profile = prof
}
if prefs, ok := preferencesFromAttributes(attrs); ok {
user.Attributes.Preferences = prefs
}
if settings, ok := settingsFromAttributes(attrs); ok {
user.Attributes.Settings = settings
}
}

if gocloakUser.RealmRoles != nil {
Expand Down Expand Up @@ -556,6 +621,8 @@ func newUserFromKeycloakUser(keycloakUser *keycloakUser) *FullUser {
IsMigrated: true,
Enabled: keycloakUser.Enabled,
Profile: attrs.Profile,
Settings: attrs.Settings,
Preferences: attrs.Preferences,
}

// All non-custodial users have a password and it's important to set the hash to a non-empty value.
Expand Down Expand Up @@ -591,6 +658,12 @@ func userToKeycloakUser(u *FullUser) *keycloakUser {
if u.Profile != nil {
keycloakUser.Attributes.Profile = u.Profile
}
if u.Preferences != nil {
keycloakUser.Attributes.Preferences = u.Preferences
}
if u.Settings != nil {
keycloakUser.Attributes.Settings = u.Settings
}

return keycloakUser
}
Expand Down
17 changes: 17 additions & 0 deletions user/keycloak_user_accessor.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"golang.org/x/oauth2"
)

// keycloakUserAccessor implements UserAccessor by using keycloak
type keycloakUserAccessor struct {
cfg *KeycloakConfig
adminToken *oauth2.Token
Expand Down Expand Up @@ -227,3 +228,19 @@ func (m *keycloakUserAccessor) UpdateUserProfile(ctx context.Context, userId str
func (m *keycloakUserAccessor) DeleteUserProfile(ctx context.Context, userId string) error {
return m.keycloakClient.DeleteUserProfile(ctx, userId)
}

func (m *keycloakUserAccessor) UpdateUserPreferences(ctx context.Context, userId string, p *Preferences) error {
return m.keycloakClient.UpdateUserPreferences(ctx, userId, p)
}

func (m *keycloakUserAccessor) DeleteUserPreferences(ctx context.Context, userId string) error {
return m.keycloakClient.DeleteUserPreferences(ctx, userId)
}

func (m *keycloakUserAccessor) UpdateUserSettings(ctx context.Context, userId string, s *Settings) error {
return m.keycloakClient.UpdateUserSettings(ctx, userId, s)
}

func (m *keycloakUserAccessor) DeleteUserSettings(ctx context.Context, userId string) error {
return m.keycloakClient.DeleteUserSettings(ctx, userId)
}
72 changes: 72 additions & 0 deletions user/preferences.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package user

import (
"fmt"
"strconv"
"time"
)

type Preferences struct {
SeenShareDataBannerDate *time.Time `json:"seenShareDataBannerDate"`
SeenShareDataBannerCount int `json:"seenShareDataBannerCount"`
DismissedShareDataBannerTime *time.Time `json:"dismissedShareDataBannerTime"`
DismissedDonateYourDataBannerTime *time.Time `json:"dismissedDonateYourDataBannerTime"`
DismissedDexcomConnectBannerTime *time.Time `json:"dismissedDexcomConnectBannerTime"`
DismissedUpdateTypeBannerTime *time.Time `json:"dismissedUpdateTypeBannerTime"`
}

func (p *Preferences) ToAttributes() map[string][]string {
attributes := map[string][]string{}

if p.SeenShareDataBannerDate != nil {
addAttribute(attributes, "preferences.seenShareDataBannerDate", p.SeenShareDataBannerDate.Format(time.RFC3339))
}
addAttribute(attributes, "preferences.seenShareDataBannerCount", fmt.Sprintf("%d", p.SeenShareDataBannerCount))
if p.DismissedShareDataBannerTime != nil {
addAttribute(attributes, "preferences.dismissedShareDataBannerTime", p.DismissedShareDataBannerTime.Format(time.RFC3339))
}
if p.DismissedDonateYourDataBannerTime != nil {
addAttribute(attributes, "preferences.dismissedDonateYourDataBannerTime", p.DismissedDonateYourDataBannerTime.Format(time.RFC3339))
}
if p.DismissedDexcomConnectBannerTime != nil {
addAttribute(attributes, "preferences.dismissedDexcomConnectBannerTime", p.DismissedDexcomConnectBannerTime.Format(time.RFC3339))
}
if p.DismissedUpdateTypeBannerTime != nil {
addAttribute(attributes, "preferences.dismissedUpdateTypeBannerTime", p.DismissedUpdateTypeBannerTime.Format(time.RFC3339))
}
return attributes
}

func preferencesFromAttributes(attributes map[string][]string) (prefs *Preferences, ok bool) {
if !containsAnyAttributeKeys(attributes, "preferences.seenShareDataBannerDate", "preferences.seenShareDataBannerCount", "preferences.dismissedShareDataBannerTime", "preferences.dismissedDonateYourDataBannerTime", "preferences.dismissedDexcomConnectBannerTime", "preferences.dismissedUpdateTypeBannerTime") {
return nil, false
}

prefs = &Preferences{}
seenShareDataBannerDate, err := time.Parse(time.RFC3339, getAttribute(attributes, "preferences.seenShareDataBannerDate"))
if err == nil {
prefs.SeenShareDataBannerDate = &seenShareDataBannerDate
}
seenShareDataBannerCount, err := strconv.Atoi(getAttribute(attributes, "preferences.seenShareDataBannerCount"))
if err == nil {
prefs.SeenShareDataBannerCount = seenShareDataBannerCount
}
dismissedShareDataBannerTime, err := time.Parse(time.RFC3339, getAttribute(attributes, "preferences.dismissedShareDataBannerTime"))
if err == nil {
prefs.DismissedShareDataBannerTime = &dismissedShareDataBannerTime
}
dismissedDonateYourDataBannerTime, err := time.Parse(time.RFC3339, getAttribute(attributes, "preferences.dismissedDonateYourDataBannerTime"))
if err == nil {
prefs.DismissedDonateYourDataBannerTime = &dismissedDonateYourDataBannerTime
}
dismissedDexcomConnectBannerTime, err := time.Parse(time.RFC3339, getAttribute(attributes, "preferences.dismissedDexcomConnectBannerTime"))
if err == nil {
prefs.DismissedDexcomConnectBannerTime = &dismissedDexcomConnectBannerTime
}
dismissedUpdateTypeBannerTime, err := time.Parse(time.RFC3339, getAttribute(attributes, "preferences.dismissedUpdateTypeBannerTime"))
if err == nil {
prefs.DismissedUpdateTypeBannerTime = &dismissedUpdateTypeBannerTime
}

return prefs, true
}
3 changes: 3 additions & 0 deletions user/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ const (
// of a time.Time is to ignore timezones when marshaling.
type Date string

// UserProfile represents user specific non keycloak attributes, formerly in seagull.
// It is named somewhat redundantly UserProfile instead of Profile because that type
// is already in use.
type UserProfile struct {
FullName string `json:"fullName"`
Patient *PatientProfile `json:"patient,omitempty"`
Expand Down
60 changes: 60 additions & 0 deletions user/settings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package user

import (
"fmt"
"strconv"
)

type Settings struct {
SiteChangeSource string `json:"siteChangeSource"`
BGTarget *settingsBGTarget `json:"bgTarget,omitempty"`
Units *settingsUnits `json:"units,omitempty"`
}

type settingsBGTarget struct {
High float64 `json:"high"`
Low float64 `json:"low"`
}

type settingsUnits struct {
BG string `json:"bg"`
}

func (s *Settings) ToAttributes() map[string][]string {
attributes := map[string][]string{}

addAttribute(attributes, "settings.siteChangeSource", s.SiteChangeSource)
if s.BGTarget != nil {
addAttribute(attributes, "settings.bgTarget.high", fmt.Sprintf("%.1f", s.BGTarget.High))
addAttribute(attributes, "settings.bgTarget.low", fmt.Sprintf("%.1f", s.BGTarget.Low))
}
if s.Units != nil {
addAttribute(attributes, "settings.units.bg", s.Units.BG)
}
return attributes
}

func settingsFromAttributes(attributes map[string][]string) (settings *Settings, ok bool) {
if !containsAnyAttributeKeys(attributes, "settings.siteChangeSource", "settings.bgTarget.high", "settings.bgTarget.low", "settings.units.bg") {
return nil, false
}

settings = &Settings{}
settings.SiteChangeSource = getAttribute(attributes, "settings.siteChangeSource")
if containsAnyAttributeKeys(attributes, "settings.bgTarget.high", "settings.bgTarget.low") {
low, lowErr := strconv.ParseFloat(getAttribute(attributes, "settings.bgTarget.low"), 64)
high, highErr := strconv.ParseFloat(getAttribute(attributes, "settings.bgTarget.high"), 64)
if lowErr == nil && highErr == nil {
settings.BGTarget = &settingsBGTarget{
Low: low,
High: high,
}
}
}
if containsAnyAttributeKeys(attributes, "settings.units.bg") {
settings.Units = &settingsUnits{
BG: getAttribute(attributes, "settings.units.bg"),
}
}
return settings, true
}
4 changes: 4 additions & 0 deletions user/user_accessor.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ type UserAccessor interface {
RemoveTokensForUser(ctx context.Context, userId string) error
UpdateUserProfile(ctx context.Context, id string, p *UserProfile) error
DeleteUserProfile(ctx context.Context, id string) error
UpdateUserPreferences(ctx context.Context, id string, p *Preferences) error
DeleteUserPreferences(ctx context.Context, id string) error
UpdateUserSettings(ctx context.Context, id string, s *Settings) error
DeleteUserSettings(ctx context.Context, id string) error
}

type TokenIntrospectionResult struct {
Expand Down

0 comments on commit 5abaa66

Please sign in to comment.