user settings
This commit is contained in:
		
							parent
							
								
									82c00b9a01
								
							
						
					
					
						commit
						3ce55bb870
					
				@ -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)
 | 
				
			||||||
 | 
				
			|||||||
@ -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
 | 
				
			||||||
 | 
				
			|||||||
@ -3,9 +3,11 @@ package models
 | 
				
			|||||||
import "errors"
 | 
					import "errors"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
    ErrNoRecord = errors.New("models: no matching record found")
 | 
						ErrNoRecord = errors.New("models: no matching record found")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    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
									
								
							
							
						
						
									
										202
									
								
								internal/models/settings.go
									
									
									
									
									
										Normal 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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -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
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user