Compare commits

..

No commits in common. "8b681922f6b5f450feab8a15aecb9ce657cc6088" and "82c00b9a01902fac7f7610c21c1ad3693191a0da" have entirely different histories.

13 changed files with 28 additions and 383 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() {
// TODO: rerender template with errors // 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

@ -59,19 +59,13 @@ func main() {
sessionManager: sessionManager, sessionManager: sessionManager,
websites: &models.WebsiteModel{DB: db}, websites: &models.WebsiteModel{DB: db},
guestbooks: &models.GuestbookModel{DB: db}, guestbooks: &models.GuestbookModel{DB: db},
users: &models.UserModel{DB: db, Settings: make(map[string]models.Setting)}, users: &models.UserModel{DB: db},
guestbookComments: &models.GuestbookCommentModel{DB: db}, guestbookComments: &models.GuestbookCommentModel{DB: db},
formDecoder: formDecoder, formDecoder: formDecoder,
debug: *debug, debug: *debug,
timezones: getAvailableTimezones(), timezones: getAvailableTimezones(),
} }
err = app.users.InitializeSettingsMap()
if err != nil {
logger.Error(err.Error())
os.Exit(1)
}
tlsConfig := &tls.Config{ tlsConfig := &tls.Config{
CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256}, CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256},
} }

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, AllowedSettingValueId integer NOT NULL,
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, AllowedSettingValueId integer NOT NULL,
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

@ -3,11 +3,9 @@ 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")
) )

View File

@ -5,14 +5,6 @@ import (
"time" "time"
) )
type GuestbookSettings struct {
IsCommentingEnabled bool
ReenableCommenting time.Time
IsVisible bool
FilteredWords []string
AllowRemoteHostAccess bool
}
type Guestbook struct { type Guestbook struct {
ID int64 ID int64
ShortId uint64 ShortId uint64
@ -21,7 +13,6 @@ type Guestbook struct {
Created time.Time Created time.Time
Deleted time.Time Deleted time.Time
IsActive bool IsActive bool
Settings GuestbookSettings
} }
type GuestbookModel struct { type GuestbookModel struct {
@ -79,47 +70,3 @@ func (m *GuestbookModel) GetAll(userId int64) ([]Guestbook, error) {
} }
return guestbooks, nil return guestbooks, nil
} }
func (m *GuestbookModel) initializeGuestbookSettings(guestbookId int64, settings UserSettings) error {
stmt := `INSERT INTO guestbook_settings (GuestbookId, SettingId, AllowedSettingValueId, UnconstrainedValue) VALUES
(?, ?, ?, ?),
(?, ?, ?, ?),
(?, ?, ?, ?),
(?, ?, ?, ?),
(?, ?, ?, ?)`
_ = len(stmt)
return nil
}
func (m *GuestbookModel) UpdateSetting(guestbookId int64, value string) {
stmt := `UPDATE guestbook_settings SET
AllowedSettingValueId=IFNULL((SELECT Id FROM allowed_setting_values WHERE SettingId = guestbook_settings.SettingId AND ItemValue = ?), AllowedSettingValueId),
UnconstrainedValue=(SELECT ? FROM settings WHERE settings.Id = guestbook_settings.SettingId AND settings.Constrained=0)
WHERE GuestbookId = ?
AND SettingId = (SELECT Id from Settings WHERE Description=?);`
_ = len(stmt)
}
func (m *GuestbookModel) SetCommentingEnabled(guestbookId int64, enabled bool) error {
return nil
}
func (m *GuestbookModel) SetReenableCommentingDate(guestbookId int64, reenableTime time.Time) error {
return nil
}
func (m *GuestbookModel) SetVisible(guestbookId int64, visible bool) error {
return nil
}
func (m *GuestbookModel) AddFilteredWord(guestbookId int64, word string) error {
return nil
}
func (m *GuestbookModel) RemoveFilteredWord(guestbookId int64, word string) error {
return nil
}
func (m *GuestbookModel) SetRemoteHostAccess(guestbookId int64, allowed bool) error {
return nil
}

View File

@ -1,223 +0,0 @@
package models
import (
"database/sql"
"maps"
"strconv"
"time"
)
type SettingModel struct {
DB *sql.DB
}
type SettingConfig struct {
}
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
dataTypeDesc string
settingGroup SettingGroup
settingGroupDesc string
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 (s Setting) Validate(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 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

@ -10,14 +10,14 @@ import (
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
const (
u_timezone = 1
)
type UserSettings struct { type UserSettings struct {
LocalTimezone *time.Location LocalTimezone *time.Location
} }
const (
USER_TIMEZONE = "local_timezone"
)
type User struct { type User struct {
ID int64 ID int64
ShortId uint64 ShortId uint64
@ -31,40 +31,7 @@ type User struct {
} }
type UserModel struct { type UserModel struct {
DB *sql.DB DB *sql.DB
Settings map[string]Setting
}
func (m *UserModel) InitializeSettingsMap() error {
if m.Settings == nil {
m.Settings = make(map[string]Setting)
}
stmt := `SELECT settings.Id, settings.Description, Constrained, d.Id, d.Description, g.Id, g.Description, MinValue, MaxValue
FROM settings
LEFT JOIN setting_data_types d ON settings.DataType = d.Id
LEFT JOIN setting_groups g ON settings.SettingGroup = g.Id
WHERE SettingGroup = (SELECT Id FROM setting_groups WHERE Description = 'user' LIMIT 1)`
result, err := m.DB.Query(stmt)
if err != nil {
return err
}
for result.Next() {
var s Setting
var mn sql.NullString
var mx sql.NullString
err := result.Scan(&s.id, &s.description, &s.constrained, &s.dataType, &s.dataTypeDesc, &s.settingGroup, &s.settingGroupDesc, &mn, &mx)
if mn.Valid {
s.minValue = mn.String
}
if mx.Valid {
s.maxValue = mx.String
}
if err != nil {
return err
}
m.Settings[s.description] = s
}
return nil
} }
func (m *UserModel) Insert(shortId uint64, username string, email string, password string, settings UserSettings) error { func (m *UserModel) Insert(shortId uint64, username string, email string, password string, settings UserSettings) error {
@ -87,7 +54,13 @@ func (m *UserModel) Insert(shortId uint64, username string, email string, passwo
if err != nil { if err != nil {
return err return err
} }
err = m.initializeUserSettings(id, settings) settingsStmt := `INSERT INTO user_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
} }
@ -201,7 +174,7 @@ func (m *UserModel) GetSettings(userId int64) (UserSettings, error) {
return settings, err return settings, err
} }
switch id { switch id {
case m.Settings[USER_TIMEZONE].id: case u_timezone:
settings.LocalTimezone, err = time.LoadLocation(unconstrainedValue.String) settings.LocalTimezone, err = time.LoadLocation(unconstrainedValue.String)
if err != nil { if err != nil {
panic(err) panic(err)
@ -211,53 +184,9 @@ func (m *UserModel) GetSettings(userId int64) (UserSettings, error) {
return settings, err return settings, err
} }
func (m *UserModel) initializeUserSettings(userId int64, settings UserSettings) error {
stmt := `INSERT INTO user_settings (UserId, SettingId, AllowedSettingValueId, UnconstrainedValue)
VALUES (?, ?, ?, ?)`
_, err := m.DB.Exec(stmt, userId, m.Settings[USER_TIMEZONE].id, nil, settings.LocalTimezone.String())
if err != nil {
return err
}
return nil
}
func (m *UserModel) UpdateUserSettings(userId int64, settings UserSettings) error {
err := m.UpdateSetting(userId, m.Settings[USER_TIMEZONE], settings.LocalTimezone.String())
if err != nil {
return err
}
return nil
}
func (m *UserModel) UpdateSetting(userId int64, setting Setting, value string) error {
stmt := `UPDATE user_settings SET
AllowedSettingValueId=IFNULL(
(SELECT Id FROM allowed_setting_values WHERE SettingId = user_settings.SettingId AND ItemValue = ?), AllowedSettingValueId
),
UnconstrainedValue=(SELECT ? FROM settings WHERE settings.Id = user_settings.SettingId AND settings.Constrained=0)
WHERE userId = ?
AND SettingId = (SELECT Id from Settings WHERE Description=?);`
result, err := m.DB.Exec(stmt, value, value, userId, setting.description)
if err != nil {
return err
}
rows, err := result.RowsAffected()
if err != nil {
return err
}
if rows != 1 {
return ErrInvalidSettingValue
}
return nil
}
func (m *UserModel) SetLocalTimezone(userId int64, timezone string) error { func (m *UserModel) SetLocalTimezone(userId int64, timezone string) error {
setting := m.Settings[USER_TIMEZONE] stmt := `UPDATE user_settings SET UnconstrainedValue = ? WHERE UserId = ?`
valid := setting.Validate(timezone) _, err := m.DB.Exec(stmt, timezone, userId)
if !valid {
return ErrInvalidSettingValue
}
err := m.UpdateSetting(userId, setting, timezone)
if err != nil { if err != nil {
return err return err
} }

View File

@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT. // Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.833 // templ: version: v0.3.857
package views package views
//lint:file-ignore SA4006 This context is only used if a nested component is present. //lint:file-ignore SA4006 This context is only used if a nested component is present.

View File

@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT. // Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.833 // templ: version: v0.3.857
package views package views
//lint:file-ignore SA4006 This context is only used if a nested component is present. //lint:file-ignore SA4006 This context is only used if a nested component is present.

View File

@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT. // Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.833 // templ: version: v0.3.857
package views package views
//lint:file-ignore SA4006 This context is only used if a nested component is present. //lint:file-ignore SA4006 This context is only used if a nested component is present.

View File

@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT. // Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.833 // templ: version: v0.3.857
package views package views
//lint:file-ignore SA4006 This context is only used if a nested component is present. //lint:file-ignore SA4006 This context is only used if a nested component is present.

View File

@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT. // Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.833 // templ: version: v0.3.857
package views package views
//lint:file-ignore SA4006 This context is only used if a nested component is present. //lint:file-ignore SA4006 This context is only used if a nested component is present.

View File

@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT. // Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.833 // templ: version: v0.3.857
package views package views
//lint:file-ignore SA4006 This context is only used if a nested component is present. //lint:file-ignore SA4006 This context is only used if a nested component is present.