From eb40471a6f5bbef0a30df50d571a60205b768ac0 Mon Sep 17 00:00:00 2001 From: Vishal Dalwadi Date: Mon, 21 Jul 2025 12:14:09 +0530 Subject: [PATCH 1/3] feat(go): add support for user settings; --- controllers/user.go | 42 ++++++++++++++++++++++++++++++++++++++++++ logic/settings.go | 45 +++++++++++++++++++++++++++++++++++++++------ migrate/migrate.go | 6 +++--- models/settings.go | 9 ++++++--- 4 files changed, 90 insertions(+), 12 deletions(-) diff --git a/controllers/user.go b/controllers/user.go index 01e5fac1..c0fd348f 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -47,6 +47,8 @@ func userHandlers(r *mux.Router) { r.HandleFunc("/api/users/{username}", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(getUser)))).Methods(http.MethodGet) r.HandleFunc("/api/users/{username}/enable", logic.SecurityCheck(true, http.HandlerFunc(enableUserAccount))).Methods(http.MethodPost) r.HandleFunc("/api/users/{username}/disable", logic.SecurityCheck(true, http.HandlerFunc(disableUserAccount))).Methods(http.MethodPost) + r.HandleFunc("/api/users/{username}/settings", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(getUserSettings)))).Methods(http.MethodGet) + r.HandleFunc("/api/users/{username}/settings", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(updateUserSettings)))).Methods(http.MethodPut) r.HandleFunc("/api/v1/users", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(getUserV1)))).Methods(http.MethodGet) r.HandleFunc("/api/users", logic.SecurityCheck(true, http.HandlerFunc(getUsers))).Methods(http.MethodGet) r.HandleFunc("/api/v1/users/roles", logic.SecurityCheck(true, http.HandlerFunc(ListRoles))).Methods(http.MethodGet) @@ -731,6 +733,46 @@ func disableUserAccount(w http.ResponseWriter, r *http.Request) { logic.ReturnSuccessResponse(w, r, "user account disabled") } +// @Summary Get a user's preferences and settings +// @Router /api/users/{username}/settings [get] +// @Tags Users +// @Param username path string true "Username of the user" +// @Success 200 {object} models.SuccessResponse +func getUserSettings(w http.ResponseWriter, r *http.Request) { + userID := r.Header.Get("user") + userSettings := logic.GetUserSettings(userID) + logic.ReturnSuccessResponseWithJson(w, r, userSettings, "fetched user settings") +} + +// @Summary Update a user's preferences and settings +// @Router /api/users/{username}/settings [put] +// @Tags Users +// @Param username path string true "Username of the user" +// @Success 200 {object} models.SuccessResponse +// @Failure 400 {object} models.ErrorResponse +// @Failure 500 {object} models.ErrorResponse +func updateUserSettings(w http.ResponseWriter, r *http.Request) { + userID := r.Header.Get("user") + var req models.UserSettings + err := json.NewDecoder(r.Body).Decode(&req) + if err != nil { + logger.Log(0, "failed to decode request body: ", err.Error()) + err = fmt.Errorf("invalid request body: %v", err) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + + err = logic.UpsertUserSettings(userID, req) + if err != nil { + err = fmt.Errorf("failed to update user settings: %v", err.Error()) + logger.Log(0, err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + + logic.ReturnSuccessResponseWithJson(w, r, req, "updated user settings") +} + // swagger:route GET /api/v1/users user getUserV1 // // Get an individual user with role info. diff --git a/logic/settings.go b/logic/settings.go index 632e5c39..d511e458 100644 --- a/logic/settings.go +++ b/logic/settings.go @@ -15,11 +15,17 @@ import ( "github.com/gravitl/netmaker/servercfg" ) -var serverSettingsDBKey = "server_cfg" +var ServerSettingsDBKey = "server_cfg" var SettingsMutex = &sync.RWMutex{} +var defaultUserSettings = models.UserSettings{ + TextSize: "16", + Theme: models.Dark, + ReducedMotion: false, +} + func GetServerSettings() (s models.ServerSettings) { - data, err := database.FetchRecord(database.SERVER_SETTINGS, serverSettingsDBKey) + data, err := database.FetchRecord(database.SERVER_SETTINGS, ServerSettingsDBKey) if err != nil { return } @@ -37,13 +43,43 @@ func UpsertServerSettings(s models.ServerSettings) error { if err != nil { return err } - err = database.Insert(serverSettingsDBKey, string(data), database.SERVER_SETTINGS) + err = database.Insert(ServerSettingsDBKey, string(data), database.SERVER_SETTINGS) if err != nil { return err } return nil } +func GetUserSettings(userID string) models.UserSettings { + data, err := database.FetchRecord(database.SERVER_SETTINGS, userID) + if err != nil { + return defaultUserSettings + } + var userSettings models.UserSettings + err = json.Unmarshal([]byte(data), &userSettings) + if err != nil { + return defaultUserSettings + } + + return userSettings +} + +func UpsertUserSettings(userID string, userSettings models.UserSettings) error { + if userSettings.TextSize == "" { + userSettings.TextSize = "16" + } + + if userSettings.Theme == "" { + userSettings.Theme = models.Dark + } + + data, err := json.Marshal(userSettings) + if err != nil { + return err + } + return database.Insert(userID, string(data), database.SERVER_SETTINGS) +} + func ValidateNewSettings(req models.ServerSettings) bool { // TODO: add checks for different fields return true @@ -76,9 +112,6 @@ func GetServerSettingsFromEnv() (s models.ServerSettings) { DefaultDomain: servercfg.GetDefaultDomain(), Stun: servercfg.IsStunEnabled(), StunServers: servercfg.GetStunServers(), - TextSize: "16", - Theme: models.Dark, - ReducedMotion: false, } return diff --git a/migrate/migrate.go b/migrate/migrate.go index 2e156ef6..d47542eb 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -25,7 +25,7 @@ import ( // Run - runs all migrations func Run() { - settings() + migrateSettings() updateEnrollmentKeys() assignSuperAdmin() createDefaultTagsAndPolicies() @@ -629,8 +629,8 @@ func migrateToEgressV1() { } } -func settings() { - _, err := database.FetchRecords(database.SERVER_SETTINGS) +func migrateSettings() { + _, err := database.FetchRecord(database.SERVER_SETTINGS, logic.ServerSettingsDBKey) if database.IsEmptyRecord(err) { logic.UpsertServerSettings(logic.GetServerSettingsFromEnv()) } diff --git a/models/settings.go b/models/settings.go index 8e5f7781..7ed44285 100644 --- a/models/settings.go +++ b/models/settings.go @@ -40,8 +40,11 @@ type ServerSettings struct { DefaultDomain string `json:"default_domain"` Stun bool `json:"stun"` StunServers string `json:"stun_servers"` - Theme Theme `json:"theme"` - TextSize string `json:"text_size"` - ReducedMotion bool `json:"reduced_motion"` AuditLogsRetentionPeriodInDays int `json:"audit_logs_retention_period"` } + +type UserSettings struct { + Theme Theme `json:"theme"` + TextSize string `json:"text_size"` + ReducedMotion bool `json:"reduced_motion"` +} From 7727a60e1275324ff830386f701f50878576eeb4 Mon Sep 17 00:00:00 2001 From: Vishal Dalwadi Date: Tue, 22 Jul 2025 10:33:17 +0530 Subject: [PATCH 2/3] feat(go): delete user settings on user delete; --- controllers/user.go | 1 + logic/settings.go | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/controllers/user.go b/controllers/user.go index c0fd348f..37a5d482 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -1325,6 +1325,7 @@ func deleteUser(w http.ResponseWriter, r *http.Request) { } } } + _ = logic.DeleteUserInvite(user.UserName) mq.PublishPeerUpdate(false) if servercfg.IsDNSMode() { logic.SetDNS() diff --git a/logic/settings.go b/logic/settings.go index d511e458..834577bb 100644 --- a/logic/settings.go +++ b/logic/settings.go @@ -80,6 +80,10 @@ func UpsertUserSettings(userID string, userSettings models.UserSettings) error { return database.Insert(userID, string(data), database.SERVER_SETTINGS) } +func DeleteUserSettings(userID string) error { + return database.DeleteRecord(database.SERVER_SETTINGS, userID) +} + func ValidateNewSettings(req models.ServerSettings) bool { // TODO: add checks for different fields return true From b778243c2c3b14988c9e9e9dbb502ee4f102fc74 Mon Sep 17 00:00:00 2001 From: Vishal Dalwadi Date: Mon, 28 Jul 2025 22:15:25 +0530 Subject: [PATCH 3/3] fix(go): go build errors; --- controllers/server.go | 9 --------- models/events.go | 2 -- 2 files changed, 11 deletions(-) diff --git a/controllers/server.go b/controllers/server.go index eec9ed06..27780be4 100644 --- a/controllers/server.go +++ b/controllers/server.go @@ -377,15 +377,6 @@ func identifySettingsUpdateAction(old, new models.ServerSettings) models.Action return models.UpdateMonitoringAndDebuggingSettings } - if old.Theme != new.Theme { - return models.UpdateDisplaySettings - } - - if old.TextSize != new.TextSize || - old.ReducedMotion != new.ReducedMotion { - return models.UpdateAccessibilitySettings - } - if old.EmailSenderAddr != new.EmailSenderAddr || old.EmailSenderUser != new.EmailSenderUser || old.EmailSenderPassword != new.EmailSenderPassword || diff --git a/models/events.go b/models/events.go index 4a6e1603..d6529b99 100644 --- a/models/events.go +++ b/models/events.go @@ -29,8 +29,6 @@ const ( UpdateClientSettings Action = "UPDATE_CLIENT_SETTINGS" UpdateAuthenticationSecuritySettings Action = "UPDATE_AUTHENTICATION_SECURITY_SETTINGS" UpdateMonitoringAndDebuggingSettings Action = "UPDATE_MONITORING_AND_DEBUGGING_SETTINGS" - UpdateDisplaySettings Action = "UPDATE_DISPLAY_SETTINGS" - UpdateAccessibilitySettings Action = "UPDATE_ACCESSIBILITY_SETTINGS" UpdateSMTPSettings Action = "UPDATE_EMAIL_SETTINGS" UpdateIDPSettings Action = "UPDATE_IDP_SETTINGS" )