From 82c00b9a01902fac7f7610c21c1ad3693191a0da Mon Sep 17 00:00:00 2001 From: yequari Date: Sat, 3 May 2025 23:14:58 -0700 Subject: [PATCH 01/11] implement user settings for timezone --- cmd/web/handlers_user.go | 34 ++++- cmd/web/helpers.go | 6 + cmd/web/main.go | 51 +++++++ cmd/web/routes.go | 3 +- db/create-settings-tables.sql | 76 ++++++++++ db/create-settings.sql | 9 ++ internal/forms/forms.go | 5 + internal/models/user.go | 71 ++++++++- ui/views/common_templ.go | 2 +- ui/views/guestbooks.templ | 4 +- ui/views/guestbooks_templ.go | 6 +- ui/views/home_templ.go | 2 +- ui/views/users.templ | 8 +- ui/views/users_settings.templ | 24 +++ ui/views/users_settings_templ.go | 141 +++++++++++++++++ ui/views/users_templ.go | 26 +++- ui/views/websites.templ | 250 +++++++++++++++---------------- ui/views/websites_templ.go | 22 +-- 18 files changed, 590 insertions(+), 150 deletions(-) create mode 100644 db/create-settings-tables.sql create mode 100644 db/create-settings.sql create mode 100644 ui/views/users_settings.templ create mode 100644 ui/views/users_settings_templ.go diff --git a/cmd/web/handlers_user.go b/cmd/web/handlers_user.go index 772c8d4..18e04bb 100644 --- a/cmd/web/handlers_user.go +++ b/cmd/web/handlers_user.go @@ -39,7 +39,8 @@ func (app *application) postUserRegister(w http.ResponseWriter, r *http.Request) return } shortId := app.createShortId() - err = app.users.Insert(shortId, form.Name, form.Email, form.Password) + settings := DefaultUserSettings() + err = app.users.Insert(shortId, form.Name, form.Email, form.Password, settings) if err != nil { if errors.Is(err, models.ErrDuplicateEmail) { form.AddFieldError("email", "Email address is already in use") @@ -129,3 +130,34 @@ func (app *application) getUser(w http.ResponseWriter, r *http.Request) { data := app.newCommonData(r) views.UserProfile(user.Username, data, user).Render(r.Context(), w) } + +func (app *application) getUserSettings(w http.ResponseWriter, r *http.Request) { + data := app.newCommonData(r) + views.UserSettingsView(data, app.timezones).Render(r.Context(), w) +} + +func (app *application) putUserSettings(w http.ResponseWriter, r *http.Request) { + userId := app.getCurrentUser(r).ID + var form forms.UserSettingsForm + err := app.decodePostForm(r, &form) + if err != nil { + app.clientError(w, http.StatusBadRequest) + app.serverError(w, r, err) + return + } + form.CheckField(validator.PermittedValue(form.LocalTimezone, app.timezones...), "timezone", "Invalid value") + if !form.Valid() { + // rerender template with errors + app.clientError(w, http.StatusUnprocessableEntity) + } + err = app.users.SetLocalTimezone(userId, form.LocalTimezone) + if err != nil { + app.serverError(w, r, err) + return + } + app.sessionManager.Put(r.Context(), "flash", "Settings changed successfully") + data := app.newCommonData(r) + w.Header().Add("HX-Refresh", "true") + views.UserSettingsView(data, app.timezones).Render(r.Context(), w) + +} diff --git a/cmd/web/helpers.go b/cmd/web/helpers.go index ca0dfdd..43a6cc5 100644 --- a/cmd/web/helpers.go +++ b/cmd/web/helpers.go @@ -112,3 +112,9 @@ func (app *application) newCommonData(r *http.Request) views.CommonData { IsHtmx: r.Header.Get("Hx-Request") == "true", } } + +func DefaultUserSettings() models.UserSettings { + return models.UserSettings{ + LocalTimezone: time.Now().UTC().Location(), + } +} diff --git a/cmd/web/main.go b/cmd/web/main.go index e1a2bec..3d7fb96 100644 --- a/cmd/web/main.go +++ b/cmd/web/main.go @@ -7,7 +7,9 @@ import ( "log/slog" "net/http" "os" + "strings" "time" + "unicode" "git.32bit.cafe/32bitcafe/guestbook/internal/models" "github.com/alexedwards/scs/sqlite3store" @@ -26,6 +28,7 @@ type application struct { sessionManager *scs.SessionManager formDecoder *schema.Decoder debug bool + timezones []string } func main() { @@ -60,6 +63,7 @@ func main() { guestbookComments: &models.GuestbookCommentModel{DB: db}, formDecoder: formDecoder, debug: *debug, + timezones: getAvailableTimezones(), } tlsConfig := &tls.Config{ @@ -97,3 +101,50 @@ func openDB(dsn string) (*sql.DB, error) { } return db, nil } + +func getAvailableTimezones() []string { + var zones []string + var zoneDirs = []string{ + "/usr/share/zoneinfo/", + "/usr/share/lib/zoneinfo/", + "/usr/lib/locale/TZ/", + } + for _, zd := range zoneDirs { + zones = walkTzDir(zd, zones) + for idx, zone := range zones { + zones[idx] = strings.ReplaceAll(zone, zd+"/", "") + } + } + return zones +} + +func walkTzDir(path string, zones []string) []string { + fileInfos, err := os.ReadDir(path) + if err != nil { + return zones + } + isAlpha := func(s string) bool { + for _, r := range s { + if !unicode.IsLetter(r) { + return false + } + } + return true + } + for _, info := range fileInfos { + if info.Name() != strings.ToUpper(info.Name()[:1])+info.Name()[1:] { + continue + } + if !isAlpha(info.Name()[:1]) { + continue + } + newPath := path + "/" + info.Name() + if info.IsDir() { + zones = walkTzDir(newPath, zones) + } else { + zones = append(zones, newPath) + } + } + return zones + +} diff --git a/cmd/web/routes.go b/cmd/web/routes.go index cc5b6b5..9690c7c 100644 --- a/cmd/web/routes.go +++ b/cmd/web/routes.go @@ -28,7 +28,8 @@ func (app *application) routes() http.Handler { // mux.Handle("GET /users", protected.ThenFunc(app.getUsersList)) mux.Handle("GET /users/{id}", protected.ThenFunc(app.getUser)) mux.Handle("POST /users/logout", protected.ThenFunc(app.postUserLogout)) - mux.Handle("GET /users/settings", protected.ThenFunc(app.notImplemented)) + mux.Handle("GET /users/settings", protected.ThenFunc(app.getUserSettings)) + mux.Handle("PUT /users/settings", protected.ThenFunc(app.putUserSettings)) mux.Handle("GET /users/privacy", protected.ThenFunc(app.notImplemented)) mux.Handle("GET /guestbooks", protected.ThenFunc(app.getAllGuestbooks)) diff --git a/db/create-settings-tables.sql b/db/create-settings-tables.sql new file mode 100644 index 0000000..07e19f1 --- /dev/null +++ b/db/create-settings-tables.sql @@ -0,0 +1,76 @@ +CREATE TABLE setting_groups ( + Id integer primary key autoincrement, + Description varchar(256) NOT NULL +); + +CREATE TABLE setting_data_types ( + Id integer primary key autoincrement, + Description varchar(64) NOT NULL +); + +CREATE TABLE settings ( + Id integer primary key autoincrement, + Description varchar(256) NOT NULL, + Constrained boolean NOT NULL, + DataType integer NOT NULL, + SettingGroup int NOT NULL, + MinValue varchar(6), + MaxValue varchar(6), + FOREIGN KEY (DataType) REFERENCES setting_data_types(Id) + ON DELETE RESTRICT + ON UPDATE RESTRICT, + FOREIGN KEY (SettingGroup) REFERENCES setting_groups(Id) + ON DELETE RESTRICT + ON UPDATE RESTRICT +); + +CREATE TABLE allowed_setting_values ( + Id integer primary key autoincrement, + SettingId integer NOT NULL, + ItemValue varchar(256), + Caption varchar(256), + FOREIGN KEY (SettingId) REFERENCES settings(Id) + ON DELETE RESTRICT + ON UPDATE RESTRICT +); + +CREATE TABLE user_settings ( + Id integer primary key autoincrement, + UserId integer NOT NULL, + SettingId integer NOT NULL, + AllowedSettingValueId integer NOT NULL, + UnconstrainedValue varchar(256), + FOREIGN KEY (UserId) REFERENCES users(Id) + ON DELETE RESTRICT + ON UPDATE RESTRICT, + FOREIGN KEY (SettingId) REFERENCES settings(Id) + ON DELETE RESTRICT + ON UPDATE RESTRICT, + FOREIGN KEY (AllowedSettingValueId) REFERENCES allowed_setting_values(Id) + ON DELETE RESTRICT + ON UPDATE RESTRICT +); + +CREATE TABLE guestbook_settings ( + Id integer primary key autoincrement, + GuestbookId integer NOT NULL, + SettingId integer NOT NULL, + AllowedSettingValueId integer NOT NULL, + UnconstrainedValue varchar(256), + FOREIGN KEY (GuestbookId) REFERENCES guestbooks(Id) + ON DELETE RESTRICT + ON UPDATE RESTRICT, + FOREIGN KEY (SettingId) REFERENCES settings(Id) + ON DELETE RESTRICT + ON UPDATE RESTRICT, + FOREIGN KEY (AllowedSettingValueId) REFERENCES allowed_setting_values(Id) + ON DELETE RESTRICT + ON UPDATE RESTRICT +); + +INSERT INTO setting_groups (Description) VALUES ('guestbook'); +INSERT INTO setting_groups (Description) VALUES ('user'); + +INSERT INTO setting_data_types (Description) VALUES ('alphanumeric'); +INSERT INTO setting_data_types (Description) VALUES ('integer'); +INSERT INTO setting_data_types (Description) VALUES ('datetime'); diff --git a/db/create-settings.sql b/db/create-settings.sql new file mode 100644 index 0000000..87ee494 --- /dev/null +++ b/db/create-settings.sql @@ -0,0 +1,9 @@ +INSERT INTO setting_groups (Description) VALUES ("guestbook"); +INSERT INTO setting_groups (Description) VALUES ("user"); + +INSERT INTO setting_data_types (Description) VALUES ("alphanumeric") +INSERT INTO setting_data_types (Description) VALUES ("integer") +INSERT INTO setting_data_types (Description) VALUES ("datetime") + +INSERT INTO settings (Description, Constrained, DataType, SettingGroup) +VALUES ("Local Timezone", 0, 1, 1); diff --git a/internal/forms/forms.go b/internal/forms/forms.go index 8d5664f..8891f2b 100644 --- a/internal/forms/forms.go +++ b/internal/forms/forms.go @@ -29,3 +29,8 @@ type WebsiteCreateForm struct { AuthorName string `schema:"authorname"` validator.Validator `schema:"-"` } + +type UserSettingsForm struct { + LocalTimezone string `schema:"timezones"` + validator.Validator `schema:"-"` +} diff --git a/internal/models/user.go b/internal/models/user.go index e4dfbe2..35ce1fd 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -10,6 +10,14 @@ import ( "golang.org/x/crypto/bcrypt" ) +const ( + u_timezone = 1 +) + +type UserSettings struct { + LocalTimezone *time.Location +} + type User struct { ID int64 ShortId uint64 @@ -19,20 +27,21 @@ type User struct { IsBanned bool HashedPassword []byte Created time.Time + Settings UserSettings } type UserModel struct { DB *sql.DB } -func (m *UserModel) Insert(shortId uint64, username string, email string, password string) error { +func (m *UserModel) Insert(shortId uint64, username string, email string, password string, settings UserSettings) error { hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 12) if err != nil { return err } stmt := `INSERT INTO users (ShortId, Username, Email, IsBanned, HashedPassword, Created) VALUES (?, ?, ?, FALSE, ?, ?)` - _, err = m.DB.Exec(stmt, shortId, username, email, hashedPassword, time.Now().UTC()) + result, err := m.DB.Exec(stmt, shortId, username, email, hashedPassword, time.Now().UTC()) if err != nil { if sqliteError, ok := err.(sqlite3.Error); ok { if sqliteError.ExtendedCode == 2067 && strings.Contains(sqliteError.Error(), "Email") { @@ -41,6 +50,17 @@ func (m *UserModel) Insert(shortId uint64, username string, email string, passwo } return err } + id, err := result.LastInsertId() + if err != nil { + return err + } + 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 } @@ -55,6 +75,11 @@ func (m *UserModel) Get(id uint64) (User, error) { } return User{}, err } + settings, err := m.GetSettings(u.ID) + if err != nil { + return u, err + } + u.Settings = settings return u, nil } @@ -69,6 +94,11 @@ func (m *UserModel) GetById(id int64) (User, error) { } return User{}, err } + settings, err := m.GetSettings(u.ID) + if err != nil { + return u, err + } + u.Settings = settings return u, nil } @@ -125,3 +155,40 @@ func (m *UserModel) Exists(id int64) (bool, error) { err := m.DB.QueryRow(stmt, id).Scan(&exists) return exists, err } + +func (m *UserModel) GetSettings(userId int64) (UserSettings, error) { + stmt := `SELECT u.SettingId, a.ItemValue, u.UnconstrainedValue FROM user_settings AS u + LEFT JOIN allowed_setting_values AS a ON u.SettingId = a.SettingId + WHERE UserId = ?` + var settings UserSettings + rows, err := m.DB.Query(stmt, userId) + if err != nil { + return settings, err + } + for rows.Next() { + var id int + var itemValue sql.NullString + var unconstrainedValue sql.NullString + err = rows.Scan(&id, &itemValue, &unconstrainedValue) + if err != nil { + return settings, err + } + switch id { + case u_timezone: + settings.LocalTimezone, err = time.LoadLocation(unconstrainedValue.String) + if err != nil { + panic(err) + } + } + } + return settings, err +} + +func (m *UserModel) SetLocalTimezone(userId int64, timezone string) error { + stmt := `UPDATE user_settings SET UnconstrainedValue = ? WHERE UserId = ?` + _, err := m.DB.Exec(stmt, timezone, userId) + if err != nil { + return err + } + return nil +} diff --git a/ui/views/common_templ.go b/ui/views/common_templ.go index f707c81..895a1cd 100644 --- a/ui/views/common_templ.go +++ b/ui/views/common_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.833 +// templ: version: v0.3.857 package views //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/ui/views/guestbooks.templ b/ui/views/guestbooks.templ index a8d56b7..92545f5 100644 --- a/ui/views/guestbooks.templ +++ b/ui/views/guestbooks.templ @@ -48,7 +48,7 @@ templ GuestbookDashboardCommentView(data CommonData, w models.Website, c models. | { c.AuthorSite } }

- { c.Created.Format("01-02-2006 03:04PM") } + { c.Created.In(data.CurrentUser.Settings.LocalTimezone).Format("01-02-2006 03:04PM") }

@@ -167,4 +167,4 @@ templ AllGuestbooksView(data CommonData, websites []models.Website) { } -} \ No newline at end of file +} diff --git a/ui/views/guestbooks_templ.go b/ui/views/guestbooks_templ.go index aab14aa..21732fa 100644 --- a/ui/views/guestbooks_templ.go +++ b/ui/views/guestbooks_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.833 +// templ: version: v0.3.857 package views //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -275,9 +275,9 @@ func GuestbookDashboardCommentView(data CommonData, w models.Website, c models.G return templ_7745c5c3_Err } var templ_7745c5c3_Var14 string - templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(c.Created.Format("01-02-2006 03:04PM")) + templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(c.Created.In(data.CurrentUser.Settings.LocalTimezone).Format("01-02-2006 03:04PM")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 51, Col: 56} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 51, Col: 100} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { diff --git a/ui/views/home_templ.go b/ui/views/home_templ.go index 23a3c6d..4829e83 100644 --- a/ui/views/home_templ.go +++ b/ui/views/home_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.833 +// templ: version: v0.3.857 package views //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/ui/views/users.templ b/ui/views/users.templ index 46467c8..20c5b26 100644 --- a/ui/views/users.templ +++ b/ui/views/users.templ @@ -79,5 +79,11 @@ templ UserProfile (title string, data CommonData, user models.User) { } } -templ UserSettings () { +templ UserSettings (title string, data CommonData, user models.User) { + @base(title, data) { +

+ + +
+ } } diff --git a/ui/views/users_settings.templ b/ui/views/users_settings.templ new file mode 100644 index 0000000..7f46ff8 --- /dev/null +++ b/ui/views/users_settings.templ @@ -0,0 +1,24 @@ +package views + +templ UserSettingsView(data CommonData, timezones []string) { + {{ user := data.CurrentUser }} + @base("User Settings", data) { +
+

User Settings

+
+ +

Timezone

+ + +
+
+ } +} diff --git a/ui/views/users_settings_templ.go b/ui/views/users_settings_templ.go new file mode 100644 index 0000000..5660866 --- /dev/null +++ b/ui/views/users_settings_templ.go @@ -0,0 +1,141 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.857 +package views + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +func UserSettingsView(data CommonData, timezones []string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + user := data.CurrentUser + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

User Settings

Timezone

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = base("User Settings", data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/ui/views/users_templ.go b/ui/views/users_templ.go index 661a6f5..81e1a2d 100644 --- a/ui/views/users_templ.go +++ b/ui/views/users_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.833 +// templ: version: v0.3.857 package views //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -400,7 +400,7 @@ func UserProfile(title string, data CommonData, user models.User) templ.Componen }) } -func UserSettings() templ.Component { +func UserSettings(title string, data CommonData, user models.User) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -421,6 +421,28 @@ func UserSettings() templ.Component { templ_7745c5c3_Var20 = templ.NopComponent } ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var21 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = base(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var21), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } return nil }) } diff --git a/ui/views/websites.templ b/ui/views/websites.templ index 5dbf533..cead964 100644 --- a/ui/views/websites.templ +++ b/ui/views/websites.templ @@ -4,151 +4,151 @@ import "fmt" import "git.32bit.cafe/32bitcafe/guestbook/internal/models" import "git.32bit.cafe/32bitcafe/guestbook/internal/forms" -func wUrl (w models.Website) string { - return fmt.Sprintf("/websites/%s", shortIdToSlug(w.ShortId)) +func wUrl(w models.Website) string { + return fmt.Sprintf("/websites/%s", shortIdToSlug(w.ShortId)) } templ wSidebar(website models.Website) { - {{ dashUrl := wUrl(website) + "/dashboard" }} - {{ gbUrl := wUrl(website) + "/guestbook" }} - + {{ dashUrl := wUrl(website) + "/dashboard" }} + {{ gbUrl := wUrl(website) + "/guestbook" }} + } -templ displayWebsites (websites []models.Website) { - if len(websites) == 0 { -

No Websites yet. Register a website.

- } else { - - } +templ displayWebsites(websites []models.Website) { + if len(websites) == 0 { +

No Websites yet. Register a website.

+ } else { + + } } templ websiteCreateForm(csrfToken string, form forms.WebsiteCreateForm) { - -
- {{ err, exists := form.FieldErrors["sitename"]}} - - if exists { - - } - -
-
- {{ err, exists = form.FieldErrors["siteurl"] }} - - if exists { - - } - -
-
- {{ err, exists = form.FieldErrors["authorname"] }} - - if exists { - - } - -
-
- -
+ +
+ {{ err, exists := form.FieldErrors["sitename"] }} + + if exists { + + } + +
+
+ {{ err, exists = form.FieldErrors["siteurl"] }} + + if exists { + + } + +
+
+ {{ err, exists = form.FieldErrors["authorname"] }} + + if exists { + + } + +
+
+ +
} templ WebsiteCreateButton() { - + } templ WebsiteList(title string, data CommonData, websites []models.Website) { - if data.IsHtmx { - @displayWebsites(websites) - } else { - @base(title, data) { -

My Websites

-
- @WebsiteCreateButton() -
-
- @displayWebsites(websites) -
- } - } + if data.IsHtmx { + @displayWebsites(websites) + } else { + @base(title, data) { +

My Websites

+

+ @WebsiteCreateButton() +

+
+ @displayWebsites(websites) +
+ } + } } templ WebsiteDashboard(title string, data CommonData, website models.Website) { - @base(title, data) { -
- @wSidebar(website) -
-

{ website.Name }

-

- Stats and stuff will go here. -

-
-
- } + @base(title, data) { +
+ @wSidebar(website) +
+

{ website.Name }

+

+ Stats and stuff will go here. +

+
+
+ } } templ WebsiteDashboardComingSoon(title string, data CommonData, website models.Website) { - @base(title, data) { -
- @wSidebar(website) -
-

{ website.Name }

-

- Coming Soon -

-
-
- } + @base(title, data) { +
+ @wSidebar(website) +
+

{ website.Name }

+

+ Coming Soon +

+
+
+ } } templ WebsiteCreate(title string, data CommonData, form forms.WebsiteCreateForm) { - if data.IsHtmx { -
- @websiteCreateForm(data.CSRFToken, form) -
- } else { -
- @websiteCreateForm(data.CSRFToken, form) -
- } -} \ No newline at end of file + if data.IsHtmx { +
+ @websiteCreateForm(data.CSRFToken, form) +
+ } else { +
+ @websiteCreateForm(data.CSRFToken, form) +
+ } +} diff --git a/ui/views/websites_templ.go b/ui/views/websites_templ.go index fbe1f45..c29b127 100644 --- a/ui/views/websites_templ.go +++ b/ui/views/websites_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.833 +// templ: version: v0.3.857 package views //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -46,7 +46,7 @@ func wSidebar(website models.Website) templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(website.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 16, Col: 30} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 16, Col: 21} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -189,7 +189,7 @@ func displayWebsites(websites []models.Website) templ.Component { var templ_7745c5c3_Var14 string templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(w.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 58, Col: 73} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 58, Col: 59} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { @@ -237,7 +237,7 @@ func websiteCreateForm(csrfToken string, form forms.WebsiteCreateForm) templ.Com var templ_7745c5c3_Var16 string templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(csrfToken) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 66, Col: 59} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 66, Col: 57} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) if templ_7745c5c3_Err != nil { @@ -260,7 +260,7 @@ func websiteCreateForm(csrfToken string, form forms.WebsiteCreateForm) templ.Com var templ_7745c5c3_Var17 string templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(err) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 71, Col: 38} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 71, Col: 29} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) if templ_7745c5c3_Err != nil { @@ -288,7 +288,7 @@ func websiteCreateForm(csrfToken string, form forms.WebsiteCreateForm) templ.Com var templ_7745c5c3_Var18 string templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(err) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 79, Col: 38} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 79, Col: 29} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) if templ_7745c5c3_Err != nil { @@ -316,7 +316,7 @@ func websiteCreateForm(csrfToken string, form forms.WebsiteCreateForm) templ.Com var templ_7745c5c3_Var19 string templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(err) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 87, Col: 38} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 87, Col: 29} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) if templ_7745c5c3_Err != nil { @@ -403,7 +403,7 @@ func WebsiteList(title string, data CommonData, websites []models.Website) templ }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "

My Websites

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "

My Websites

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -411,7 +411,7 @@ func WebsiteList(title string, data CommonData, websites []models.Website) templ if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -482,7 +482,7 @@ func WebsiteDashboard(title string, data CommonData, website models.Website) tem var templ_7745c5c3_Var25 string templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(website.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 121, Col: 34} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 121, Col: 22} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) if templ_7745c5c3_Err != nil { @@ -550,7 +550,7 @@ func WebsiteDashboardComingSoon(title string, data CommonData, website models.We var templ_7745c5c3_Var28 string templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(website.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 135, Col: 34} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 135, Col: 22} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) if templ_7745c5c3_Err != nil { -- 2.30.2 From 3ce55bb8707a671dc527ee7bff8636956f36d610 Mon Sep 17 00:00:00 2001 From: yequari Date: Fri, 9 May 2025 20:24:53 -0700 Subject: [PATCH 02/11] user settings --- cmd/web/handlers_user.go | 2 +- db/create-settings-tables.sql | 4 +- internal/models/errors.go | 8 +- internal/models/settings.go | 202 ++++++++++++++++++++++++++++++++++ internal/models/user.go | 30 +++-- 5 files changed, 230 insertions(+), 16 deletions(-) create mode 100644 internal/models/settings.go diff --git a/cmd/web/handlers_user.go b/cmd/web/handlers_user.go index 18e04bb..45f2087 100644 --- a/cmd/web/handlers_user.go +++ b/cmd/web/handlers_user.go @@ -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") if !form.Valid() { - // rerender template with errors + // TODO: rerender template with errors app.clientError(w, http.StatusUnprocessableEntity) } err = app.users.SetLocalTimezone(userId, form.LocalTimezone) diff --git a/db/create-settings-tables.sql b/db/create-settings-tables.sql index 07e19f1..4708887 100644 --- a/db/create-settings-tables.sql +++ b/db/create-settings-tables.sql @@ -38,7 +38,7 @@ CREATE TABLE user_settings ( Id integer primary key autoincrement, UserId integer NOT NULL, SettingId integer NOT NULL, - AllowedSettingValueId integer NOT NULL, + AllowedSettingValueId integer, UnconstrainedValue varchar(256), FOREIGN KEY (UserId) REFERENCES users(Id) ON DELETE RESTRICT @@ -55,7 +55,7 @@ CREATE TABLE guestbook_settings ( Id integer primary key autoincrement, GuestbookId integer NOT NULL, SettingId integer NOT NULL, - AllowedSettingValueId integer NOT NULL, + AllowedSettingValueId integer, UnconstrainedValue varchar(256), FOREIGN KEY (GuestbookId) REFERENCES guestbooks(Id) ON DELETE RESTRICT diff --git a/internal/models/errors.go b/internal/models/errors.go index 48a3033..2b02a0b 100644 --- a/internal/models/errors.go +++ b/internal/models/errors.go @@ -3,9 +3,11 @@ package models import "errors" 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") ) diff --git a/internal/models/settings.go b/internal/models/settings.go new file mode 100644 index 0000000..4e976c3 --- /dev/null +++ b/internal/models/settings.go @@ -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 +} diff --git a/internal/models/user.go b/internal/models/user.go index 35ce1fd..3d6432e 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -54,13 +54,7 @@ func (m *UserModel) Insert(shortId uint64, username string, email string, passwo if err != nil { return err } - 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 - } + err = m.initializeUserSettings(id, settings) return nil } @@ -184,9 +178,25 @@ func (m *UserModel) GetSettings(userId int64) (UserSettings, error) { return settings, err } -func (m *UserModel) SetLocalTimezone(userId int64, timezone string) error { - stmt := `UPDATE user_settings SET UnconstrainedValue = ? WHERE UserId = ?` - _, err := m.DB.Exec(stmt, timezone, userId) +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, 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 { return err } -- 2.30.2 From 2b9d0e11b868eb282f21c463635fd38f3edcba71 Mon Sep 17 00:00:00 2001 From: yequari Date: Fri, 9 May 2025 23:59:54 -0700 Subject: [PATCH 03/11] generic setting update code --- internal/models/user.go | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/internal/models/user.go b/internal/models/user.go index 3d6432e..5dbad1e 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -31,7 +31,8 @@ type User struct { } type UserModel struct { - DB *sql.DB + DB *sql.DB + Settings map[string]Setting } func (m *UserModel) Insert(shortId uint64, username string, email string, password string, settings UserSettings) error { @@ -187,6 +188,34 @@ func (m *UserModel) initializeUserSettings(userId int64, settings UserSettings) return nil } +func (m *UserModel) UpdateUserSettings(userId int64, settings UserSettings) error { + err := m.UpdateSetting(userId, m.Settings["local_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=(SELECT Id FROM allowed_setting_values WHERE SettingId = user_settings.SettingId AND ItemValue = ?), + 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) + if err != nil { + return err + } + rows, err := result.RowsAffected() + if err != nil { + return err + } + if rows != 1 { + return err + } + return nil +} + func (m *UserModel) SetLocalTimezone(userId int64, timezone string) error { valid, err := validateSetting(m.DB, u_timezone, timezone) if err != nil { -- 2.30.2 From 8b681922f6b5f450feab8a15aecb9ce657cc6088 Mon Sep 17 00:00:00 2001 From: yequari Date: Sun, 18 May 2025 10:49:24 -0700 Subject: [PATCH 04/11] restructure settings code --- cmd/web/main.go | 8 +++- internal/models/guestbook.go | 53 +++++++++++++++++++++++++ internal/models/settings.go | 37 ++++++++++++++---- internal/models/user.go | 66 ++++++++++++++++++++++++-------- ui/views/common_templ.go | 2 +- ui/views/guestbooks_templ.go | 2 +- ui/views/home_templ.go | 2 +- ui/views/users_settings_templ.go | 2 +- ui/views/users_templ.go | 2 +- ui/views/websites_templ.go | 2 +- 10 files changed, 144 insertions(+), 32 deletions(-) diff --git a/cmd/web/main.go b/cmd/web/main.go index 3d7fb96..7b678b6 100644 --- a/cmd/web/main.go +++ b/cmd/web/main.go @@ -59,13 +59,19 @@ func main() { sessionManager: sessionManager, websites: &models.WebsiteModel{DB: db}, guestbooks: &models.GuestbookModel{DB: db}, - users: &models.UserModel{DB: db}, + users: &models.UserModel{DB: db, Settings: make(map[string]models.Setting)}, guestbookComments: &models.GuestbookCommentModel{DB: db}, formDecoder: formDecoder, debug: *debug, timezones: getAvailableTimezones(), } + err = app.users.InitializeSettingsMap() + if err != nil { + logger.Error(err.Error()) + os.Exit(1) + } + tlsConfig := &tls.Config{ CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256}, } diff --git a/internal/models/guestbook.go b/internal/models/guestbook.go index 21d899e..c072c3b 100644 --- a/internal/models/guestbook.go +++ b/internal/models/guestbook.go @@ -5,6 +5,14 @@ import ( "time" ) +type GuestbookSettings struct { + IsCommentingEnabled bool + ReenableCommenting time.Time + IsVisible bool + FilteredWords []string + AllowRemoteHostAccess bool +} + type Guestbook struct { ID int64 ShortId uint64 @@ -13,6 +21,7 @@ type Guestbook struct { Created time.Time Deleted time.Time IsActive bool + Settings GuestbookSettings } type GuestbookModel struct { @@ -70,3 +79,47 @@ func (m *GuestbookModel) GetAll(userId int64) ([]Guestbook, error) { } 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 +} diff --git a/internal/models/settings.go b/internal/models/settings.go index 4e976c3..3666e58 100644 --- a/internal/models/settings.go +++ b/internal/models/settings.go @@ -7,6 +7,13 @@ import ( "time" ) +type SettingModel struct { + DB *sql.DB +} + +type SettingConfig struct { +} + type SettingGroup int const ( @@ -48,14 +55,16 @@ func (s *AllowedSettingValue) Caption() string { } 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 + 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 { @@ -173,6 +182,18 @@ 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 = ? diff --git a/internal/models/user.go b/internal/models/user.go index 5dbad1e..5925441 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -10,14 +10,14 @@ import ( "golang.org/x/crypto/bcrypt" ) -const ( - u_timezone = 1 -) - type UserSettings struct { LocalTimezone *time.Location } +const ( + USER_TIMEZONE = "local_timezone" +) + type User struct { ID int64 ShortId uint64 @@ -35,6 +35,38 @@ type UserModel struct { 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 { hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 12) if err != nil { @@ -169,7 +201,7 @@ func (m *UserModel) GetSettings(userId int64) (UserSettings, error) { return settings, err } switch id { - case u_timezone: + case m.Settings[USER_TIMEZONE].id: settings.LocalTimezone, err = time.LoadLocation(unconstrainedValue.String) if err != nil { panic(err) @@ -180,8 +212,9 @@ func (m *UserModel) GetSettings(userId int64) (UserSettings, error) { } 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, u_timezone, nil, settings.LocalTimezone.String()) + 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 } @@ -189,7 +222,7 @@ func (m *UserModel) initializeUserSettings(userId int64, settings UserSettings) } func (m *UserModel) UpdateUserSettings(userId int64, settings UserSettings) error { - err := m.UpdateSetting(userId, m.Settings["local_timezone"], settings.LocalTimezone.String()) + err := m.UpdateSetting(userId, m.Settings[USER_TIMEZONE], settings.LocalTimezone.String()) if err != nil { return err } @@ -198,11 +231,13 @@ func (m *UserModel) UpdateUserSettings(userId int64, settings UserSettings) erro func (m *UserModel) UpdateSetting(userId int64, setting Setting, value string) error { stmt := `UPDATE user_settings SET - AllowedSettingValueId=(SELECT Id FROM allowed_setting_values WHERE SettingId = user_settings.SettingId AND ItemValue = ?), + 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) + result, err := m.DB.Exec(stmt, value, value, userId, setting.description) if err != nil { return err } @@ -211,21 +246,18 @@ func (m *UserModel) UpdateSetting(userId int64, setting Setting, value string) e return err } if rows != 1 { - return err + return ErrInvalidSettingValue } return nil } func (m *UserModel) SetLocalTimezone(userId int64, timezone string) error { - valid, err := validateSetting(m.DB, u_timezone, timezone) - if err != nil { - return err - } + setting := m.Settings[USER_TIMEZONE] + valid := setting.Validate(timezone) if !valid { return ErrInvalidSettingValue } - stmt := `UPDATE user_settings SET UnconstrainedValue = ? WHERE UserId = ?` - _, err = m.DB.Exec(stmt, timezone, userId) + err := m.UpdateSetting(userId, setting, timezone) if err != nil { return err } diff --git a/ui/views/common_templ.go b/ui/views/common_templ.go index 895a1cd..f707c81 100644 --- a/ui/views/common_templ.go +++ b/ui/views/common_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.857 +// templ: version: v0.3.833 package views //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/ui/views/guestbooks_templ.go b/ui/views/guestbooks_templ.go index 21732fa..c5242f4 100644 --- a/ui/views/guestbooks_templ.go +++ b/ui/views/guestbooks_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.857 +// templ: version: v0.3.833 package views //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/ui/views/home_templ.go b/ui/views/home_templ.go index 4829e83..23a3c6d 100644 --- a/ui/views/home_templ.go +++ b/ui/views/home_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.857 +// templ: version: v0.3.833 package views //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/ui/views/users_settings_templ.go b/ui/views/users_settings_templ.go index 5660866..1528730 100644 --- a/ui/views/users_settings_templ.go +++ b/ui/views/users_settings_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.857 +// templ: version: v0.3.833 package views //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/ui/views/users_templ.go b/ui/views/users_templ.go index 81e1a2d..5b51716 100644 --- a/ui/views/users_templ.go +++ b/ui/views/users_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.857 +// templ: version: v0.3.833 package views //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/ui/views/websites_templ.go b/ui/views/websites_templ.go index c29b127..0e47209 100644 --- a/ui/views/websites_templ.go +++ b/ui/views/websites_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.857 +// templ: version: v0.3.833 package views //lint:file-ignore SA4006 This context is only used if a nested component is present. -- 2.30.2 From 3c811c9533ea1f5a13ed5fe4b482c256417d8be9 Mon Sep 17 00:00:00 2001 From: yequari Date: Sun, 18 May 2025 11:27:54 -0700 Subject: [PATCH 05/11] clean up unnecessary code --- internal/models/settings.go | 145 ++++++++++-------------------------- internal/models/user.go | 2 +- 2 files changed, 39 insertions(+), 108 deletions(-) diff --git a/internal/models/settings.go b/internal/models/settings.go index 3666e58..3dfadc9 100644 --- a/internal/models/settings.go +++ b/internal/models/settings.go @@ -1,70 +1,55 @@ package models import ( - "database/sql" - "maps" "strconv" "time" ) -type SettingModel struct { - DB *sql.DB +type SettingGroup struct { + id int + description string } -type SettingConfig struct { +func (g *SettingGroup) Id() int { + return g.id } -type SettingGroup int +func (g *SettingGroup) Description() string { + return g.description +} const ( - SettingGroupUser SettingGroup = 1 - SettingGroupGuestbook SettingGroup = 2 + SETTING_GROUP_USER = "user" + SETTING_GROUP_GUESTBOOK = "guestbook" ) -type SettingDataType int +type SettingDataType struct { + id int + description string +} + +func (d *SettingDataType) Id() int { + return d.id +} + +func (d *SettingDataType) Description() string { + return d.description +} const ( - SettingTypeInt SettingDataType = 1 - SettingTypeDate SettingDataType = 2 - SettingTypeAlphanum SettingDataType = 3 + SETTING_TYPE_INTEGER = "integer" + SETTING_TYPE_STRING = "alphanumeric" + SETTING_TYPE_DATE = "datetime" ) -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 + id int + description string + constrained bool + dataType SettingDataType + settingGroup SettingGroup + minValue string + maxValue string } func (s *Setting) Id() int { @@ -95,32 +80,18 @@ 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: +func (s *Setting) Validate(value string) bool { + switch s.dataType.description { + case SETTING_TYPE_INTEGER: return s.validateInt(value) - case SettingTypeAlphanum: + case SETTING_TYPE_STRING: return s.validateAlphanum(value) - case SettingTypeDate: + case SETTING_TYPE_DATE: 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 { @@ -179,45 +150,5 @@ func (s *Setting) validateDatetime(value string) bool { } 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 + return len(value) >= 0 } diff --git a/internal/models/user.go b/internal/models/user.go index 5925441..5f107cd 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -52,7 +52,7 @@ func (m *UserModel) InitializeSettingsMap() error { 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) + err := result.Scan(&s.id, &s.description, &s.constrained, &s.dataType.id, &s.dataType.description, &s.settingGroup.id, &s.settingGroup.description, &mn, &mx) if mn.Valid { s.minValue = mn.String } -- 2.30.2 From 1a41a94b41fca194adc0920ec4b289ef87400a96 Mon Sep 17 00:00:00 2001 From: yequari Date: Fri, 23 May 2025 10:15:30 -0700 Subject: [PATCH 06/11] use generic SetUserSettings function, remove specific function --- cmd/web/handlers_user.go | 10 ++++++++-- internal/models/user.go | 28 +++++++++++----------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/cmd/web/handlers_user.go b/cmd/web/handlers_user.go index 45f2087..8690065 100644 --- a/cmd/web/handlers_user.go +++ b/cmd/web/handlers_user.go @@ -3,6 +3,7 @@ package main import ( "errors" "net/http" + "time" "git.32bit.cafe/32bitcafe/guestbook/internal/forms" "git.32bit.cafe/32bitcafe/guestbook/internal/models" @@ -137,7 +138,7 @@ func (app *application) getUserSettings(w http.ResponseWriter, r *http.Request) } func (app *application) putUserSettings(w http.ResponseWriter, r *http.Request) { - userId := app.getCurrentUser(r).ID + user := app.getCurrentUser(r) var form forms.UserSettingsForm err := app.decodePostForm(r, &form) if err != nil { @@ -150,7 +151,12 @@ func (app *application) putUserSettings(w http.ResponseWriter, r *http.Request) // TODO: rerender template with errors app.clientError(w, http.StatusUnprocessableEntity) } - err = app.users.SetLocalTimezone(userId, form.LocalTimezone) + user.Settings.LocalTimezone, err = time.LoadLocation(form.LocalTimezone) + if err != nil { + app.serverError(w, r, err) + return + } + err = app.users.UpdateUserSettings(user.ID, user.Settings) if err != nil { app.serverError(w, r, err) return diff --git a/internal/models/user.go b/internal/models/user.go index 5f107cd..85f5b65 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -15,7 +15,7 @@ type UserSettings struct { } const ( - USER_TIMEZONE = "local_timezone" + SettingUserTimezone = "local_timezone" ) type User struct { @@ -88,6 +88,9 @@ func (m *UserModel) Insert(shortId uint64, username string, email string, passwo return err } err = m.initializeUserSettings(id, settings) + if err != nil { + return err + } return nil } @@ -201,7 +204,7 @@ func (m *UserModel) GetSettings(userId int64) (UserSettings, error) { return settings, err } switch id { - case m.Settings[USER_TIMEZONE].id: + case m.Settings[SettingUserTimezone].id: settings.LocalTimezone, err = time.LoadLocation(unconstrainedValue.String) if err != nil { panic(err) @@ -214,7 +217,7 @@ func (m *UserModel) GetSettings(userId int64) (UserSettings, error) { 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()) + _, err := m.DB.Exec(stmt, userId, m.Settings[SettingUserTimezone].id, nil, settings.LocalTimezone.String()) if err != nil { return err } @@ -222,7 +225,7 @@ func (m *UserModel) initializeUserSettings(userId int64, settings UserSettings) } func (m *UserModel) UpdateUserSettings(userId int64, settings UserSettings) error { - err := m.UpdateSetting(userId, m.Settings[USER_TIMEZONE], settings.LocalTimezone.String()) + err := m.UpdateSetting(userId, m.Settings[SettingUserTimezone], settings.LocalTimezone.String()) if err != nil { return err } @@ -230,6 +233,10 @@ func (m *UserModel) UpdateUserSettings(userId int64, settings UserSettings) erro } func (m *UserModel) UpdateSetting(userId int64, setting Setting, value string) error { + valid := setting.Validate(value) + if !valid { + return ErrInvalidSettingValue + } stmt := `UPDATE user_settings SET AllowedSettingValueId=IFNULL( (SELECT Id FROM allowed_setting_values WHERE SettingId = user_settings.SettingId AND ItemValue = ?), AllowedSettingValueId @@ -250,16 +257,3 @@ func (m *UserModel) UpdateSetting(userId int64, setting Setting, value string) e } return nil } - -func (m *UserModel) SetLocalTimezone(userId int64, timezone string) error { - setting := m.Settings[USER_TIMEZONE] - valid := setting.Validate(timezone) - if !valid { - return ErrInvalidSettingValue - } - err := m.UpdateSetting(userId, setting, timezone) - if err != nil { - return err - } - return nil -} -- 2.30.2 From 260ddbe74071459e38c009035824325026e3b77e Mon Sep 17 00:00:00 2001 From: yequari Date: Sun, 25 May 2025 23:05:53 -0700 Subject: [PATCH 07/11] move user settings code --- ui/views/users.templ | 164 +++++++++++++++++-------------- ui/views/users_settings.templ | 24 ----- ui/views/users_settings_templ.go | 141 -------------------------- ui/views/users_templ.go | 115 +++++++++++++++++++--- 4 files changed, 188 insertions(+), 256 deletions(-) delete mode 100644 ui/views/users_settings.templ delete mode 100644 ui/views/users_settings_templ.go diff --git a/ui/views/users.templ b/ui/views/users.templ index 20c5b26..4fb839b 100644 --- a/ui/views/users.templ +++ b/ui/views/users.templ @@ -5,85 +5,99 @@ import ( "git.32bit.cafe/32bitcafe/guestbook/internal/models" ) -templ UserLogin (title string, data CommonData, form forms.UserLoginForm) { - @base(title, data) { -

Login

-
- - for _, error := range form.NonFieldErrors { -
{ error }
- } -
- - {{ error, exists := form.FieldErrors["email"] }} - if exists { - - } - -
-
- - {{ error, exists = form.FieldErrors["password"] }} - if exists { - - } - -
-
- -
-
- } +templ UserLogin(title string, data CommonData, form forms.UserLoginForm) { + @base(title, data) { +

Login

+
+ + for _, error := range form.NonFieldErrors { +
{ error }
+ } +
+ + {{ error, exists := form.FieldErrors["email"] }} + if exists { + + } + +
+
+ + {{ error, exists = form.FieldErrors["password"] }} + if exists { + + } + +
+
+ +
+
+ } } -templ UserRegistration (title string, data CommonData, form forms.UserRegistrationForm) { - @base(title, data) { -

User Registration

-
- -
- {{ error, exists := form.FieldErrors["name"] }} - - if exists { - - } - -
-
- {{ error, exists = form.FieldErrors["email"] }} - - if exists { - - } - -
-
- {{ error, exists = form.FieldErrors["password"] }} - - if exists { - - } - -
-
- -
-
- } +templ UserRegistration(title string, data CommonData, form forms.UserRegistrationForm) { + @base(title, data) { +

User Registration

+
+ +
+ {{ error, exists := form.FieldErrors["name"] }} + + if exists { + + } + +
+
+ {{ error, exists = form.FieldErrors["email"] }} + + if exists { + + } + +
+
+ {{ error, exists = form.FieldErrors["password"] }} + + if exists { + + } + +
+
+ +
+
+ } } -templ UserProfile (title string, data CommonData, user models.User) { - @base(title, data) { -

{ user.Username }

-

{ user.Email }

- } +templ UserProfile(title string, data CommonData, user models.User) { + @base(title, data) { +

{ user.Username }

+

{ user.Email }

+ } } -templ UserSettings (title string, data CommonData, user models.User) { - @base(title, data) { -
- - -
- } +templ UserSettingsView(data CommonData, timezones []string) { + {{ user := data.CurrentUser }} + @base("User Settings", data) { +
+

User Settings

+
+ + + + +
+
+ } } diff --git a/ui/views/users_settings.templ b/ui/views/users_settings.templ deleted file mode 100644 index 7f46ff8..0000000 --- a/ui/views/users_settings.templ +++ /dev/null @@ -1,24 +0,0 @@ -package views - -templ UserSettingsView(data CommonData, timezones []string) { - {{ user := data.CurrentUser }} - @base("User Settings", data) { -
-

User Settings

-
- -

Timezone

- - -
-
- } -} diff --git a/ui/views/users_settings_templ.go b/ui/views/users_settings_templ.go deleted file mode 100644 index 1528730..0000000 --- a/ui/views/users_settings_templ.go +++ /dev/null @@ -1,141 +0,0 @@ -// Code generated by templ - DO NOT EDIT. - -// templ: version: v0.3.833 -package views - -//lint:file-ignore SA4006 This context is only used if a nested component is present. - -import "github.com/a-h/templ" -import templruntime "github.com/a-h/templ/runtime" - -func UserSettingsView(data CommonData, timezones []string) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var1 := templ.GetChildren(ctx) - if templ_7745c5c3_Var1 == nil { - templ_7745c5c3_Var1 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - user := data.CurrentUser - templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

User Settings

Timezone

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) - templ_7745c5c3_Err = base("User Settings", data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -var _ = templruntime.GeneratedTemplate diff --git a/ui/views/users_templ.go b/ui/views/users_templ.go index 5b51716..c791e2d 100644 --- a/ui/views/users_templ.go +++ b/ui/views/users_templ.go @@ -53,7 +53,7 @@ func UserLogin(title string, data CommonData, form forms.UserLoginForm) templ.Co var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.CSRFToken) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 12, Col: 73} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 12, Col: 64} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -71,7 +71,7 @@ func UserLogin(title string, data CommonData, form forms.UserLoginForm) templ.Co var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(error) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 14, Col: 42} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 14, Col: 30} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -95,7 +95,7 @@ func UserLogin(title string, data CommonData, form forms.UserLoginForm) templ.Co var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(error) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 20, Col: 48} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 20, Col: 33} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -113,7 +113,7 @@ func UserLogin(title string, data CommonData, form forms.UserLoginForm) templ.Co var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(form.Email) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 22, Col: 66} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 22, Col: 55} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -132,7 +132,7 @@ func UserLogin(title string, data CommonData, form forms.UserLoginForm) templ.Co var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(error) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 28, Col: 48} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 28, Col: 33} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -197,7 +197,7 @@ func UserRegistration(title string, data CommonData, form forms.UserRegistration var templ_7745c5c3_Var10 string templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(data.CSRFToken) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 43, Col: 73} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 43, Col: 64} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { @@ -220,7 +220,7 @@ func UserRegistration(title string, data CommonData, form forms.UserRegistration var templ_7745c5c3_Var11 string templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(error) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 48, Col: 48} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 48, Col: 33} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) if templ_7745c5c3_Err != nil { @@ -238,7 +238,7 @@ func UserRegistration(title string, data CommonData, form forms.UserRegistration var templ_7745c5c3_Var12 string templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(form.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 50, Col: 81} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 50, Col: 70} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { @@ -261,7 +261,7 @@ func UserRegistration(title string, data CommonData, form forms.UserRegistration var templ_7745c5c3_Var13 string templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(error) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 56, Col: 48} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 56, Col: 33} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { @@ -279,7 +279,7 @@ func UserRegistration(title string, data CommonData, form forms.UserRegistration var templ_7745c5c3_Var14 string templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(form.Email) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 58, Col: 76} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 58, Col: 65} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { @@ -302,7 +302,7 @@ func UserRegistration(title string, data CommonData, form forms.UserRegistration var templ_7745c5c3_Var15 string templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(error) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 64, Col: 48} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 64, Col: 33} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) if templ_7745c5c3_Err != nil { @@ -367,7 +367,7 @@ func UserProfile(title string, data CommonData, user models.User) templ.Componen var templ_7745c5c3_Var18 string templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(user.Username) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 77, Col: 27} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 77, Col: 21} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) if templ_7745c5c3_Err != nil { @@ -380,7 +380,7 @@ func UserProfile(title string, data CommonData, user models.User) templ.Componen var templ_7745c5c3_Var19 string templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(user.Email) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 78, Col: 23} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 78, Col: 17} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) if templ_7745c5c3_Err != nil { @@ -400,7 +400,7 @@ func UserProfile(title string, data CommonData, user models.User) templ.Componen }) } -func UserSettings(title string, data CommonData, user models.User) templ.Component { +func UserSettingsView(data CommonData, timezones []string) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -421,6 +421,7 @@ func UserSettings(title string, data CommonData, user models.User) templ.Compone templ_7745c5c3_Var20 = templ.NopComponent } ctx = templ.ClearChildren(ctx) + user := data.CurrentUser templ_7745c5c3_Var21 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) @@ -433,13 +434,95 @@ func UserSettings(title string, data CommonData, user models.User) templ.Compone }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "

User Settings

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } return nil }) - templ_7745c5c3_Err = base(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var21), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = base("User Settings", data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var21), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } -- 2.30.2 From 019b31aa9f71cd52d77a6fbd8572f65241b76b00 Mon Sep 17 00:00:00 2001 From: yequari Date: Sun, 25 May 2025 23:07:44 -0700 Subject: [PATCH 08/11] implement guestbook settings db code --- internal/models/guestbook.go | 112 +++++++++++++++++++++++++++-------- 1 file changed, 87 insertions(+), 25 deletions(-) diff --git a/internal/models/guestbook.go b/internal/models/guestbook.go index c072c3b..a9e762a 100644 --- a/internal/models/guestbook.go +++ b/internal/models/guestbook.go @@ -2,6 +2,7 @@ package models import ( "database/sql" + "strconv" "time" ) @@ -13,6 +14,14 @@ type GuestbookSettings struct { AllowRemoteHostAccess bool } +const ( + SettingGbCommentingEnabled = "commenting_enabled" + SettingGbReenableComments = "reenable_comments" + SettingGbVisible = "is_visible" + SettingGbFilteredWords = "filtered_words" + SettingGbAllowRemote = "remote_enabled" +) + type Guestbook struct { ID int64 ShortId uint64 @@ -25,10 +34,43 @@ type Guestbook struct { } type GuestbookModel struct { - DB *sql.DB + DB *sql.DB + Settings map[string]Setting } -func (m *GuestbookModel) Insert(shortId uint64, userId int64, websiteId int64) (int64, error) { +func (m *GuestbookModel) 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 = 'guestbook' 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.id, &s.dataType.description, &s.settingGroup.id, &s.settingGroup.description, &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 *GuestbookModel) Insert(shortId uint64, userId int64, websiteId int64, settings GuestbookSettings) (int64, error) { stmt := `INSERT INTO guestbooks (ShortId, UserId, WebsiteId, Created, IsActive) VALUES(?, ?, ?, ?, TRUE)` result, err := m.DB.Exec(stmt, shortId, userId, websiteId, time.Now().UTC()) @@ -39,6 +81,10 @@ func (m *GuestbookModel) Insert(shortId uint64, userId int64, websiteId int64) ( if err != nil { return -1, err } + err = m.initializeGuestbookSettings(id, settings) + if err != nil { + return id, err + } return id, nil } @@ -80,35 +126,55 @@ func (m *GuestbookModel) GetAll(userId int64) ([]Guestbook, error) { return guestbooks, nil } -func (m *GuestbookModel) initializeGuestbookSettings(guestbookId int64, settings UserSettings) error { +func (m *GuestbookModel) initializeGuestbookSettings(guestbookId int64, settings GuestbookSettings) error { stmt := `INSERT INTO guestbook_settings (GuestbookId, SettingId, AllowedSettingValueId, UnconstrainedValue) VALUES (?, ?, ?, ?), (?, ?, ?, ?), (?, ?, ?, ?), (?, ?, ?, ?), (?, ?, ?, ?)` - _ = len(stmt) + _, err := m.DB.Exec(stmt, + guestbookId, m.Settings[SettingGbCommentingEnabled].id, settings.IsCommentingEnabled, nil, + guestbookId, m.Settings[SettingGbReenableComments].id, nil, settings.ReenableCommenting.String(), + guestbookId, m.Settings[SettingGbVisible].id, settings.IsVisible, nil, + guestbookId, m.Settings[SettingGbAllowRemote].id, settings.AllowRemoteHostAccess, nil) + if err != nil { + return err + } return nil } -func (m *GuestbookModel) UpdateSetting(guestbookId int64, value string) { +func (m *GuestbookModel) UpdateGuestbookSettings(guestbookId int64, settings GuestbookSettings) error { + err := m.UpdateSetting(guestbookId, m.Settings[SettingGbCommentingEnabled], strconv.FormatBool(settings.IsCommentingEnabled)) + if err != nil { + return err + } + err = m.UpdateSetting(guestbookId, m.Settings[SettingGbReenableComments], settings.ReenableCommenting.String()) + if err != nil { + return err + } + return nil +} + +func (m *GuestbookModel) UpdateSetting(guestbookId int64, setting Setting, value string) error { 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 { + 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=?);` + result, err := m.DB.Exec(stmt, value, value, guestbookId, setting.description) + if err != nil { + return err + } + rows, err := result.RowsAffected() + if err != nil { + return err + } + if rows != 1 { + return ErrInvalidSettingValue + } return nil } @@ -119,7 +185,3 @@ func (m *GuestbookModel) AddFilteredWord(guestbookId int64, word string) error { func (m *GuestbookModel) RemoveFilteredWord(guestbookId int64, word string) error { return nil } - -func (m *GuestbookModel) SetRemoteHostAccess(guestbookId int64, allowed bool) error { - return nil -} -- 2.30.2 From c3f08fb745215914fe6aa9ad6e6803edbf39a19c Mon Sep 17 00:00:00 2001 From: yequari Date: Sun, 25 May 2025 23:08:20 -0700 Subject: [PATCH 09/11] scaffolding for guestbook settings ui --- cmd/web/handlers_guestbook.go | 14 ++ cmd/web/handlers_website.go | 5 +- cmd/web/routes.go | 2 +- ui/views/guestbooks.templ | 350 +++++++++++++++++++--------------- ui/views/guestbooks_templ.go | 196 +++++++++++++------ ui/views/websites.templ | 2 +- ui/views/websites_templ.go | 8 +- 7 files changed, 364 insertions(+), 213 deletions(-) diff --git a/cmd/web/handlers_guestbook.go b/cmd/web/handlers_guestbook.go index b7adbeb..30f97cd 100644 --- a/cmd/web/handlers_guestbook.go +++ b/cmd/web/handlers_guestbook.go @@ -32,6 +32,20 @@ func (app *application) getGuestbook(w http.ResponseWriter, r *http.Request) { views.GuestbookView("Guestbook", data, website, website.Guestbook, comments, forms.CommentCreateForm{}).Render(r.Context(), w) } +func (app *application) getGuestbookSettings(w http.ResponseWriter, r *http.Request) { + slug := r.PathValue("id") + website, err := app.websites.Get(slugToShortId(slug)) + if err != nil { + if errors.Is(err, models.ErrNoRecord) { + http.NotFound(w, r) + } else { + app.serverError(w, r, err) + } + } + data := app.newCommonData(r) + views.GuestbookSettingsView(data, website).Render(r.Context(), w) +} + func (app *application) getGuestbookComments(w http.ResponseWriter, r *http.Request) { slug := r.PathValue("id") website, err := app.websites.Get(slugToShortId(slug)) diff --git a/cmd/web/handlers_website.go b/cmd/web/handlers_website.go index 8cb45b8..8c2d1d5 100644 --- a/cmd/web/handlers_website.go +++ b/cmd/web/handlers_website.go @@ -46,7 +46,10 @@ func (app *application) postWebsiteCreate(w http.ResponseWriter, r *http.Request } // TODO: how to handle website creation success but guestbook creation failure? guestbookShortID := app.createShortId() - _, err = app.guestbooks.Insert(guestbookShortID, userId, websiteId) + guestbookSettings := models.GuestbookSettings{ + IsCommentingEnabled: true, + } + _, err = app.guestbooks.Insert(guestbookShortID, userId, websiteId, guestbookSettings) if err != nil { app.serverError(w, r, err) return diff --git a/cmd/web/routes.go b/cmd/web/routes.go index 9690c7c..2a26790 100644 --- a/cmd/web/routes.go +++ b/cmd/web/routes.go @@ -41,7 +41,7 @@ func (app *application) routes() http.Handler { mux.Handle("GET /websites/{id}/dashboard/guestbook/comments/queue", protected.ThenFunc(app.getCommentQueue)) mux.Handle("DELETE /websites/{id}/dashboard/guestbook/comments/{commentId}", protected.ThenFunc(app.deleteGuestbookComment)) mux.Handle("PUT /websites/{id}/dashboard/guestbook/comments/{commentId}", protected.ThenFunc(app.putHideGuestbookComment)) - mux.Handle("GET /websites/{id}/dashboard/guestbook/blocklist", protected.ThenFunc(app.getComingSoon)) + mux.Handle("GET /websites/{id}/dashboard/guestbook/settings", protected.ThenFunc(app.getGuestbookSettings)) mux.Handle("GET /websites/{id}/dashboard/guestbook/comments/trash", protected.ThenFunc(app.getCommentTrash)) mux.Handle("GET /websites/{id}/dashboard/guestbook/themes", protected.ThenFunc(app.getComingSoon)) mux.Handle("GET /websites/{id}/dashboard/guestbook/customize", protected.ThenFunc(app.getComingSoon)) diff --git a/ui/views/guestbooks.templ b/ui/views/guestbooks.templ index 92545f5..99f9ea6 100644 --- a/ui/views/guestbooks.templ +++ b/ui/views/guestbooks.templ @@ -5,166 +5,218 @@ import "git.32bit.cafe/32bitcafe/guestbook/internal/models" import "git.32bit.cafe/32bitcafe/guestbook/internal/forms" templ GuestbookDashboardCommentsView(title string, data CommonData, website models.Website, guestbook models.Guestbook, comments []models.GuestbookComment) { - @base(title, data) { -
- @wSidebar(website) -
-

Comments on { website.SiteUrl }

-
- if len(comments) == 0 { -

No comments yet!

- } - for _, c := range comments { - @GuestbookDashboardCommentView(data, website, c) - } -
-
- } + @base(title, data) { +
+ @wSidebar(website) +
+

Comments on { website.SiteUrl }

+
+ if len(comments) == 0 { +

No comments yet!

+ } + for _, c := range comments { + @GuestbookDashboardCommentView(data, website, c) + } +
+
+ } } templ GuestbookDashboardCommentView(data CommonData, w models.Website, c models.GuestbookComment) { - {{ commentUrl := fmt.Sprintf("%s/dashboard/guestbook/comments/%s", wUrl(w), shortIdToSlug(c.ShortId)) }} - {{ hxHeaders := fmt.Sprintf("{\"X-CSRF-Token\": \"%s\"}", data.CSRFToken) }} -
-
- if c.Deleted.IsZero() { - - - } -
-
- { c.AuthorName } - if len(c.AuthorEmail) > 0 { - {{ email := "mailto:" + c.AuthorEmail}} - | { c.AuthorEmail } - } - if len(c.AuthorSite) > 0 { - | { c.AuthorSite } - } -

- { c.Created.In(data.CurrentUser.Settings.LocalTimezone).Format("01-02-2006 03:04PM") } -

-
-

- { c.CommentText } -

-
-
+ {{ commentUrl := fmt.Sprintf("%s/dashboard/guestbook/comments/%s", wUrl(w), shortIdToSlug(c.ShortId)) }} + {{ hxHeaders := fmt.Sprintf("{\"X-CSRF-Token\": \"%s\"}", data.CSRFToken) }} +
+
+ if c.Deleted.IsZero() { + + + } +
+
+ { c.AuthorName } + if len(c.AuthorEmail) > 0 { + {{ email := "mailto:" + c.AuthorEmail }} + | { c.AuthorEmail } + } + if len(c.AuthorSite) > 0 { + | { c.AuthorSite } + } +

+ { c.Created.In(data.CurrentUser.Settings.LocalTimezone).Format("01-02-2006 03:04PM") } +

+
+

+ { c.CommentText } +

+
+
} templ commentForm(form forms.CommentCreateForm) { -
- - {{ error, exists := form.FieldErrors["authorName"] }} - if exists { - - } - -
-
- - {{ error, exists = form.FieldErrors["authorEmail"] }} - if exists { - - } - -
-
- - {{ error, exists = form.FieldErrors["authorSite"] }} - if exists { - - } - -
-
- - {{ error, exists = form.FieldErrors["content"] }} - if exists { - - } - -
-
- -
+
+ + {{ error, exists := form.FieldErrors["authorName"] }} + if exists { + + } + +
+
+ + {{ error, exists = form.FieldErrors["authorEmail"] }} + if exists { + + } + +
+
+ + {{ error, exists = form.FieldErrors["authorSite"] }} + if exists { + + } + +
+
+ + {{ error, exists = form.FieldErrors["content"] }} + if exists { + + } + +
+
+ +
} templ GuestbookView(title string, data CommonData, website models.Website, guestbook models.Guestbook, comments []models.GuestbookComment, form forms.CommentCreateForm) { - {{ postUrl := fmt.Sprintf("/websites/%s/guestbook/comments/create", shortIdToSlug(website.ShortId)) }} - if data.IsHtmx { - @commentForm(form) - } else { - - - { title } - - - -
-
-

Guestbook for { website.Name }

-
- - @commentForm(form) -
-
-
- if len(comments) == 0 { -

No comments yet!

- } - for _, c := range comments { -
-

{ c.AuthorName }

- { c.Created.Format("01-02-2006 03:04PM") } -

- { c.CommentText } -

-
- } -
-
- - - } - } + {{ postUrl := fmt.Sprintf("/websites/%s/guestbook/comments/create", shortIdToSlug(website.ShortId)) }} + if data.IsHtmx { + @commentForm(form) + } else { + + + { title } + + + +
+
+

Guestbook for { website.Name }

+
+ + @commentForm(form) +
+
+
+ if len(comments) == 0 { +

No comments yet!

+ } + for _, c := range comments { +
+

{ c.AuthorName }

+ { c.Created.Format("01-02-2006 03:04PM") } +

+ { c.CommentText } +

+
+ } +
+
+ + + } +} - templ CreateGuestbookComment(title string, data CommonData, website models.Website, guestbook models.Guestbook, form forms.CommentCreateForm) { - {{ postUrl := fmt.Sprintf("/websites/%s/guestbook/comments/create", shortIdToSlug(website.ShortId)) }} - if data.IsHtmx { -
- @commentForm(form) -
- } else { - @base(title, data) { -
- @commentForm(form) -
- } - } +templ CreateGuestbookComment(title string, data CommonData, website models.Website, guestbook models.Guestbook, form forms.CommentCreateForm) { + {{ postUrl := fmt.Sprintf("/websites/%s/guestbook/comments/create", shortIdToSlug(website.ShortId)) }} + if data.IsHtmx { +
+ @commentForm(form) +
+ } else { + @base(title, data) { +
+ @commentForm(form) +
+ } + } +} + +templ GuestbookSettingsView(data CommonData, website models.Website) { + {{ putUrl := fmt.Sprintf("/websites/%s/dashboard/guestbook/settings", shortIdToSlug(website.ShortId)) }} + @base("Guestbook Settings", data) { +
+ @wSidebar(website) +
+

Guestbook Settings

+
+ +
+ + + +
+
+ + + +
+
+ + + +
+ +
+
+
+ } } templ AllGuestbooksView(data CommonData, websites []models.Website) { - @base("All Guestbooks", data) { -
-

All Guestbooks

-

- This page exists only for testing the service. -

-
    - for _, w := range websites { -
  • - {{ gbUrl := fmt.Sprintf("/websites/%s/guestbook", shortIdToSlug(w.ShortId))}} - { w.Name } -
  • - } -
-
- } + @base("All Guestbooks", data) { +
+

All Guestbooks

+

+ This page exists only for testing the service. +

+
    + for _, w := range websites { +
  • + {{ gbUrl := fmt.Sprintf("/websites/%s/guestbook", shortIdToSlug(w.ShortId)) }} + { w.Name } +
  • + } +
+
+ } } diff --git a/ui/views/guestbooks_templ.go b/ui/views/guestbooks_templ.go index c5242f4..65416be 100644 --- a/ui/views/guestbooks_templ.go +++ b/ui/views/guestbooks_templ.go @@ -60,7 +60,7 @@ func GuestbookDashboardCommentsView(title string, data CommonData, website model var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(website.SiteUrl) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 12, Col: 49} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 12, Col: 37} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -131,7 +131,7 @@ func GuestbookDashboardCommentView(data CommonData, w models.Website, c models.G var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(commentUrl) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 31, Col: 61} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 31, Col: 49} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -144,7 +144,7 @@ func GuestbookDashboardCommentView(data CommonData, w models.Website, c models.G var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(hxHeaders) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 31, Col: 118} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 31, Col: 106} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -157,7 +157,7 @@ func GuestbookDashboardCommentView(data CommonData, w models.Website, c models.G var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(commentUrl) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 32, Col: 59} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 32, Col: 47} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -170,7 +170,7 @@ func GuestbookDashboardCommentView(data CommonData, w models.Website, c models.G var templ_7745c5c3_Var8 string templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(hxHeaders) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 32, Col: 116} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 32, Col: 104} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { @@ -203,7 +203,7 @@ func GuestbookDashboardCommentView(data CommonData, w models.Website, c models.G var templ_7745c5c3_Var9 string templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(c.AuthorName) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 42, Col: 34} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 42, Col: 25} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { @@ -231,7 +231,7 @@ func GuestbookDashboardCommentView(data CommonData, w models.Website, c models.G var templ_7745c5c3_Var11 string templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(c.AuthorEmail) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 45, Col: 78} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 45, Col: 66} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) if templ_7745c5c3_Err != nil { @@ -259,7 +259,7 @@ func GuestbookDashboardCommentView(data CommonData, w models.Website, c models.G var templ_7745c5c3_Var13 string templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(c.AuthorSite) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 48, Col: 96} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 48, Col: 85} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { @@ -277,7 +277,7 @@ func GuestbookDashboardCommentView(data CommonData, w models.Website, c models.G var templ_7745c5c3_Var14 string templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(c.Created.In(data.CurrentUser.Settings.LocalTimezone).Format("01-02-2006 03:04PM")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 51, Col: 100} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 51, Col: 88} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { @@ -290,7 +290,7 @@ func GuestbookDashboardCommentView(data CommonData, w models.Website, c models.G var templ_7745c5c3_Var15 string templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(c.CommentText) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 55, Col: 27} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 55, Col: 18} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) if templ_7745c5c3_Err != nil { @@ -338,7 +338,7 @@ func commentForm(form forms.CommentCreateForm) templ.Component { var templ_7745c5c3_Var17 string templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(error) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 66, Col: 36} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 66, Col: 31} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) if templ_7745c5c3_Err != nil { @@ -362,7 +362,7 @@ func commentForm(form forms.CommentCreateForm) templ.Component { var templ_7745c5c3_Var18 string templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(error) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 74, Col: 36} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 74, Col: 31} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) if templ_7745c5c3_Err != nil { @@ -386,7 +386,7 @@ func commentForm(form forms.CommentCreateForm) templ.Component { var templ_7745c5c3_Var19 string templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(error) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 82, Col: 36} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 82, Col: 31} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) if templ_7745c5c3_Err != nil { @@ -410,7 +410,7 @@ func commentForm(form forms.CommentCreateForm) templ.Component { var templ_7745c5c3_Var20 string templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(error) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 90, Col: 36} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 90, Col: 31} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) if templ_7745c5c3_Err != nil { @@ -464,7 +464,7 @@ func GuestbookView(title string, data CommonData, website models.Website, guestb var templ_7745c5c3_Var22 string templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 106, Col: 26} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 106, Col: 18} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) if templ_7745c5c3_Err != nil { @@ -477,7 +477,7 @@ func GuestbookView(title string, data CommonData, website models.Website, guestb var templ_7745c5c3_Var23 string templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(website.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 112, Col: 56} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 112, Col: 38} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) if templ_7745c5c3_Err != nil { @@ -499,7 +499,7 @@ func GuestbookView(title string, data CommonData, website models.Website, guestb var templ_7745c5c3_Var25 string templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(data.CSRFToken) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 114, Col: 88} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 114, Col: 68} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) if templ_7745c5c3_Err != nil { @@ -531,7 +531,7 @@ func GuestbookView(title string, data CommonData, website models.Website, guestb var templ_7745c5c3_Var26 string templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(c.AuthorName) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 124, Col: 50} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 124, Col: 26} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) if templ_7745c5c3_Err != nil { @@ -544,7 +544,7 @@ func GuestbookView(title string, data CommonData, website models.Website, guestb var templ_7745c5c3_Var27 string templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(c.Created.Format("01-02-2006 03:04PM")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 125, Col: 72} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 125, Col: 48} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) if templ_7745c5c3_Err != nil { @@ -557,7 +557,7 @@ func GuestbookView(title string, data CommonData, website models.Website, guestb var templ_7745c5c3_Var28 string templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(c.CommentText) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 127, Col: 51} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 127, Col: 24} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) if templ_7745c5c3_Err != nil { @@ -607,7 +607,7 @@ func CreateGuestbookComment(title string, data CommonData, website models.Websit var templ_7745c5c3_Var30 string templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(postUrl) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 141, Col: 35} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 141, Col: 25} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) if templ_7745c5c3_Err != nil { @@ -670,7 +670,7 @@ func CreateGuestbookComment(title string, data CommonData, website models.Websit }) } -func AllGuestbooksView(data CommonData, websites []models.Website) templ.Component { +func GuestbookSettingsView(data CommonData, website models.Website) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -691,6 +691,7 @@ func AllGuestbooksView(data CommonData, websites []models.Website) templ.Compone templ_7745c5c3_Var33 = templ.NopComponent } ctx = templ.ClearChildren(ctx) + putUrl := fmt.Sprintf("/websites/%s/dashboard/guestbook/settings", shortIdToSlug(website.ShortId)) templ_7745c5c3_Var34 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) @@ -703,50 +704,131 @@ func AllGuestbooksView(data CommonData, websites []models.Website) templ.Compone }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "

All Guestbooks

This page exists only for testing the service.

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "

Guestbook Settings

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } return nil }) - templ_7745c5c3_Err = base("All Guestbooks", data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var34), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = base("Guestbook Settings", data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var34), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func AllGuestbooksView(data CommonData, websites []models.Website) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var37 := templ.GetChildren(ctx) + if templ_7745c5c3_Var37 == nil { + templ_7745c5c3_Var37 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var38 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "

All Guestbooks

This page exists only for testing the service.

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = base("All Guestbooks", data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var38), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/ui/views/websites.templ b/ui/views/websites.templ index cead964..8ef61a6 100644 --- a/ui/views/websites.templ +++ b/ui/views/websites.templ @@ -25,8 +25,8 @@ templ wSidebar(website models.Website) {