user settings

This commit is contained in:
yequari 2025-05-09 20:24:53 -07:00
parent 82c00b9a01
commit 3ce55bb870
5 changed files with 230 additions and 16 deletions

View File

@ -147,7 +147,7 @@ func (app *application) putUserSettings(w http.ResponseWriter, r *http.Request)
} }
form.CheckField(validator.PermittedValue(form.LocalTimezone, app.timezones...), "timezone", "Invalid value") form.CheckField(validator.PermittedValue(form.LocalTimezone, app.timezones...), "timezone", "Invalid value")
if !form.Valid() { if !form.Valid() {
// rerender template with errors // TODO: rerender template with errors
app.clientError(w, http.StatusUnprocessableEntity) app.clientError(w, http.StatusUnprocessableEntity)
} }
err = app.users.SetLocalTimezone(userId, form.LocalTimezone) err = app.users.SetLocalTimezone(userId, form.LocalTimezone)

View File

@ -38,7 +38,7 @@ CREATE TABLE user_settings (
Id integer primary key autoincrement, Id integer primary key autoincrement,
UserId integer NOT NULL, UserId integer NOT NULL,
SettingId integer NOT NULL, SettingId integer NOT NULL,
AllowedSettingValueId integer NOT NULL, AllowedSettingValueId integer,
UnconstrainedValue varchar(256), UnconstrainedValue varchar(256),
FOREIGN KEY (UserId) REFERENCES users(Id) FOREIGN KEY (UserId) REFERENCES users(Id)
ON DELETE RESTRICT ON DELETE RESTRICT
@ -55,7 +55,7 @@ CREATE TABLE guestbook_settings (
Id integer primary key autoincrement, Id integer primary key autoincrement,
GuestbookId integer NOT NULL, GuestbookId integer NOT NULL,
SettingId integer NOT NULL, SettingId integer NOT NULL,
AllowedSettingValueId integer NOT NULL, AllowedSettingValueId integer,
UnconstrainedValue varchar(256), UnconstrainedValue varchar(256),
FOREIGN KEY (GuestbookId) REFERENCES guestbooks(Id) FOREIGN KEY (GuestbookId) REFERENCES guestbooks(Id)
ON DELETE RESTRICT ON DELETE RESTRICT

View File

@ -8,4 +8,6 @@ var (
ErrInvalidCredentials = errors.New("models: invalid credentials") ErrInvalidCredentials = errors.New("models: invalid credentials")
ErrDuplicateEmail = errors.New("models: duplicate email") ErrDuplicateEmail = errors.New("models: duplicate email")
ErrInvalidSettingValue = errors.New("models: invalid setting value")
) )

202
internal/models/settings.go Normal file
View File

@ -0,0 +1,202 @@
package models
import (
"database/sql"
"maps"
"strconv"
"time"
)
type SettingGroup int
const (
SettingGroupUser SettingGroup = 1
SettingGroupGuestbook SettingGroup = 2
)
type SettingDataType int
const (
SettingTypeInt SettingDataType = 1
SettingTypeDate SettingDataType = 2
SettingTypeAlphanum SettingDataType = 3
)
type SettingValue struct {
Id int
SettingId int
AllowedSettingId int
UnconstrainedValue string
}
type AllowedSettingValue struct {
id int
itemValue string
caption string
}
func (s *AllowedSettingValue) Id() int {
return s.id
}
func (s *AllowedSettingValue) ItemValue() string {
return s.itemValue
}
func (s *AllowedSettingValue) Caption() string {
return s.caption
}
type Setting struct {
id int
description string
constrained bool
dataType SettingDataType
settingGroup SettingGroup
minValue string // TODO: Maybe should be int?
maxValue string
allowedValues map[int]AllowedSettingValue
}
func (s *Setting) Id() int {
return s.id
}
func (s *Setting) Description() string {
return s.description
}
func (s *Setting) Constrained() bool {
return s.constrained
}
func (s *Setting) DataType() SettingDataType {
return s.dataType
}
func (s *Setting) SettingGroup() SettingGroup {
return s.settingGroup
}
func (s *Setting) MinValue() string {
return s.minValue
}
func (s *Setting) MaxValue() string {
return s.maxValue
}
func (s *Setting) AllowedValues() map[int]AllowedSettingValue {
result := make(map[int]AllowedSettingValue, 50)
maps.Copy(result, s.allowedValues)
return result
}
func (s *Setting) ValidateUnconstrained(value string) bool {
switch s.dataType {
case SettingTypeInt:
return s.validateInt(value)
case SettingTypeAlphanum:
return s.validateAlphanum(value)
case SettingTypeDate:
return s.validateDatetime(value)
}
return false
}
func (s *Setting) ValidateConstrained(value *AllowedSettingValue) bool {
_, exists := s.allowedValues[value.id]
if s.constrained && exists {
return true
}
return false
}
func (s *Setting) validateInt(value string) bool {
v, err := strconv.ParseInt(value, 10, 0)
if err != nil {
return false
}
var min int64
var max int64
if len(s.minValue) > 0 {
min, err = strconv.ParseInt(s.minValue, 10, 0)
if err != nil {
return false
}
if v < min {
return false
}
}
if len(s.maxValue) > 0 {
max, err = strconv.ParseInt(s.maxValue, 10, 0)
if err != nil {
return false
}
if v < max {
return false
}
}
return true
}
func (s *Setting) validateDatetime(value string) bool {
v, err := time.Parse(time.DateTime, value)
if err != nil {
return false
}
var min time.Time
var max time.Time
if len(s.minValue) > 0 {
min, err = time.Parse(time.DateTime, s.minValue)
if err != nil {
return false
}
if v.Before(min) {
return false
}
}
if len(s.maxValue) > 0 {
max, err = time.Parse(time.DateTime, s.maxValue)
if err != nil {
return false
}
if v.After(max) {
return false
}
}
return false
}
func (s *Setting) validateAlphanum(value string) bool {
return true
}
func validateSetting(db *sql.DB, settingId int, value string) (bool, error) {
stmt := `SELECT s.Id, Description, Constrained, DataType, SettingGroup, MinValue, MaxValue, a.Id
FROM settings AS s LEFT JOIN allowed_setting_values AS a ON s.Id = a.SettingId AND a.ItemValue = ?
WHERE s.Id = ?`
result := db.QueryRow(stmt, value, settingId)
var s Setting
var minval sql.NullString
var maxval sql.NullString
var allowedId sql.NullInt64
err := result.Scan(&s.id, &s.description, &s.constrained, &s.dataType, &s.settingGroup, &minval, &maxval, &allowedId)
if err != nil {
return false, err
}
if s.constrained && allowedId.Valid {
return true, nil
}
switch s.dataType {
case SettingTypeInt:
return s.validateInt(value), nil
case SettingTypeAlphanum:
return s.validateAlphanum(value), nil
case SettingTypeDate:
return s.validateDatetime(value), nil
}
return false, nil
}

View File

@ -54,13 +54,7 @@ func (m *UserModel) Insert(shortId uint64, username string, email string, passwo
if err != nil { if err != nil {
return err return err
} }
settingsStmt := `INSERT INTO user_settings err = m.initializeUserSettings(id, settings)
(UserId, SettingId, AllowedSettingValueId, UnconstrainedValue)
VALUES (?, ?, ?, ?)`
_, err = m.DB.Exec(settingsStmt, id, u_timezone, nil, settings.LocalTimezone.String())
if err != nil {
return err
}
return nil return nil
} }
@ -184,9 +178,25 @@ func (m *UserModel) GetSettings(userId int64) (UserSettings, error) {
return settings, err return settings, err
} }
func (m *UserModel) SetLocalTimezone(userId int64, timezone string) error { func (m *UserModel) initializeUserSettings(userId int64, settings UserSettings) error {
stmt := `UPDATE user_settings SET UnconstrainedValue = ? WHERE UserId = ?` stmt := `INSERT INTO user_settings (UserId, SettingId, AllowedSettingValueId, UnconstrainedValue) VALUES (?, ?, ?, ?)`
_, err := m.DB.Exec(stmt, timezone, userId) _, err := m.DB.Exec(stmt, userId, u_timezone, nil, settings.LocalTimezone.String())
if err != nil {
return err
}
return nil
}
func (m *UserModel) SetLocalTimezone(userId int64, timezone string) error {
valid, err := validateSetting(m.DB, u_timezone, timezone)
if err != nil {
return err
}
if !valid {
return ErrInvalidSettingValue
}
stmt := `UPDATE user_settings SET UnconstrainedValue = ? WHERE UserId = ?`
_, err = m.DB.Exec(stmt, timezone, userId)
if err != nil { if err != nil {
return err return err
} }