From 00f7fb13c175ea6a0395056077d77c71f2fb47d2 Mon Sep 17 00:00:00 2001 From: yequari Date: Sat, 23 Aug 2025 14:07:24 -0700 Subject: [PATCH 1/8] minor UI updates --- ui/static/css/style.css | 1 + ui/views/users.templ | 87 +++++++++++++++++++++-------------------- ui/views/users_templ.go | 46 +++++++++++----------- 3 files changed, 69 insertions(+), 65 deletions(-) diff --git a/ui/static/css/style.css b/ui/static/css/style.css index 78fdb6d..af544a5 100644 --- a/ui/static/css/style.css +++ b/ui/static/css/style.css @@ -319,6 +319,7 @@ label { input[type="text"], input[type="email"], input[type="url"], +input[type="password"], textarea, select { width: 100%; diff --git a/ui/views/users.templ b/ui/views/users.templ index dda28ba..4e2ee70 100644 --- a/ui/views/users.templ +++ b/ui/views/users.templ @@ -9,25 +9,27 @@ 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 { - +
+ + for _, error := range form.NonFieldErrors { +
{ error }
} - -
-
- - {{ error, exists = form.FieldErrors["password"] }} - if exists { - - } - +
+ + {{ error, exists := form.FieldErrors["email"] }} + if exists { + + } + +
+
+ + {{ error, exists = form.FieldErrors["password"] }} + if exists { + + } + +
@@ -44,29 +46,31 @@ templ UserRegistration(title string, data CommonData, form forms.UserRegistratio

User Registration

-
- {{ error, exists := form.FieldErrors["name"] }} - - if exists { - - } - -
-
- {{ error, exists = form.FieldErrors["email"] }} - - if exists { - - } - -
-
- {{ error, exists = form.FieldErrors["password"] }} - - if exists { - - } - +
+
+ {{ error, exists := form.FieldErrors["name"] }} + + if exists { + + } + +
+
+ {{ error, exists = form.FieldErrors["email"] }} + + if exists { + + } + +
+
+ {{ error, exists = form.FieldErrors["password"] }} + + if exists { + + } + +
@@ -90,7 +94,6 @@ templ UserSettingsView(data CommonData, timezones []string) {
- User Settings
Login
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -207,13 +207,13 @@ 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: 46, Col: 64} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 48, Col: 64} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "\">
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "\">
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -230,7 +230,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: 51, Col: 33} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 54, Col: 34} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) if templ_7745c5c3_Err != nil { @@ -248,7 +248,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: 53, Col: 70} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 56, Col: 71} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { @@ -271,7 +271,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: 59, Col: 33} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 62, Col: 34} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { @@ -289,7 +289,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: 61, Col: 65} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 64, Col: 66} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { @@ -312,7 +312,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: 67, Col: 33} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 70, Col: 34} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) if templ_7745c5c3_Err != nil { @@ -323,7 +323,7 @@ func UserRegistration(title string, data CommonData, form forms.UserRegistration return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -377,7 +377,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: 80, Col: 21} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 84, Col: 21} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) if templ_7745c5c3_Err != nil { @@ -390,7 +390,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: 81, Col: 17} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 85, Col: 17} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) if templ_7745c5c3_Err != nil { @@ -451,13 +451,13 @@ func UserSettingsView(data CommonData, timezones []string) templ.Component { var templ_7745c5c3_Var22 string templ_7745c5c3_Var22, 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: 91, Col: 66} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 95, Col: 66} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "\">
User Settings
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -470,7 +470,7 @@ func UserSettingsView(data CommonData, timezones []string) templ.Component { var templ_7745c5c3_Var23 string templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(tz) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 99, Col: 28} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 102, Col: 28} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) if templ_7745c5c3_Err != nil { @@ -483,7 +483,7 @@ func UserSettingsView(data CommonData, timezones []string) templ.Component { var templ_7745c5c3_Var24 string templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(tz) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 99, Col: 51} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 102, Col: 51} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) if templ_7745c5c3_Err != nil { @@ -501,7 +501,7 @@ func UserSettingsView(data CommonData, timezones []string) templ.Component { var templ_7745c5c3_Var25 string templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(tz) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 101, Col: 28} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 104, Col: 28} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) if templ_7745c5c3_Err != nil { @@ -514,7 +514,7 @@ func UserSettingsView(data CommonData, timezones []string) templ.Component { var templ_7745c5c3_Var26 string templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(tz) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 101, Col: 35} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 104, Col: 35} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) if templ_7745c5c3_Err != nil { -- 2.30.2 From 8d705c957beb462796c2e04ef7f51e261f59ee22 Mon Sep 17 00:00:00 2001 From: yequari Date: Sat, 23 Aug 2025 14:09:34 -0700 Subject: [PATCH 2/8] Setup admin groups, admin panel view, installation process --- cmd/web/handlers_admin.go | 61 +++ cmd/web/installer.go | 69 ++++ cmd/web/main.go | 61 ++- cmd/web/middleware.go | 16 + cmd/web/routes.go | 7 +- internal/forms/forms.go | 16 +- internal/models/install.go | 62 +++ internal/models/mocks/user.go | 5 +- internal/models/user.go | 128 ++++++- migrations/000008_add_user_groups.down.sql | 2 + migrations/000008_add_user_groups.up.sql | 19 + migrations/000009_user_ban_date.down.sql | 2 + migrations/000009_user_ban_date.up.sql | 2 + migrations/000010_installation.down.sql | 1 + migrations/000010_installation.up.sql | 5 + ui/views/admin.templ | 117 ++++++ ui/views/admin_templ.go | 416 +++++++++++++++++++++ ui/views/common.templ | 24 +- ui/views/common_templ.go | 69 ++-- ui/views/install.templ | 75 ++++ ui/views/install_templ.go | 335 +++++++++++++++++ 21 files changed, 1443 insertions(+), 49 deletions(-) create mode 100644 cmd/web/handlers_admin.go create mode 100644 cmd/web/installer.go create mode 100644 internal/models/install.go create mode 100644 migrations/000008_add_user_groups.down.sql create mode 100644 migrations/000008_add_user_groups.up.sql create mode 100644 migrations/000009_user_ban_date.down.sql create mode 100644 migrations/000009_user_ban_date.up.sql create mode 100644 migrations/000010_installation.down.sql create mode 100644 migrations/000010_installation.up.sql create mode 100644 ui/views/admin.templ create mode 100644 ui/views/admin_templ.go create mode 100644 ui/views/install.templ create mode 100644 ui/views/install_templ.go diff --git a/cmd/web/handlers_admin.go b/cmd/web/handlers_admin.go new file mode 100644 index 0000000..cd21ee3 --- /dev/null +++ b/cmd/web/handlers_admin.go @@ -0,0 +1,61 @@ +package main + +import ( + "errors" + "fmt" + "net/http" + + "git.32bit.cafe/32bitcafe/guestbook/internal/models" + "git.32bit.cafe/32bitcafe/guestbook/ui/views" +) + +func (app *application) getAdminPanelLanding(w http.ResponseWriter, r *http.Request) { + data := app.newCommonData(r) + views.AdminPanelLandingView("Admin Panel", data).Render(r.Context(), w) +} + +func (app *application) getAdminPanelAllUsers(w http.ResponseWriter, r *http.Request) { + users, err := app.users.GetAll() + if err != nil { + app.serverError(w, r, err) + return + } + data := app.newCommonData(r) + views.AdminPanelUsersView("All Users - Admin", data, users).Render(r.Context(), w) +} + +func (app *application) getAdminPanelUser(w http.ResponseWriter, r *http.Request) { + slug := r.PathValue("id") + u, err := app.users.Get(slugToShortId(slug)) + if err != nil { + if errors.Is(err, models.ErrNoRecord) { + http.NotFound(w, r) + } else { + app.serverError(w, r, err) + } + return + } + data := app.newCommonData(r) + views.AdminPanelUserMgmtView(fmt.Sprintf("User Management - %s", u.Username), data, u).Render(r.Context(), w) +} + +func (app *application) putAdminPanelBanUser(w http.ResponseWriter, r *http.Request) { + slug := r.PathValue("id") + u, err := app.users.Get(slugToShortId(slug)) + if err != nil { + if errors.Is(err, models.ErrNoRecord) { + http.NotFound(w, r) + } else { + app.serverError(w, r, err) + } + return + } + err = app.users.BanUser(u.ID) + if err != nil { + app.serverError(w, r, err) + return + } +} + +func (app *application) getAdminPanelWebsites(w http.ResponseWriter, r *http.Request) { +} diff --git a/cmd/web/installer.go b/cmd/web/installer.go new file mode 100644 index 0000000..42cdd5d --- /dev/null +++ b/cmd/web/installer.go @@ -0,0 +1,69 @@ +package main + +import ( + "net/http" + + "git.32bit.cafe/32bitcafe/guestbook/internal/forms" + "git.32bit.cafe/32bitcafe/guestbook/internal/models" + "git.32bit.cafe/32bitcafe/guestbook/internal/validator" + "git.32bit.cafe/32bitcafe/guestbook/ui" + "git.32bit.cafe/32bitcafe/guestbook/ui/views" + "github.com/justinas/alice" +) + +func (i *appInstaller) installRoutes() http.Handler { + mux := http.NewServeMux() + if i.app.config.environment == "PROD" { + mux.Handle("GET /static/", http.FileServerFS(ui.Files)) + } else { + fileServer := http.FileServer(http.Dir("./ui/static/")) + mux.Handle("GET /static/", http.StripPrefix("/static", fileServer)) + } + + mux.HandleFunc("GET /ping", ping) + standard := alice.New(i.app.recoverPanic, i.app.logRequest, commonHeaders) + mux.Handle("/{$}", standard.ThenFunc(i.getInstallHomepage)) + mux.Handle("GET /install", standard.ThenFunc(i.getInstallForm)) + mux.Handle("POST /install", standard.ThenFunc(i.postInstallForm)) + + return standard.Then(mux) +} + +func (i *appInstaller) getInstallHomepage(w http.ResponseWriter, r *http.Request) { + views.InitialInstallView("Installation").Render(r.Context(), w) +} + +func (i *appInstaller) getInstallForm(w http.ResponseWriter, r *http.Request) { + var form forms.InstallForm + views.InstallFormView("Installation - Settings", form).Render(r.Context(), w) +} + +func (i *appInstaller) postInstallForm(w http.ResponseWriter, r *http.Request) { + var form forms.InstallForm + err := i.app.decodePostForm(r, &form) + if err != nil { + i.app.clientError(w, http.StatusBadRequest) + return + } + form.CheckField(validator.NotBlank(form.Name), "name", "This field cannot be blank") + form.CheckField(validator.NotBlank(form.Email), "email", "This field cannot be blank") + form.CheckField(validator.Matches(form.Email, validator.EmailRX), "email", "This field must be a valid email address") + form.CheckField(validator.NotBlank(form.Password), "password", "This field cannot be blank") + form.CheckField(validator.MinChars(form.Password, 8), "password", "This field must be at least 8 characters long") + if !form.Valid() { + w.WriteHeader(http.StatusUnprocessableEntity) + views.InstallFormView("User Registration", form).Render(r.Context(), w) + return + } + sId := i.app.createShortId() + err = i.app.users.Insert(sId, form.Name, form.Email, form.Password, DefaultUserSettings()) + if err != nil { + i.app.serverError(w, r, err) + } + err = i.app.users.AddUserToGroup(1, models.AdminGroup) + if err != nil { + i.app.serverError(w, r, err) + } + views.InstallSuccessView().Render(r.Context(), w) + i.srv.Close() +} diff --git a/cmd/web/main.go b/cmd/web/main.go index 26ff243..b9dba2f 100644 --- a/cmd/web/main.go +++ b/cmd/web/main.go @@ -56,6 +56,12 @@ type application struct { timezones []string } +type appInstaller struct { + app *application + srv *http.Server + installModel models.InstallModelInterface +} + func main() { addr := flag.String("addr", ":3000", "HTTP network address") dsn := flag.String("dsn", "guestbook.db", "data source name") @@ -102,6 +108,29 @@ func main() { timezones: getAvailableTimezones(), } + tlsConfig := &tls.Config{ + CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256}, + } + + installer := &appInstaller{ + app: app, + installModel: &models.InstallModel{DB: db}, + } + installer.srv = &http.Server{ + Addr: *addr, + Handler: installer.installRoutes(), + ErrorLog: slog.NewLogLogger(logger.Handler(), slog.LevelError), + TLSConfig: tlsConfig, + IdleTimeout: time.Minute, + ReadTimeout: 5 * time.Second, + WriteTimeout: 10 * time.Second, + } + err = runInstaller(installer) + if err != nil { + logger.Error(err.Error()) + os.Exit(1) + } + err = app.users.InitializeSettingsMap() if err != nil { logger.Error(err.Error()) @@ -113,10 +142,6 @@ func main() { os.Exit(1) } - tlsConfig := &tls.Config{ - CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256}, - } - srv := &http.Server{ Addr: *addr, Handler: app.routes(), @@ -273,3 +298,31 @@ func walkTzDir(path string, zones []string) []string { return zones } + +func runInstaller(i *appInstaller) error { + i.app.logger.Info("Performing migrations") + err := i.installModel.SetupDatabase() + if err != nil { + return err + } + installed, _ := i.installModel.GetInstalledFlag() + if installed { + return nil + } + i.app.logger.Info("App not installed, running installer...") + i.app.logger.Info("Starting installation server", slog.Any("addr", i.srv.Addr)) + if i.app.debug { + err = i.srv.ListenAndServeTLS("./tls/cert.pem", "./tls/key.pem") + } else { + err = i.srv.ListenAndServe() + } + if err != nil && !errors.Is(err, http.ErrServerClosed) { + return err + } + i.app.logger.Info("Installation complete") + err = i.installModel.SetInstalledFlag() + if err != nil { + return err + } + return nil +} diff --git a/cmd/web/middleware.go b/cmd/web/middleware.go index a2d916e..781ad0b 100644 --- a/cmd/web/middleware.go +++ b/cmd/web/middleware.go @@ -4,7 +4,9 @@ import ( "context" "fmt" "net/http" + "slices" + "git.32bit.cafe/32bitcafe/guestbook/internal/models" "github.com/justinas/nosurf" ) @@ -56,6 +58,17 @@ func (app *application) requireAuthentication(next http.Handler) http.Handler { }) } +func (app *application) requireAdmin(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + user := app.getCurrentUser(r) + if !slices.Contains(user.Groups, models.AdminGroup) { + app.clientError(w, http.StatusForbidden) + return + } + next.ServeHTTP(w, r) + }) +} + func noSurf(next http.Handler) http.Handler { csrfHandler := nosurf.New(next) csrfHandler.SetBaseCookie(http.Cookie{ @@ -84,6 +97,9 @@ func (app *application) authenticate(next http.Handler) http.Handler { app.serverError(w, r, err) return } + if !user.Banned.IsZero() { + http.Redirect(w, r, "/banned", http.StatusSeeOther) + } if exists { ctx := context.WithValue(r.Context(), isAuthenticatedContextKey, true) ctx = context.WithValue(ctx, userNameContextKey, user) diff --git a/cmd/web/routes.go b/cmd/web/routes.go index e2f6509..1ba345b 100644 --- a/cmd/web/routes.go +++ b/cmd/web/routes.go @@ -39,13 +39,11 @@ func (app *application) routes() http.Handler { protected := dynamic.Append(app.requireAuthentication) - // 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.getUserSettings)) mux.Handle("PUT /users/settings", protected.ThenFunc(app.putUserSettings)) mux.Handle("GET /guestbooks", protected.ThenFunc(app.getAllGuestbooks)) - mux.Handle("GET /websites", protected.ThenFunc(app.getWebsiteList)) mux.Handle("GET /websites/create", protected.ThenFunc(app.getWebsiteCreate)) mux.Handle("POST /websites/create", protected.ThenFunc(app.postWebsiteCreate)) @@ -60,5 +58,10 @@ func (app *application) routes() http.Handler { mux.Handle("GET /websites/{id}/dashboard/guestbook/themes", protected.ThenFunc(app.getComingSoon)) mux.Handle("GET /websites/{id}/dashboard/guestbook/customize", protected.ThenFunc(app.getComingSoon)) + adminOnly := protected.Append(app.requireAdmin) + mux.Handle("GET /admin", adminOnly.ThenFunc(app.getAdminPanelLanding)) + mux.Handle("GET /admin/users", adminOnly.ThenFunc(app.getAdminPanelAllUsers)) + mux.Handle("GET /admin/users/{id}", adminOnly.ThenFunc(app.getAdminPanelUser)) + return standard.Then(mux) } diff --git a/internal/forms/forms.go b/internal/forms/forms.go index ebb7b25..bfb739c 100644 --- a/internal/forms/forms.go +++ b/internal/forms/forms.go @@ -25,9 +25,9 @@ type CommentCreateForm struct { } type WebsiteCreateForm struct { - Name string `schema:"sitename"` - SiteUrl string `schema:"siteurl"` - AuthorName string `schema:"authorname"` + Name string `schema:"ws_name""` + SiteUrl string `schema:"ws_url"` + AuthorName string `schema:"ws_author"` validator.Validator `schema:"-"` } @@ -50,3 +50,13 @@ type WebsiteSettingsForm struct { WidgetsEnabled string `schema:"gb_remote"` validator.Validator `schema:"-"` } + +type AdminUserMgmtForm struct { +} + +type InstallForm struct { + Name string `schema:"username"` + Email string `schema:"email"` + Password string `schema:"password"` + validator.Validator `schema:"-"` +} diff --git a/internal/models/install.go b/internal/models/install.go new file mode 100644 index 0000000..872d75d --- /dev/null +++ b/internal/models/install.go @@ -0,0 +1,62 @@ +package models + +import ( + "database/sql" + "errors" + "time" + + "github.com/golang-migrate/migrate/v4" + "github.com/golang-migrate/migrate/v4/database/sqlite3" + _ "github.com/golang-migrate/migrate/v4/source/file" + _ "github.com/mattn/go-sqlite3" +) + +type InstallModel struct { + DB *sql.DB +} + +type InstallModelInterface interface { + SetupDatabase() error + SetInstalledFlag() error + GetInstalledFlag() (bool, error) +} + +func (m *InstallModel) SetupDatabase() error { + driver, err := sqlite3.WithInstance(m.DB, &sqlite3.Config{}) + if err != nil { + return err + } + mi, err := migrate.NewWithDatabaseInstance("file://migrations", "sqlite", driver) + if err != nil { + return err + } + err = mi.Up() + if err != nil && !errors.Is(err, migrate.ErrNoChange) { + return err + } + return nil +} + +func (m *InstallModel) SetInstalledFlag() error { + stmt := `INSERT INTO installed (InstallDate) VALUES (?)` + _, err := m.DB.Exec(stmt, time.Now().UTC()) + if err != nil { + return err + } + return nil +} + +func (m *InstallModel) GetInstalledFlag() (bool, error) { + stmt := `SELECT InstallDate FROM installed` + row := m.DB.QueryRow(stmt) + var d time.Time + err := row.Scan(&d) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return false, nil + } else { + return false, err + } + } + return true, nil +} diff --git a/internal/models/mocks/user.go b/internal/models/mocks/user.go index 0ca4d30..25da0e3 100644 --- a/internal/models/mocks/user.go +++ b/internal/models/mocks/user.go @@ -13,7 +13,6 @@ var mockUser = models.User{ Username: "tester", Email: "test@example.com", Deleted: false, - IsBanned: false, Created: time.Now(), Settings: mockUserSettings, } @@ -120,3 +119,7 @@ func (m *UserModel) UpdateSubject(userId int64, subject string) error { } return errors.New("invalid") } + +func (m *UserModel) GetNumberOfUsers() int { + return 1 +} diff --git a/internal/models/user.go b/internal/models/user.go index 13eacc5..f7d735c 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -10,6 +10,13 @@ import ( "golang.org/x/crypto/bcrypt" ) +type UserGroupId int64 + +const ( + AdminGroup UserGroupId = 1 + UserGroup UserGroupId = 2 +) + type UserSettings struct { LocalTimezone *time.Location } @@ -24,10 +31,11 @@ type User struct { Username string Email string Deleted bool - IsBanned bool HashedPassword []byte Created time.Time + Banned time.Time Settings UserSettings + Groups []UserGroupId } type UserModel struct { @@ -57,6 +65,9 @@ type UserModelInterface interface { UpdateUserSettings(userId int64, settings UserSettings) error UpdateSetting(userId int64, setting Setting, value string) error UpdateSubject(userId int64, subject string) error + GetNumberOfUsers() int + AddUserToGroup(userId int64, groupId UserGroupId) error + BanUser(userId int64) error } func (m *UserModel) InitializeSettingsMap() error { @@ -96,8 +107,8 @@ func (m *UserModel) Insert(shortId uint64, username string, email string, passwo if err != nil { return err } - stmt := `INSERT INTO users (ShortId, Username, Email, IsBanned, HashedPassword, Created) - VALUES (?, ?, ?, FALSE, ?, ?)` + stmt := `INSERT INTO users (ShortId, Username, Email, HashedPassword, Created) + VALUES (?, ?, ?, ?, ?)` tx, err := m.DB.Begin() if err != nil { return err @@ -107,7 +118,8 @@ func (m *UserModel) Insert(shortId uint64, username string, email string, passwo if rollbackErr := tx.Rollback(); rollbackErr != nil { return err } - if sqliteError, ok := err.(sqlite3.Error); ok { + var sqliteError sqlite3.Error + if errors.As(err, &sqliteError) { if sqliteError.ExtendedCode == 2067 && strings.Contains(sqliteError.Error(), "Email") { return ErrDuplicateEmail } @@ -130,17 +142,20 @@ func (m *UserModel) Insert(shortId uint64, username string, email string, passwo } return err } + err = m.addUserToGroup(tx, id, UserGroup) + if err != nil { + return err + } err = tx.Commit() if err != nil { return err } - return nil } func (m *UserModel) InsertWithoutPassword(shortId uint64, username string, email string, subject string, settings UserSettings) (int64, error) { - stmt := `INSERT INTO users (ShortId, Username, Email, IsBanned, OIDCSubject, Created) - VALUES (?, ?, ?, FALSE, ?, ?)` + stmt := `INSERT INTO users (ShortId, Username, Email, OIDCSubject, Created) + VALUES (?, ?, ?, ?, ?)` tx, err := m.DB.Begin() if err != nil { return -1, err @@ -150,7 +165,8 @@ func (m *UserModel) InsertWithoutPassword(shortId uint64, username string, email if rollbackErr := tx.Rollback(); rollbackErr != nil { return -1, err } - if sqliteError, ok := err.(sqlite3.Error); ok { + var sqliteError sqlite3.Error + if errors.As(err, &sqliteError) { if sqliteError.ExtendedCode == 2067 && strings.Contains(sqliteError.Error(), "Email") { return -1, ErrDuplicateEmail } @@ -173,6 +189,7 @@ func (m *UserModel) InsertWithoutPassword(shortId uint64, username string, email } return -1, err } + err = m.addUserToGroup(tx, id, UserGroup) err = tx.Commit() if err != nil { return -1, err @@ -182,14 +199,15 @@ func (m *UserModel) InsertWithoutPassword(shortId uint64, username string, email } func (m *UserModel) Get(shortId uint64) (User, error) { - stmt := `SELECT Id, ShortId, Username, Email, Created FROM users WHERE ShortId = ? AND Deleted IS NULL` + stmt := `SELECT Id, ShortId, Username, Email, Created, Banned FROM users WHERE ShortId = ? AND Deleted IS NULL` tx, err := m.DB.Begin() if err != nil { return User{}, err } row := tx.QueryRow(stmt, shortId) var u User - err = row.Scan(&u.ID, &u.ShortId, &u.Username, &u.Email, &u.Created) + var b sql.NullTime + err = row.Scan(&u.ID, &u.ShortId, &u.Username, &u.Email, &u.Created, &b) if err != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil { return User{}, err @@ -199,6 +217,9 @@ func (m *UserModel) Get(shortId uint64) (User, error) { } return User{}, err } + if b.Valid { + u.Banned = b.Time + } settings, err := m.getSettings(tx, u.ID) if err != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil { @@ -207,6 +228,14 @@ func (m *UserModel) Get(shortId uint64) (User, error) { return User{}, err } u.Settings = settings + groups, err := m.getGroups(tx, u.ID) + if err != nil { + if rollbackErr := tx.Rollback(); rollbackErr != nil { + return User{}, err + } + return User{}, err + } + u.Groups = groups err = tx.Commit() if err != nil { return User{}, err @@ -215,14 +244,15 @@ func (m *UserModel) Get(shortId uint64) (User, error) { } func (m *UserModel) GetById(id int64) (User, error) { - stmt := `SELECT Id, ShortId, Username, Email, Created FROM users WHERE Id = ? AND Deleted IS NULL` + stmt := `SELECT Id, ShortId, Username, Email, Created, Banned FROM users WHERE Id = ? AND Deleted IS NULL` tx, err := m.DB.Begin() if err != nil { return User{}, err } row := tx.QueryRow(stmt, id) var u User - err = row.Scan(&u.ID, &u.ShortId, &u.Username, &u.Email, &u.Created) + var b sql.NullTime + err = row.Scan(&u.ID, &u.ShortId, &u.Username, &u.Email, &u.Created, &b) if err != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil { return User{}, err @@ -232,6 +262,9 @@ func (m *UserModel) GetById(id int64) (User, error) { } return User{}, err } + if b.Valid { + u.Banned = b.Time + } settings, err := m.getSettings(tx, u.ID) if err != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil { @@ -240,6 +273,14 @@ func (m *UserModel) GetById(id int64) (User, error) { return User{}, err } u.Settings = settings + groups, err := m.getGroups(tx, u.ID) + if err != nil { + if rollbackErr := tx.Rollback(); rollbackErr != nil { + return User{}, err + } + return User{}, err + } + u.Groups = groups err = tx.Commit() if err != nil { return User{}, err @@ -400,3 +441,66 @@ func (m *UserModel) UpdateSetting(userId int64, setting Setting, value string) e } return nil } + +func (m *UserModel) GetNumberOfUsers() int { + stmt := `SELECT COUNT(Id) FROM users WHERE Deleted IS NULL;` + row := m.DB.QueryRow(stmt) + var count int + err := row.Scan(&count) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + count = 0 + } else { + count = 1 + } + } + return count +} + +func (m *UserModel) AddUserToGroup(userId int64, groupId UserGroupId) error { + stmt := `INSERT INTO users_groups (UserId, GroupId) VALUES (?, ?)` + _, err := m.DB.Exec(stmt, userId, groupId) + if err != nil { + return err + } + return nil +} + +func (m *UserModel) BanUser(userId int64) error { + stmt := `UPDATE users SET Banned=? WHERE Id=?` + _, err := m.DB.Exec(stmt, time.Now().UTC().Format(time.RFC3339), userId) + if err != nil { + return err + } + return nil +} + +func (m *UserModel) addUserToGroup(tx *sql.Tx, userId int64, groupId UserGroupId) error { + stmt := `INSERT INTO users_groups (UserId, GroupId) VALUES (?, ?)` + _, err := tx.Exec(stmt, userId, groupId) + if err != nil { + if rollbackErr := tx.Rollback(); rollbackErr != nil { + return err + } + return err + } + return nil +} + +func (m *UserModel) getGroups(tx *sql.Tx, userId int64) ([]UserGroupId, error) { + stmt := `SELECT DISTINCT GroupId FROM users_groups WHERE UserId = ?` + rows, err := tx.Query(stmt, userId) + result := make([]UserGroupId, 0, 10) + if err != nil { + return result, err + } + for rows.Next() { + var g UserGroupId + err = rows.Scan(&g) + if err != nil { + return result, err + } + result = append(result, g) + } + return result, nil +} diff --git a/migrations/000008_add_user_groups.down.sql b/migrations/000008_add_user_groups.down.sql new file mode 100644 index 0000000..95884c5 --- /dev/null +++ b/migrations/000008_add_user_groups.down.sql @@ -0,0 +1,2 @@ +DROP TABLE IF EXISTS users_groups; +DROP TABLE IF EXISTS groups; diff --git a/migrations/000008_add_user_groups.up.sql b/migrations/000008_add_user_groups.up.sql new file mode 100644 index 0000000..99c1aab --- /dev/null +++ b/migrations/000008_add_user_groups.up.sql @@ -0,0 +1,19 @@ +CREATE TABLE IF NOT EXISTS groups ( + Id integer PRIMARY KEY NOT NULL, + Description varchar(256) +); + +INSERT INTO groups (Id, Description) + VALUES (1, 'admin'),(2, 'user'); + +CREATE TABLE IF NOT EXISTS users_groups ( + Id integer primary key autoincrement, + UserId integer NOT NULL, + GroupId integer NOT NULL, + FOREIGN KEY (UserId) REFERENCES users(Id) + ON DELETE RESTRICT + ON UPDATE RESTRICT, + FOREIGN KEY (GroupId) REFERENCES groups(Id) + ON DELETE RESTRICT + ON UPDATE RESTRICT +); diff --git a/migrations/000009_user_ban_date.down.sql b/migrations/000009_user_ban_date.down.sql new file mode 100644 index 0000000..9fa7787 --- /dev/null +++ b/migrations/000009_user_ban_date.down.sql @@ -0,0 +1,2 @@ +ALTER TABLE users DROP COLUMN Banned; +ALTER TABLE users ADD COLUMN IsBanned boolean NOT NULL DEFAULT false; \ No newline at end of file diff --git a/migrations/000009_user_ban_date.up.sql b/migrations/000009_user_ban_date.up.sql new file mode 100644 index 0000000..df8b6ad --- /dev/null +++ b/migrations/000009_user_ban_date.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE users DROP COLUMN IsBanned; +ALTER TABLE users ADD COLUMN Banned datetime NULL; \ No newline at end of file diff --git a/migrations/000010_installation.down.sql b/migrations/000010_installation.down.sql new file mode 100644 index 0000000..9760855 --- /dev/null +++ b/migrations/000010_installation.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS installed; \ No newline at end of file diff --git a/migrations/000010_installation.up.sql b/migrations/000010_installation.up.sql new file mode 100644 index 0000000..5cb45d9 --- /dev/null +++ b/migrations/000010_installation.up.sql @@ -0,0 +1,5 @@ +CREATE TABLE installed ( + Id integer primary key autoincrement, + InstallDate datetime NOT NULL, + InstallVersion varchar(100) NULL +); \ No newline at end of file diff --git a/ui/views/admin.templ b/ui/views/admin.templ new file mode 100644 index 0000000..10247cd --- /dev/null +++ b/ui/views/admin.templ @@ -0,0 +1,117 @@ +package views + +import ( + "fmt" + "git.32bit.cafe/32bitcafe/guestbook/internal/models" + "time" +) + +templ adminBase(title string, data CommonData) { + + + + { title } - webweav.ing + + + + + + + + + +
+ Back to webweav.ing +
+
+ { children... } +
+ @commonFooter() + + +} + +templ adminSidebar() { + +} + +templ AdminPanelLandingView(title string, data CommonData) { + @adminBase(title, data) { +
+ @adminSidebar() +
+

{ title }

+

Welcome to the admin panel

+
+
+ } +} + +templ AdminPanelUsersView(title string, data CommonData, users []models.User) { + @adminBase(title, data) { +
+ @adminSidebar() +
+
+ + + + + + for _, u := range users { + {{ url := fmt.Sprintf("/admin/users/%s", shortIdToSlug(u.ShortId)) }} + + + + } + +
UsernameJoinedEmail
{ u.Username }{ u.Created.Format(time.RFC3339) }{ u.Email }
+
+
+
+ } +} + +templ AdminPanelUserMgmtView(title string, data CommonData, user models.User) { + @adminBase(title, data) { +
+ @adminSidebar() +
+
+

User Info

+
+
Username
+

{ user.Username }

+
+
+
Email
+

{ user.Email }

+
+
+
Joined
+

{ user.Created.Format(time.RFC3339) }

+
+
+
+

Groups

+
    + for _, g := range user.Groups { +
  • { fmt.Sprintf("%d %s", g, getGroupName(g)) }
  • + } +
+
+
+
+ } +} diff --git a/ui/views/admin_templ.go b/ui/views/admin_templ.go new file mode 100644 index 0000000..6bd303b --- /dev/null +++ b/ui/views/admin_templ.go @@ -0,0 +1,416 @@ +// 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" + +import ( + "fmt" + "git.32bit.cafe/32bitcafe/guestbook/internal/models" + "time" +) + +func adminBase(title string, data CommonData) 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) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 string + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 13, Col: 17} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " - webweav.ing
Back to webweav.ing
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = commonFooter().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func adminSidebar() 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_Var4 := templ.GetChildren(ctx) + if templ_7745c5c3_Var4 == nil { + templ_7745c5c3_Var4 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func AdminPanelLandingView(title string, data CommonData) 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_Var5 := templ.GetChildren(ctx) + if templ_7745c5c3_Var5 == nil { + templ_7745c5c3_Var5 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var6 := 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, 7, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = adminSidebar().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 54, Col: 15} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "

Welcome to the admin panel

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = adminBase(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var6), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func AdminPanelUsersView(title string, data CommonData, users []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 { + 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_Var8 := templ.GetChildren(ctx) + if templ_7745c5c3_Var8 == nil { + templ_7745c5c3_Var8 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var9 := 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, 10, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = adminSidebar().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, u := range users { + url := fmt.Sprintf("/admin/users/%s", shortIdToSlug(u.ShortId)) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
UsernameJoinedEmail
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var11 string + templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(u.Username) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 74, Col: 51} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var12 string + templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(u.Created.Format(time.RFC3339)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 75, Col: 44} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var13 string + templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(u.Email) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 76, Col: 21} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = adminBase(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var9), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func AdminPanelUserMgmtView(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 { + 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_Var14 := templ.GetChildren(ctx) + if templ_7745c5c3_Var14 == nil { + templ_7745c5c3_Var14 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var15 := 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, 18, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = adminSidebar().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "

User Info

Username

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var16 string + templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(user.Username) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 95, Col: 24} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "

Email

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var17 string + templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(user.Email) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 99, Col: 21} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "

Joined

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var18 string + templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(user.Created.Format(time.RFC3339)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 103, Col: 44} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "

Groups

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, g := range user.Groups { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "
  • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var19 string + templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d %s", g, getGroupName(g))) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 110, Col: 53} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
  • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = adminBase(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var15), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/ui/views/common.templ b/ui/views/common.templ index 666b370..720c840 100644 --- a/ui/views/common.templ +++ b/ui/views/common.templ @@ -1,9 +1,12 @@ package views -import "git.32bit.cafe/32bitcafe/guestbook/internal/models" -import "strconv" -import "fmt" -import "strings" +import ( + "fmt" + "git.32bit.cafe/32bitcafe/guestbook/internal/models" + "slices" + "strconv" + "strings" +) type CommonData struct { CurrentYear int @@ -33,6 +36,16 @@ func externalUrl(url string) string { return url } +func getGroupName(id models.UserGroupId) string { + switch id { + case 1: + return "Admin" + case 2: + return "User" + } + return "Unknown" +} + templ commonHeader() {

webweav.ing

@@ -54,6 +67,9 @@ templ topNav(data CommonData) {
  • All Guestbooks
  • My Websites
  • Settings
  • + if slices.Contains(data.CurrentUser.Groups, models.AdminGroup) { +
  • Admin Panel
  • + }
  • Logout
  • } else { if data.LocalAuthEnabled { diff --git a/ui/views/common_templ.go b/ui/views/common_templ.go index 6391240..7546f26 100644 --- a/ui/views/common_templ.go +++ b/ui/views/common_templ.go @@ -8,10 +8,13 @@ package views import "github.com/a-h/templ" import templruntime "github.com/a-h/templ/runtime" -import "git.32bit.cafe/32bitcafe/guestbook/internal/models" -import "strconv" -import "fmt" -import "strings" +import ( + "fmt" + "git.32bit.cafe/32bitcafe/guestbook/internal/models" + "slices" + "strconv" + "strings" +) type CommonData struct { CurrentYear int @@ -41,6 +44,16 @@ func externalUrl(url string) string { return url } +func getGroupName(id models.UserGroupId) string { + switch id { + case 1: + return "Admin" + case 2: + return "User" + } + return "Unknown" +} + func commonHeader() 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 @@ -104,7 +117,7 @@ func topNav(data CommonData) templ.Component { var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.CurrentUser.Username) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 48, Col: 41} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 61, Col: 41} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -116,36 +129,46 @@ func topNav(data CommonData) templ.Component { return templ_7745c5c3_Err } if data.IsAuthenticated { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
  • All Guestbooks
  • My Websites
  • Settings
  • All Guestbooks
  • My Websites
  • Settings
  • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if slices.Contains(data.CurrentUser.Groups, models.AdminGroup) { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
  • Admin Panel
  • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
  • Logout
  • ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\">Logout") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { if data.LocalAuthEnabled { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
  • Create an Account
  • | ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
  • Create an Account
  • | ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
  • Login
  • ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
  • Login
  • ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -174,7 +197,7 @@ func commonFooter() templ.Component { templ_7745c5c3_Var5 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -203,33 +226,33 @@ func base(title string, data CommonData) templ.Component { templ_7745c5c3_Var6 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<!doctype html><html lang=\"en\"><head><title>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 82, Col: 17} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 98, Col: 17} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, " - webweav.ing") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -241,25 +264,25 @@ func base(title string, data CommonData) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if data.Flash != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var9 string templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(data.Flash) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 96, Col: 43} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 112, Col: 43} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -268,7 +291,7 @@ func base(title string, data CommonData) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -276,7 +299,7 @@ func base(title string, data CommonData) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/ui/views/install.templ b/ui/views/install.templ new file mode 100644 index 0000000..c71172f --- /dev/null +++ b/ui/views/install.templ @@ -0,0 +1,75 @@ +package views + +import "git.32bit.cafe/32bitcafe/guestbook/internal/forms" + +templ installBase(title string) { + + + + { title } - webweav.ing + + + + + + +
    +

    { title }

    + { children... } +
    + @commonFooter() + + +} + +templ InitialInstallView(title string) { + @installBase(title) { +

    Installing

    + Next + } +} + +templ InstallFormView(title string, form forms.InstallForm) { + @installBase(title) { +
    +
    + Admin User +
    +
    + {{ error, exists := form.FieldErrors[""] }} + + if exists { + + } + +
    +
    + {{ error, exists = form.FieldErrors["email"] }} + + if exists { + + } + +
    +
    + {{ error, exists = form.FieldErrors["password"] }} + + if exists { + + } + +
    +
    +
    +
    + +
    +
    + } +} + +templ InstallSuccessView() { + @installBase("Success") { +

    Installation was successful. Go home.

    + } +} diff --git a/ui/views/install_templ.go b/ui/views/install_templ.go new file mode 100644 index 0000000..0c958a2 --- /dev/null +++ b/ui/views/install_templ.go @@ -0,0 +1,335 @@ +// 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" + +import "git.32bit.cafe/32bitcafe/guestbook/internal/forms" + +func installBase(title 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) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 string + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/install.templ`, Line: 9, Col: 17} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " - webweav.ing

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/install.templ`, Line: 17, Col: 15} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = commonFooter().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func InitialInstallView(title 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_Var4 := templ.GetChildren(ctx) + if templ_7745c5c3_Var4 == nil { + templ_7745c5c3_Var4 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var5 := 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, 6, "

    Installing

    Next") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = installBase(title).Render(templ.WithChildren(ctx, templ_7745c5c3_Var5), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func InstallFormView(title string, form forms.InstallForm) 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_Var6 := templ.GetChildren(ctx) + if templ_7745c5c3_Var6 == nil { + templ_7745c5c3_Var6 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var7 := 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, 7, "
    Admin User
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + error, exists := form.FieldErrors[""] + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if exists { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + error, exists = form.FieldErrors["email"] + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if exists { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + error, exists = form.FieldErrors["password"] + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if exists { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = installBase(title).Render(templ.WithChildren(ctx, templ_7745c5c3_Var7), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func InstallSuccessView() 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_Var13 := templ.GetChildren(ctx) + if templ_7745c5c3_Var13 == nil { + templ_7745c5c3_Var13 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var14 := 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, 22, "

    Installation was successful. Go home.

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = installBase("Success").Render(templ.WithChildren(ctx, templ_7745c5c3_Var14), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate -- 2.30.2 From e02d212277f1307248a22b2bcc0ab6fcb34d8a5e Mon Sep 17 00:00:00 2001 From: yequari Date: Fri, 12 Dec 2025 18:18:36 -0700 Subject: [PATCH 3/8] Add some user controls --- internal/models/mocks/user.go | 25 ++++++++++++++++ internal/models/user.go | 34 ++++++++++++++++++++++ ui/views/admin.templ | 7 ++--- ui/views/admin_templ.go | 54 +++++++++++++++++++---------------- 4 files changed, 91 insertions(+), 29 deletions(-) diff --git a/internal/models/mocks/user.go b/internal/models/mocks/user.go index 25da0e3..c865e0f 100644 --- a/internal/models/mocks/user.go +++ b/internal/models/mocks/user.go @@ -25,6 +25,31 @@ type UserModel struct { Settings map[string]models.Setting } +func (m *UserModel) AddUserToGroup(userId int64, groupId models.UserGroupId) error { + //TODO implement me + panic("implement me") +} + +func (m *UserModel) BanUser(userId int64) error { + //TODO implement me + panic("implement me") +} + +func (m *UserModel) UpdateUser(u models.User) error { + //TODO implement me + panic("implement me") +} + +func (m *UserModel) UpdatePassword(userId int64, password string) error { + //TODO implement me + panic("implement me") +} + +func (m *UserModel) Delete(userId int64) error { + //TODO implement me + panic("implement me") +} + func (m *UserModel) InitializeSettingsMap() error { return nil } diff --git a/internal/models/user.go b/internal/models/user.go index f7d735c..6c38f9f 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -68,6 +68,9 @@ type UserModelInterface interface { GetNumberOfUsers() int AddUserToGroup(userId int64, groupId UserGroupId) error BanUser(userId int64) error + UpdateUser(u User) error + UpdatePassword(userId int64, password string) error + Delete(userId int64) error } func (m *UserModel) InitializeSettingsMap() error { @@ -475,6 +478,37 @@ func (m *UserModel) BanUser(userId int64) error { return nil } +func (m *UserModel) UpdateUser(u User) error { + stmt := `UPDATE users SET Email=?, Name=? WHERE Id=?` + _, err := m.DB.Exec(stmt, u.Email, u.Username, u.ID) + if err != nil { + return err + } + return nil +} + +func (m *UserModel) UpdatePassword(userId int64, password string) error { + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 12) + if err != nil { + return err + } + stmt := `UPDATE users SET HashedPassword=? WHERE Id=?` + _, err = m.DB.Exec(stmt, hashedPassword, userId) + if err != nil { + return err + } + return nil +} + +func (m *UserModel) Delete(userId int64) error { + stmt := `UPDATE users SET Deleted=? WHERE Id=?` + _, err := m.DB.Exec(stmt, time.Now().UTC().Format(time.RFC3339), userId) + if err != nil { + return err + } + return nil +} + func (m *UserModel) addUserToGroup(tx *sql.Tx, userId int64, groupId UserGroupId) error { stmt := `INSERT INTO users_groups (UserId, GroupId) VALUES (?, ?)` _, err := tx.Exec(stmt, userId, groupId) diff --git a/ui/views/admin.templ b/ui/views/admin.templ index 10247cd..c1e8038 100644 --- a/ui/views/admin.templ +++ b/ui/views/admin.templ @@ -39,7 +39,6 @@ templ adminSidebar() {
    @@ -68,14 +67,14 @@ templ AdminPanelUsersView(title string, data CommonData, users []models.User) { Username Joined Email - for _, u := range users { + {{ url := fmt.Sprintf("/admin/users/%s", shortIdToSlug(u.ShortId)) }} { u.Username } { u.Created.Format(time.RFC3339) } { u.Email } + } -
    @@ -107,7 +106,7 @@ templ AdminPanelUserMgmtView(title string, data CommonData, user models.User) {

    Groups

      for _, g := range user.Groups { -
    • { fmt.Sprintf("%d %s", g, getGroupName(g)) }
    • +
    • { fmt.Sprintf("%s", getGroupName(g)) }
    • }
    diff --git a/ui/views/admin_templ.go b/ui/views/admin_templ.go index 6bd303b..62ca8da 100644 --- a/ui/views/admin_templ.go +++ b/ui/views/admin_templ.go @@ -106,7 +106,7 @@ func adminSidebar() templ.Component { templ_7745c5c3_Var4 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -162,7 +162,7 @@ func AdminPanelLandingView(title string, data CommonData) templ.Component { var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 54, Col: 15} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 53, Col: 15} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -223,13 +223,17 @@ func AdminPanelUsersView(title string, data CommonData, users []models.User) tem if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
    UsernameJoinedEmail
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } for _, u := range users { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } url := fmt.Sprintf("/admin/users/%s", shortIdToSlug(u.ShortId)) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
    UsernameJoinedEmail
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var11 string templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(u.Username) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 74, Col: 51} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 73, Col: 51} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var12 string templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(u.Created.Format(time.RFC3339)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 75, Col: 44} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 74, Col: 44} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var13 string templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(u.Email) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 76, Col: 21} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 75, Col: 21} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -329,7 +333,7 @@ func AdminPanelUserMgmtView(title string, data CommonData, user models.User) tem }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -337,69 +341,69 @@ func AdminPanelUserMgmtView(title string, data CommonData, user models.User) tem if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "

    User Info

    Username

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

    User Info

    Username

    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var16 string templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(user.Username) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 95, Col: 24} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 94, Col: 24} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "

    Email

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

    Email

    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var17 string templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(user.Email) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 99, Col: 21} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 98, Col: 21} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "

    Joined

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

    Joined

    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var18 string templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(user.Created.Format(time.RFC3339)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 103, Col: 44} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 102, Col: 44} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "

    Groups

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

    Groups

      ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } for _, g := range user.Groups { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "
    • ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
    • ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var19 string - templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d %s", g, getGroupName(g))) + templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s", getGroupName(g))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 110, Col: 53} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 109, Col: 47} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
    • ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } -- 2.30.2 From e5e16b5a334c2e6c5678e0182b37fbdef6b0f6bd Mon Sep 17 00:00:00 2001 From: yequari Date: Tue, 16 Dec 2025 22:11:42 -0700 Subject: [PATCH 4/8] add basic user info editing in admin panel --- cmd/web/handlers_admin.go | 73 +++++ cmd/web/routes.go | 3 + internal/forms/forms.go | 9 +- internal/models/user.go | 13 +- migrations/000008_add_user_groups.up.sql | 1 + ui/views/admin.templ | 100 +++++-- ui/views/admin_templ.go | 328 ++++++++++++++++++----- 7 files changed, 429 insertions(+), 98 deletions(-) diff --git a/cmd/web/handlers_admin.go b/cmd/web/handlers_admin.go index cd21ee3..d2c36bd 100644 --- a/cmd/web/handlers_admin.go +++ b/cmd/web/handlers_admin.go @@ -5,7 +5,9 @@ import ( "fmt" "net/http" + "git.32bit.cafe/32bitcafe/guestbook/internal/forms" "git.32bit.cafe/32bitcafe/guestbook/internal/models" + "git.32bit.cafe/32bitcafe/guestbook/internal/validator" "git.32bit.cafe/32bitcafe/guestbook/ui/views" ) @@ -39,6 +41,77 @@ func (app *application) getAdminPanelUser(w http.ResponseWriter, r *http.Request views.AdminPanelUserMgmtView(fmt.Sprintf("User Management - %s", u.Username), data, u).Render(r.Context(), w) } +func (app *application) getAdminPanelUserMgmtDetail(w http.ResponseWriter, r *http.Request) { + slug := r.PathValue("id") + u, err := app.users.Get(slugToShortId(slug)) + if err != nil { + if errors.Is(err, models.ErrNoRecord) { + http.NotFound(w, r) + } else { + app.serverError(w, r, err) + } + return + } + views.AdminPanelUserMgmtDetail(u).Render(r.Context(), w) +} + +func (app *application) getAdminPanelUserMgmtForm(w http.ResponseWriter, r *http.Request) { + slug := r.PathValue("id") + u, err := app.users.Get(slugToShortId(slug)) + if err != nil { + if errors.Is(err, models.ErrNoRecord) { + http.NotFound(w, r) + } else { + app.serverError(w, r, err) + } + return + } + var form forms.AdminUserMgmtForm + form.Username = u.Username + form.Email = u.Email + + data := app.newCommonData(r) + views.AdminPanelUserMgmtEditForm(data.CSRFToken, form, u, []models.UserGroupId{}).Render(r.Context(), w) +} + +func (app *application) putAdminPanelUserMgmtForm(w http.ResponseWriter, r *http.Request) { + slug := r.PathValue("id") + u, err := app.users.Get(slugToShortId(slug)) + if err != nil { + if errors.Is(err, models.ErrNoRecord) { + http.NotFound(w, r) + } else { + app.serverError(w, r, err) + } + return + } + var form forms.AdminUserMgmtForm + err = app.decodePostForm(r, &form) + if err != nil { + app.clientError(w, http.StatusBadRequest) + } + + form.CheckField(validator.NotBlank(form.Username), "admin_username", "This field cannot be blank") + form.CheckField(validator.NotBlank(form.Email), "admin_useremail", "This field cannot be blank") + form.CheckField(validator.Matches(form.Email, validator.EmailRX), "admin_useremail", "Please provide a valid email address") + if !form.Valid() { + data := app.newCommonData(r) + w.WriteHeader(http.StatusUnprocessableEntity) + views.AdminPanelUserMgmtEditForm(data.CSRFToken, form, u, []models.UserGroupId{}).Render(r.Context(), w) + return + } + updatedUser := u + updatedUser.Username = form.Username + updatedUser.Email = form.Email + err = app.users.UpdateUser(updatedUser) + if err != nil { + app.serverError(w, r, err) + return + } + views.AdminPanelUserMgmtDetail(updatedUser).Render(r.Context(), w) + +} + func (app *application) putAdminPanelBanUser(w http.ResponseWriter, r *http.Request) { slug := r.PathValue("id") u, err := app.users.Get(slugToShortId(slug)) diff --git a/cmd/web/routes.go b/cmd/web/routes.go index 1ba345b..d0d61bb 100644 --- a/cmd/web/routes.go +++ b/cmd/web/routes.go @@ -62,6 +62,9 @@ func (app *application) routes() http.Handler { mux.Handle("GET /admin", adminOnly.ThenFunc(app.getAdminPanelLanding)) mux.Handle("GET /admin/users", adminOnly.ThenFunc(app.getAdminPanelAllUsers)) mux.Handle("GET /admin/users/{id}", adminOnly.ThenFunc(app.getAdminPanelUser)) + mux.Handle("GET /admin/users/{id}/edit", adminOnly.ThenFunc(app.getAdminPanelUserMgmtForm)) + mux.Handle("GET /admin/users/{id}/detail", adminOnly.ThenFunc(app.getAdminPanelUserMgmtDetail)) + mux.Handle("PUT /admin/users/{id}/edit", adminOnly.ThenFunc(app.putAdminPanelUserMgmtForm)) return standard.Then(mux) } diff --git a/internal/forms/forms.go b/internal/forms/forms.go index bfb739c..25ba116 100644 --- a/internal/forms/forms.go +++ b/internal/forms/forms.go @@ -1,6 +1,9 @@ package forms -import "git.32bit.cafe/32bitcafe/guestbook/internal/validator" +import ( + "git.32bit.cafe/32bitcafe/guestbook/internal/models" + "git.32bit.cafe/32bitcafe/guestbook/internal/validator" +) type UserRegistrationForm struct { Name string `schema:"username"` @@ -52,6 +55,10 @@ type WebsiteSettingsForm struct { } type AdminUserMgmtForm struct { + Username string `schema:"admin_username"` + Email string `schema:"admin_useremail"` + Groups []models.UserGroupId `schema:"admin_usergroups"` + validator.Validator `schema:"-"` } type InstallForm struct { diff --git a/internal/models/user.go b/internal/models/user.go index 6c38f9f..7719a17 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -461,7 +461,16 @@ func (m *UserModel) GetNumberOfUsers() int { } func (m *UserModel) AddUserToGroup(userId int64, groupId UserGroupId) error { - stmt := `INSERT INTO users_groups (UserId, GroupId) VALUES (?, ?)` + stmt := `INSERT OR IGNORE INTO users_groups (UserId, GroupId) VALUES (?, ?)` + _, err := m.DB.Exec(stmt, userId, groupId) + if err != nil { + return err + } + return nil +} + +func (m *UserModel) RemoveUserFromGroup(userId int64, groupId UserGroupId) error { + stmt := `DELETE FROM users_groups WHERE UserId = ? AND GroupId = ?` _, err := m.DB.Exec(stmt, userId, groupId) if err != nil { return err @@ -479,7 +488,7 @@ func (m *UserModel) BanUser(userId int64) error { } func (m *UserModel) UpdateUser(u User) error { - stmt := `UPDATE users SET Email=?, Name=? WHERE Id=?` + stmt := `UPDATE users SET Email=?, Username=? WHERE Id=?` _, err := m.DB.Exec(stmt, u.Email, u.Username, u.ID) if err != nil { return err diff --git a/migrations/000008_add_user_groups.up.sql b/migrations/000008_add_user_groups.up.sql index 99c1aab..df0ca97 100644 --- a/migrations/000008_add_user_groups.up.sql +++ b/migrations/000008_add_user_groups.up.sql @@ -16,4 +16,5 @@ CREATE TABLE IF NOT EXISTS users_groups ( FOREIGN KEY (GroupId) REFERENCES groups(Id) ON DELETE RESTRICT ON UPDATE RESTRICT + UNIQUE(UserId, GroupId) ); diff --git a/ui/views/admin.templ b/ui/views/admin.templ index c1e8038..3e04beb 100644 --- a/ui/views/admin.templ +++ b/ui/views/admin.templ @@ -4,6 +4,7 @@ import ( "fmt" "git.32bit.cafe/32bitcafe/guestbook/internal/models" "time" + "git.32bit.cafe/32bitcafe/guestbook/internal/forms" ) templ adminBase(title string, data CommonData) { @@ -82,35 +83,84 @@ templ AdminPanelUsersView(title string, data CommonData, users []models.User) { } } +templ AdminPanelUserMgmtDetail(user models.User) { +
    +
    +

    User Info

    +
    +
    Username
    +

    { user.Username }

    +
    +
    +
    Email
    +

    { user.Email }

    +
    +
    +
    Joined
    +

    { user.Created.Format(time.RFC3339) }

    +
    +
    +
    +

    Groups

    +
      + for _, g := range user.Groups { +
    • { fmt.Sprintf("%s", getGroupName(g)) }
    • + } +
    +
    +
    +

    Actions

    + {{ getFormUrl := fmt.Sprintf("/admin/users/%s/edit", shortIdToSlug(user.ShortId)) }} + + + +
    +
    +} + templ AdminPanelUserMgmtView(title string, data CommonData, user models.User) { @adminBase(title, data) {
    @adminSidebar() -
    -
    -

    User Info

    -
    -
    Username
    -

    { user.Username }

    -
    -
    -
    Email
    -

    { user.Email }

    -
    -
    -
    Joined
    -

    { user.Created.Format(time.RFC3339) }

    -
    -
    -
    -

    Groups

    -
      - for _, g := range user.Groups { -
    • { fmt.Sprintf("%s", getGroupName(g)) }
    • - } -
    -
    -
    + @AdminPanelUserMgmtDetail(user)
    } } + +templ AdminPanelUserMgmtEditForm(csrfToken string, form forms.AdminUserMgmtForm, user models.User, groups []models.UserGroupId) { +
    +
    + +
    +

    User Info

    +
    +
    Username
    + +
    +
    +
    Email
    + +
    +
    +
    Joined
    +

    { user.Created.Format(time.RFC3339) }

    +
    +
    +
    +

    Groups

    +
      + for _, g := range groups { +
    • { fmt.Sprintf("%s", getGroupName(g)) }
    • + } +
    +
    +
    +

    Actions

    + {{ putFormUrl := fmt.Sprintf("/admin/users/%s/edit", shortIdToSlug(user.ShortId)) }} + {{ getDetailUrl := fmt.Sprintf("/admin/users/%s/detail", shortIdToSlug(user.ShortId)) }} + + +
    +
    +
    +} diff --git a/ui/views/admin_templ.go b/ui/views/admin_templ.go index 62ca8da..47762e1 100644 --- a/ui/views/admin_templ.go +++ b/ui/views/admin_templ.go @@ -10,6 +10,7 @@ import templruntime "github.com/a-h/templ/runtime" import ( "fmt" + "git.32bit.cafe/32bitcafe/guestbook/internal/forms" "git.32bit.cafe/32bitcafe/guestbook/internal/models" "time" ) @@ -42,7 +43,7 @@ func adminBase(title string, data CommonData) templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 13, Col: 17} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 14, Col: 17} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -55,7 +56,7 @@ func adminBase(title string, data CommonData) templ.Component { var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(`{"includeIndicatorStyles":false}`) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 16, Col: 72} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 17, Col: 72} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -162,7 +163,7 @@ func AdminPanelLandingView(title string, data CommonData) templ.Component { var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 53, Col: 15} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 54, Col: 15} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -249,7 +250,7 @@ func AdminPanelUsersView(title string, data CommonData, users []models.User) tem var templ_7745c5c3_Var11 string templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(u.Username) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 73, Col: 51} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 74, Col: 51} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) if templ_7745c5c3_Err != nil { @@ -262,7 +263,7 @@ func AdminPanelUsersView(title string, data CommonData, users []models.User) tem var templ_7745c5c3_Var12 string templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(u.Created.Format(time.RFC3339)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 74, Col: 44} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 75, Col: 44} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { @@ -275,7 +276,7 @@ func AdminPanelUsersView(title string, data CommonData, users []models.User) tem var templ_7745c5c3_Var13 string templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(u.Email) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 75, Col: 21} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 76, Col: 21} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { @@ -300,7 +301,7 @@ func AdminPanelUsersView(title string, data CommonData, users []models.User) tem }) } -func AdminPanelUserMgmtView(title string, data CommonData, user models.User) templ.Component { +func AdminPanelUserMgmtDetail(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 { @@ -321,7 +322,116 @@ func AdminPanelUserMgmtView(title string, data CommonData, user models.User) tem templ_7745c5c3_Var14 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var15 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "

    User Info

    Username

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var15 string + templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(user.Username) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 92, Col: 34} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "

    Email

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var16 string + templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(user.Email) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 96, Col: 31} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "

    Joined

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var17 string + templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(user.Created.Format(time.RFC3339)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 100, Col: 54} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "

    Groups

      ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, g := range user.Groups { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "
    • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var18 string + templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s", getGroupName(g))) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 107, Col: 60} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
    • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "

    Actions

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + getFormUrl := fmt.Sprintf("/admin/users/%s/edit", shortIdToSlug(user.ShortId)) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func AdminPanelUserMgmtView(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 { + 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_Var20 := templ.GetChildren(ctx) + if templ_7745c5c3_Var20 == nil { + 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 { @@ -333,7 +443,7 @@ func AdminPanelUserMgmtView(title string, data CommonData, user models.User) tem }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -341,75 +451,153 @@ func AdminPanelUserMgmtView(title string, data CommonData, user models.User) tem if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "

    User Info

    Username

    ") + templ_7745c5c3_Err = AdminPanelUserMgmtDetail(user).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var16 string - templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(user.Username) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 94, Col: 24} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "

    Email

    ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var17 string - templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(user.Email) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 98, Col: 21} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "

    Joined

    ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var18 string - templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(user.Created.Format(time.RFC3339)) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 102, Col: 44} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "

    Groups

      ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - for _, g := range user.Groups { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
    • ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var19 string - templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s", getGroupName(g))) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 109, Col: 47} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "
    • ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } return nil }) - templ_7745c5c3_Err = adminBase(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var15), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = adminBase(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var21), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func AdminPanelUserMgmtEditForm(csrfToken string, form forms.AdminUserMgmtForm, user models.User, groups []models.UserGroupId) 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_Var22 := templ.GetChildren(ctx) + if templ_7745c5c3_Var22 == nil { + templ_7745c5c3_Var22 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "

    User Info

    Username
    Email
    Joined

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var26 string + templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(user.Created.Format(time.RFC3339)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 146, Col: 54} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "

    Groups

      ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, g := range groups { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "
    • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var27 string + templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s", getGroupName(g))) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 153, Col: 60} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "
    • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "

    Actions

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + putFormUrl := fmt.Sprintf("/admin/users/%s/edit", shortIdToSlug(user.ShortId)) + getDetailUrl := fmt.Sprintf("/admin/users/%s/detail", shortIdToSlug(user.ShortId)) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } -- 2.30.2 From a0ba2622ced6ba8fdd24d4118068123d5458ec47 Mon Sep 17 00:00:00 2001 From: yequari Date: Sat, 27 Dec 2025 11:24:47 -0700 Subject: [PATCH 5/8] user & group modification in admin panel --- cmd/web/handlers_admin.go | 31 +++- cmd/web/routes.go | 2 + internal/forms/forms.go | 2 +- internal/models/user.go | 10 ++ ui/views/admin.templ | 166 +++++++++++--------- ui/views/admin_templ.go | 319 +++++++++++++++++++++++--------------- 6 files changed, 330 insertions(+), 200 deletions(-) diff --git a/cmd/web/handlers_admin.go b/cmd/web/handlers_admin.go index d2c36bd..4adc7ec 100644 --- a/cmd/web/handlers_admin.go +++ b/cmd/web/handlers_admin.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "net/http" + "time" "git.32bit.cafe/32bitcafe/guestbook/internal/forms" "git.32bit.cafe/32bitcafe/guestbook/internal/models" @@ -52,7 +53,8 @@ func (app *application) getAdminPanelUserMgmtDetail(w http.ResponseWriter, r *ht } return } - views.AdminPanelUserMgmtDetail(u).Render(r.Context(), w) + commonData := app.newCommonData(r) + views.AdminPanelUserMgmtDetail(commonData.CSRFToken, u).Render(r.Context(), w) } func (app *application) getAdminPanelUserMgmtForm(w http.ResponseWriter, r *http.Request) { @@ -108,7 +110,8 @@ func (app *application) putAdminPanelUserMgmtForm(w http.ResponseWriter, r *http app.serverError(w, r, err) return } - views.AdminPanelUserMgmtDetail(updatedUser).Render(r.Context(), w) + commonData := app.newCommonData(r) + views.AdminPanelUserMgmtDetail(commonData.CSRFToken, updatedUser).Render(r.Context(), w) } @@ -128,6 +131,30 @@ func (app *application) putAdminPanelBanUser(w http.ResponseWriter, r *http.Requ app.serverError(w, r, err) return } + u.Banned = time.Now() + commonData := app.newCommonData(r) + views.AdminPanelUserMgmtDetail(commonData.CSRFToken, u).Render(r.Context(), w) +} + +func (app *application) putAdminPanelUnbanUser(w http.ResponseWriter, r *http.Request) { + slug := r.PathValue("id") + u, err := app.users.Get(slugToShortId(slug)) + if err != nil { + if errors.Is(err, models.ErrNoRecord) { + http.NotFound(w, r) + } else { + app.serverError(w, r, err) + } + return + } + err = app.users.UnbanUser(u.ID) + if err != nil { + app.serverError(w, r, err) + return + } + u.Banned = time.Time{} + commonData := app.newCommonData(r) + views.AdminPanelUserMgmtDetail(commonData.CSRFToken, u).Render(r.Context(), w) } func (app *application) getAdminPanelWebsites(w http.ResponseWriter, r *http.Request) { diff --git a/cmd/web/routes.go b/cmd/web/routes.go index d0d61bb..1f843cf 100644 --- a/cmd/web/routes.go +++ b/cmd/web/routes.go @@ -65,6 +65,8 @@ func (app *application) routes() http.Handler { mux.Handle("GET /admin/users/{id}/edit", adminOnly.ThenFunc(app.getAdminPanelUserMgmtForm)) mux.Handle("GET /admin/users/{id}/detail", adminOnly.ThenFunc(app.getAdminPanelUserMgmtDetail)) mux.Handle("PUT /admin/users/{id}/edit", adminOnly.ThenFunc(app.putAdminPanelUserMgmtForm)) + mux.Handle("PUT /admin/users/{id}/ban", adminOnly.ThenFunc(app.putAdminPanelBanUser)) + mux.Handle("PUT /admin/users/{id}/unban", adminOnly.ThenFunc(app.putAdminPanelUnbanUser)) return standard.Then(mux) } diff --git a/internal/forms/forms.go b/internal/forms/forms.go index 25ba116..85fddad 100644 --- a/internal/forms/forms.go +++ b/internal/forms/forms.go @@ -28,7 +28,7 @@ type CommentCreateForm struct { } type WebsiteCreateForm struct { - Name string `schema:"ws_name""` + Name string `schema:"ws_name"` SiteUrl string `schema:"ws_url"` AuthorName string `schema:"ws_author"` validator.Validator `schema:"-"` diff --git a/internal/models/user.go b/internal/models/user.go index 7719a17..1e70241 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -68,6 +68,7 @@ type UserModelInterface interface { GetNumberOfUsers() int AddUserToGroup(userId int64, groupId UserGroupId) error BanUser(userId int64) error + UnbanUser(userId int64) error UpdateUser(u User) error UpdatePassword(userId int64, password string) error Delete(userId int64) error @@ -487,6 +488,15 @@ func (m *UserModel) BanUser(userId int64) error { return nil } +func (m *UserModel) UnbanUser(userId int64) error { + stmt := `UPDATE users SET Banned=NULL WHERE Id=?` + _, err := m.DB.Exec(stmt, userId) + if err != nil { + return err + } + return nil +} + func (m *UserModel) UpdateUser(u User) error { stmt := `UPDATE users SET Email=?, Username=? WHERE Id=?` _, err := m.DB.Exec(stmt, u.Email, u.Username, u.ID) diff --git a/ui/views/admin.templ b/ui/views/admin.templ index 3e04beb..f8ab1c3 100644 --- a/ui/views/admin.templ +++ b/ui/views/admin.templ @@ -2,9 +2,10 @@ package views import ( "fmt" - "git.32bit.cafe/32bitcafe/guestbook/internal/models" - "time" "git.32bit.cafe/32bitcafe/guestbook/internal/forms" + "git.32bit.cafe/32bitcafe/guestbook/internal/models" + "slices" + "time" ) templ adminBase(title string, data CommonData) { @@ -38,6 +39,7 @@ templ adminSidebar() {

    Administration

    @@ -68,14 +70,14 @@ templ AdminPanelUsersView(title string, data CommonData, users []models.User) { Username Joined Email - for _, u := range users { - + for _, u := range users { + {{ url := fmt.Sprintf("/admin/users/%s", shortIdToSlug(u.ShortId)) }} { u.Username } { u.Created.Format(time.RFC3339) } { u.Email } - - } + + }
    @@ -83,84 +85,98 @@ templ AdminPanelUsersView(title string, data CommonData, users []models.User) { } } -templ AdminPanelUserMgmtDetail(user models.User) { -
    -
    -

    User Info

    -
    -
    Username
    -

    { user.Username }

    -
    -
    -
    Email
    -

    { user.Email }

    -
    -
    -
    Joined
    -

    { user.Created.Format(time.RFC3339) }

    -
    -
    -
    -

    Groups

    -
      - for _, g := range user.Groups { -
    • { fmt.Sprintf("%s", getGroupName(g)) }
    • - } -
    -
    -
    -

    Actions

    - {{ getFormUrl := fmt.Sprintf("/admin/users/%s/edit", shortIdToSlug(user.ShortId)) }} - - - -
    -
    +templ AdminPanelUserMgmtDetail(csrfToken string, user models.User) { +
    +
    + +
    +

    User Info

    +
    +
    Username
    +

    { user.Username }

    +
    +
    +
    Email
    +

    { user.Email }

    +
    +
    +
    Joined
    +

    { user.Created.Format(time.RFC3339) }

    +
    +
    +
    +

    Groups

    +
      + for _, g := range user.Groups { +
    • { fmt.Sprintf("%s", getGroupName(g)) }
    • + } +
    +
    +
    +

    Actions

    + {{ getFormUrl := fmt.Sprintf("/admin/users/%s/edit", shortIdToSlug(user.ShortId)) }} + {{ putBanUrl := fmt.Sprintf("/admin/users/%s/ban", shortIdToSlug(user.ShortId)) }} + {{ putUnbanUrl := fmt.Sprintf("/admin/users/%s/unban", shortIdToSlug(user.ShortId)) }} + {{ deleteUrl := fmt.Sprintf("/admin/users/%s", shortIdToSlug(user.ShortId)) }} + + if user.ID != 1 { + if user.Banned.IsZero() { + + } else { + + } + + } +
    +
    +
    } templ AdminPanelUserMgmtView(title string, data CommonData, user models.User) { @adminBase(title, data) {
    @adminSidebar() - @AdminPanelUserMgmtDetail(user) + @AdminPanelUserMgmtDetail(data.CSRFToken, user)
    } } templ AdminPanelUserMgmtEditForm(csrfToken string, form forms.AdminUserMgmtForm, user models.User, groups []models.UserGroupId) { -
    -
    - -
    -

    User Info

    -
    -
    Username
    - -
    -
    -
    Email
    - -
    -
    -
    Joined
    -

    { user.Created.Format(time.RFC3339) }

    -
    -
    -
    -

    Groups

    -
      - for _, g := range groups { -
    • { fmt.Sprintf("%s", getGroupName(g)) }
    • - } -
    -
    -
    -

    Actions

    - {{ putFormUrl := fmt.Sprintf("/admin/users/%s/edit", shortIdToSlug(user.ShortId)) }} - {{ getDetailUrl := fmt.Sprintf("/admin/users/%s/detail", shortIdToSlug(user.ShortId)) }} - - -
    -
    -
    +
    +
    + +
    +

    User Info

    +
    +
    Username
    + +
    +
    +
    Email
    + +
    +
    +
    Joined
    +

    { user.Created.Format(time.RFC3339) }

    +
    +
    +
    +
    +

    Groups

    + {{ isAdmin := slices.Contains(user.Groups, models.AdminGroup) }} + + + + +
    +
    +
    +

    Actions

    + {{ putFormUrl := fmt.Sprintf("/admin/users/%s/edit", shortIdToSlug(user.ShortId)) }} + {{ getDetailUrl := fmt.Sprintf("/admin/users/%s/detail", shortIdToSlug(user.ShortId)) }} + + +
    +
    +
    } diff --git a/ui/views/admin_templ.go b/ui/views/admin_templ.go index 47762e1..d2884b2 100644 --- a/ui/views/admin_templ.go +++ b/ui/views/admin_templ.go @@ -12,6 +12,7 @@ import ( "fmt" "git.32bit.cafe/32bitcafe/guestbook/internal/forms" "git.32bit.cafe/32bitcafe/guestbook/internal/models" + "slices" "time" ) @@ -43,7 +44,7 @@ func adminBase(title string, data CommonData) templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 14, Col: 17} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 15, Col: 17} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -56,7 +57,7 @@ func adminBase(title string, data CommonData) templ.Component { var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(`{"includeIndicatorStyles":false}`) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 17, Col: 72} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 18, Col: 72} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -107,7 +108,7 @@ func adminSidebar() templ.Component { templ_7745c5c3_Var4 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -163,7 +164,7 @@ func AdminPanelLandingView(title string, data CommonData) templ.Component { var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 54, Col: 15} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 56, Col: 15} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -250,7 +251,7 @@ func AdminPanelUsersView(title string, data CommonData, users []models.User) tem var templ_7745c5c3_Var11 string templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(u.Username) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 74, Col: 51} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 76, Col: 51} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) if templ_7745c5c3_Err != nil { @@ -263,7 +264,7 @@ func AdminPanelUsersView(title string, data CommonData, users []models.User) tem var templ_7745c5c3_Var12 string templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(u.Created.Format(time.RFC3339)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 75, Col: 44} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 77, Col: 44} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { @@ -276,7 +277,7 @@ func AdminPanelUsersView(title string, data CommonData, users []models.User) tem var templ_7745c5c3_Var13 string templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(u.Email) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 76, Col: 21} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 78, Col: 21} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { @@ -301,7 +302,7 @@ func AdminPanelUsersView(title string, data CommonData, users []models.User) tem }) } -func AdminPanelUserMgmtDetail(user models.User) templ.Component { +func AdminPanelUserMgmtDetail(csrfToken string, 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 { @@ -322,87 +323,163 @@ func AdminPanelUserMgmtDetail(user models.User) templ.Component { templ_7745c5c3_Var14 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "

    User Info

    Username

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

    Email

    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\">

    User Info

    Username

    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var16 string - templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(user.Email) + templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(user.Username) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 96, Col: 31} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 96, Col: 23} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "

    Joined

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

    Email

    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var17 string - templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(user.Created.Format(time.RFC3339)) + templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(user.Email) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 100, Col: 54} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 100, Col: 20} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "

    Groups

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

    Joined

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var18 string + templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(user.Created.Format(time.RFC3339)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 104, Col: 43} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "

    Groups

      ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } for _, g := range user.Groups { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "
    • ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
    • ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var18 string - templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s", getGroupName(g))) + var templ_7745c5c3_Var19 string + templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s", getGroupName(g))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 107, Col: 60} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 111, Col: 46} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
    • ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "

    Actions

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

    Actions

    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } getFormUrl := fmt.Sprintf("/admin/users/%s/edit", shortIdToSlug(user.ShortId)) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "\" hx-target=\"#user-info\">Edit ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if user.ID != 1 { + if user.Banned.IsZero() { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -426,12 +503,12 @@ func AdminPanelUserMgmtView(title string, data CommonData, user models.User) tem }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var20 := templ.GetChildren(ctx) - if templ_7745c5c3_Var20 == nil { - templ_7745c5c3_Var20 = templ.NopComponent + templ_7745c5c3_Var24 := templ.GetChildren(ctx) + if templ_7745c5c3_Var24 == nil { + templ_7745c5c3_Var24 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var21 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_Var25 := 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 { @@ -443,7 +520,7 @@ func AdminPanelUserMgmtView(title string, data CommonData, user models.User) tem }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -451,17 +528,17 @@ func AdminPanelUserMgmtView(title string, data CommonData, user models.User) tem if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = AdminPanelUserMgmtDetail(user).Render(ctx, templ_7745c5c3_Buffer) + templ_7745c5c3_Err = AdminPanelUserMgmtDetail(data.CSRFToken, user).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } return nil }) - templ_7745c5c3_Err = adminBase(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var21), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = adminBase(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var25), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -485,119 +562,117 @@ func AdminPanelUserMgmtEditForm(csrfToken string, form forms.AdminUserMgmtForm, }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var22 := templ.GetChildren(ctx) - if templ_7745c5c3_Var22 == nil { - templ_7745c5c3_Var22 = templ.NopComponent + templ_7745c5c3_Var26 := templ.GetChildren(ctx) + if templ_7745c5c3_Var26 == nil { + templ_7745c5c3_Var26 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "

    User Info

    Username
    Email
    Joined

    ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var26 string - templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(user.Created.Format(time.RFC3339)) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 146, Col: 54} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "

    Groups

      ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - for _, g := range groups { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "
    • ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var27 string - templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s", getGroupName(g))) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 153, Col: 60} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "
    • ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "

    Actions

    ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - putFormUrl := fmt.Sprintf("/admin/users/%s/edit", shortIdToSlug(user.ShortId)) - getDetailUrl := fmt.Sprintf("/admin/users/%s/detail", shortIdToSlug(user.ShortId)) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "
    Email
    Cancel
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "\" required>
    Joined

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var30 string + templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(user.Created.Format(time.RFC3339)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 160, Col: 43} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "

    Groups

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + isAdmin := slices.Contains(user.Groups, models.AdminGroup) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "

    Actions

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + putFormUrl := fmt.Sprintf("/admin/users/%s/edit", shortIdToSlug(user.ShortId)) + getDetailUrl := fmt.Sprintf("/admin/users/%s/detail", shortIdToSlug(user.ShortId)) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } -- 2.30.2 From fc91b3dec76c596a41c45c844f13744ca37f6685 Mon Sep 17 00:00:00 2001 From: yequari Date: Sat, 27 Dec 2025 11:25:19 -0700 Subject: [PATCH 6/8] add database indices --- migrations/000011_create_indices.down.sql | 5 +++++ migrations/000011_create_indices.up.sql | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 migrations/000011_create_indices.down.sql create mode 100644 migrations/000011_create_indices.up.sql diff --git a/migrations/000011_create_indices.down.sql b/migrations/000011_create_indices.down.sql new file mode 100644 index 0000000..c6fabe1 --- /dev/null +++ b/migrations/000011_create_indices.down.sql @@ -0,0 +1,5 @@ +DROP INDEX websites_users; +DROP INDEX guestbooks_websites; +DROP INDEX comments_guestbooks; +DROP INDEX web_settings_websites; +DROP INDEX user_settings_users; diff --git a/migrations/000011_create_indices.up.sql b/migrations/000011_create_indices.up.sql new file mode 100644 index 0000000..6d3c89b --- /dev/null +++ b/migrations/000011_create_indices.up.sql @@ -0,0 +1,5 @@ +CREATE INDEX websites_users ON websites(UserId); +CREATE INDEX guestbooks_websites ON guestbooks(WebsiteId); +CREATE INDEX comments_guestbooks ON guestbook_comments(GuestbookId); +CREATE INDEX web_settings_websites ON guestbook_settings(GuestbookId); +CREATE INDEX user_settings_users ON user_settings(UserId); \ No newline at end of file -- 2.30.2 From 702c261e2be9578524709e39ca3abf8e5d7417f0 Mon Sep 17 00:00:00 2001 From: yequari Date: Thu, 1 Jan 2026 12:02:41 -0700 Subject: [PATCH 7/8] add more admin panel pages and styling --- cmd/web/handlers_admin.go | 89 +++- cmd/web/routes.go | 2 + internal/models/guestbookcomment.go | 15 + internal/models/mocks/website.go | 16 + internal/models/user.go | 15 + internal/models/website.go | 93 ++++ ui/static/css/style.css | 48 ++ ui/views/admin.templ | 91 +++- ui/views/admin_templ.go | 735 ++++++++++++++++++++-------- ui/views/common.templ | 33 ++ ui/views/common_templ.go | 144 +++++- 11 files changed, 1071 insertions(+), 210 deletions(-) diff --git a/cmd/web/handlers_admin.go b/cmd/web/handlers_admin.go index 4adc7ec..ba47bf6 100644 --- a/cmd/web/handlers_admin.go +++ b/cmd/web/handlers_admin.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "net/http" + "strconv" "time" "git.32bit.cafe/32bitcafe/guestbook/internal/forms" @@ -13,8 +14,28 @@ import ( ) func (app *application) getAdminPanelLanding(w http.ResponseWriter, r *http.Request) { + websites, err := app.websites.GetCount() + if err != nil { + app.serverError(w, r, err) + return + } + users, err := app.users.GetCount() + if err != nil { + app.serverError(w, r, err) + return + } + comments, err := app.guestbookComments.GetCount() + if err != nil { + app.serverError(w, r, err) + return + } + stats := views.AdminStat{ + WebsiteCount: websites, + UserCount: users, + CommentCount: comments, + } data := app.newCommonData(r) - views.AdminPanelLandingView("Admin Panel", data).Render(r.Context(), w) + views.AdminPanelLandingView("Admin Panel", data, stats).Render(r.Context(), w) } func (app *application) getAdminPanelAllUsers(w http.ResponseWriter, r *http.Request) { @@ -158,4 +179,70 @@ func (app *application) putAdminPanelUnbanUser(w http.ResponseWriter, r *http.Re } func (app *application) getAdminPanelWebsites(w http.ResponseWriter, r *http.Request) { + page := r.URL.Query().Get("page") + count := r.URL.Query().Get("count") + var pageNum int64 = 1 + var pageSize int64 = 5 + var err error + if page != "" { + pageNum, err = strconv.ParseInt(page, 10, 0) + if err != nil { + app.clientError(w, http.StatusBadRequest) + return + } + } + if count != "" { + pageSize, err = strconv.ParseInt(count, 10, 0) + if err != nil { + app.clientError(w, http.StatusBadRequest) + return + } + } + websites, err := app.websites.GetAllPage(pageNum, pageSize) + if err != nil { + app.serverError(w, r, err) + return + } + total, err := app.websites.GetCount() + if err != nil { + app.serverError(w, r, err) + return + } + commonData := app.newCommonData(r) + views.AdminPanelAllWebsitesView("All websites", commonData, websites, pageNum, total).Render(r.Context(), w) +} + +func (app *application) getAdminPanelWebsiteDetails(w http.ResponseWriter, r *http.Request) { + slug := r.PathValue("id") + // page := r.URL.Query().Get("page") + // count := r.URL.Query().Get("count") + // var pageNum int64 = 1 + // var pageSize int64 = 5 + // var err error + // if page != "" { + // pageNum, err = strconv.ParseInt(page, 10, 0) + // if err != nil { + // app.clientError(w, http.StatusBadRequest) + // return + // } + // } + // if count != "" { + // pageSize, err = strconv.ParseInt(count, 10, 0) + // if err != nil { + // app.clientError(w, http.StatusBadRequest) + // return + // } + // } + website, err := app.websites.Get(slugToShortId(slug)) + if err != nil { + app.serverError(w, r, err) + return + } + comments, err := app.guestbookComments.GetAll(website.Guestbook.ID) + if err != nil { + app.serverError(w, r, err) + return + } + commonData := app.newCommonData(r) + views.AdminPanelWebsiteDetailView(fmt.Sprintf("Admin - %s", website.Name), commonData, website, comments).Render(r.Context(), w) } diff --git a/cmd/web/routes.go b/cmd/web/routes.go index 1f843cf..932e778 100644 --- a/cmd/web/routes.go +++ b/cmd/web/routes.go @@ -67,6 +67,8 @@ func (app *application) routes() http.Handler { mux.Handle("PUT /admin/users/{id}/edit", adminOnly.ThenFunc(app.putAdminPanelUserMgmtForm)) mux.Handle("PUT /admin/users/{id}/ban", adminOnly.ThenFunc(app.putAdminPanelBanUser)) mux.Handle("PUT /admin/users/{id}/unban", adminOnly.ThenFunc(app.putAdminPanelUnbanUser)) + mux.Handle("GET /admin/websites", adminOnly.ThenFunc(app.getAdminPanelWebsites)) + mux.Handle("GET /admin/websites/{id}", adminOnly.ThenFunc(app.getAdminPanelWebsiteDetails)) return standard.Then(mux) } diff --git a/internal/models/guestbookcomment.go b/internal/models/guestbookcomment.go index eb94614..00ba1a4 100644 --- a/internal/models/guestbookcomment.go +++ b/internal/models/guestbookcomment.go @@ -33,6 +33,7 @@ type GuestbookCommentModel struct { type GuestbookCommentModelInterface interface { Insert(shortId uint64, guestbookId, parentId int64, authorName, authorEmail, authorSite, commentText, pageUrl string, isPublished bool) (int64, error) Get(shortId uint64) (GuestbookComment, error) + GetCount() (int64, error) GetVisible(guestbookId int64) ([]GuestbookComment, error) GetVisibleSerialized(guestbookId int64) ([]GuestbookCommentSerialized, error) GetDeleted(guestbookId int64) ([]GuestbookComment, error) @@ -74,6 +75,20 @@ func (m *GuestbookCommentModel) Get(shortId uint64) (GuestbookComment, error) { return c, nil } +func (m *GuestbookCommentModel) GetCount() (int64, error) { + stmt := `SELECT COUNT(*) FROM guestbook_comments WHERE Deleted IS NULL` + row := m.DB.QueryRow(stmt) + var result int64 + err := row.Scan(&result) + if err != nil { + return -1, err + } + if err = row.Err(); err != nil { + return -1, err + } + return result, nil +} + func (m *GuestbookCommentModel) GetVisible(guestbookId int64) ([]GuestbookComment, error) { stmt := `SELECT Id, ShortId, GuestbookId, ParentId, AuthorName, AuthorEmail, AuthorSite, CommentText, PageUrl, Created, IsPublished diff --git a/internal/models/mocks/website.go b/internal/models/mocks/website.go index bc50020..0fed223 100644 --- a/internal/models/mocks/website.go +++ b/internal/models/mocks/website.go @@ -56,6 +56,14 @@ func (m *WebsiteModel) GetAllUser(userId int64) ([]models.Website, error) { return []models.Website{mockWebsite}, nil } +func GetCountUser(userId int64) (int64, error) { + return 1, nil +} + +func (m *WebsiteModel) GetAllUserPage(userId int64, pageNum int64) ([]models.Website, error) { + return []models.Website{mockWebsite}, nil +} + func (m *WebsiteModel) GetById(id int64) (models.Website, error) { switch id { case 1: @@ -69,6 +77,14 @@ func (m *WebsiteModel) GetAll() ([]models.Website, error) { return []models.Website{mockWebsite}, nil } +func GetCount() (int64, error) { + return 1, nil +} + +func (m *WebsiteModel) GetAllPage(pageNum int64) ([]models.Website, error) { + return []models.Website{mockWebsite}, nil +} + func (m *WebsiteModel) Update(w models.Website) error { return nil } diff --git a/internal/models/user.go b/internal/models/user.go index 1e70241..a7b4e78 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -56,6 +56,7 @@ type UserModelInterface interface { Insert(shortId uint64, username string, email string, password string, settings UserSettings) error InsertWithoutPassword(shortId uint64, username string, email string, subject string, settings UserSettings) (int64, error) Get(shortId uint64) (User, error) + GetCount() (int64, error) GetById(id int64) (User, error) GetByEmail(email string) (int64, error) GetBySubject(subject string) (int64, error) @@ -247,6 +248,20 @@ func (m *UserModel) Get(shortId uint64) (User, error) { return u, nil } +func (m *UserModel) GetCount() (int64, error) { + stmt := `SELECT COUNT(*) FROM users WHERE Deleted IS NULL` + row := m.DB.QueryRow(stmt) + var result int64 + err := row.Scan(&result) + if err != nil { + return -1, err + } + if err = row.Err(); err != nil { + return -1, err + } + return result, nil +} + func (m *UserModel) GetById(id int64) (User, error) { stmt := `SELECT Id, ShortId, Username, Email, Created, Banned FROM users WHERE Id = ? AND Deleted IS NULL` tx, err := m.DB.Begin() diff --git a/internal/models/website.go b/internal/models/website.go index 9d29399..1d0048d 100644 --- a/internal/models/website.go +++ b/internal/models/website.go @@ -95,7 +95,11 @@ type WebsiteModelInterface interface { Insert(shortId uint64, userId int64, siteName, siteUrl, authorName string) (int64, error) Get(shortId uint64) (Website, error) GetAllUser(userId int64) ([]Website, error) + GetCountUser(userId int64) (int64, error) + GetAllUserPage(userId int64, pageNum int64, pageSize int64) ([]Website, error) GetAll() ([]Website, error) + GetCount() (int64, error) + GetAllPage(pageNum int64, pageSize int64) ([]Website, error) Update(w Website) error InitializeSettingsMap() error UpdateGuestbookSettings(guestbookId int64, settings GuestbookSettings) error @@ -270,6 +274,51 @@ func (m *WebsiteModel) GetAllUser(userId int64) ([]Website, error) { return websites, nil } +func (m *WebsiteModel) GetCountUser(userId int64) (int64, error) { + stmt := `SELECT COUNT(*) FROM websites WHERE UserId = ? AND Deleted IS NULL` + row := m.DB.QueryRow(stmt, userId) + var result int64 + err := row.Scan(&result) + if err != nil { + return -1, err + } + if err = row.Err(); err != nil { + return -1, err + } + return result, nil +} + +func (m *WebsiteModel) GetAllUserPage(userId int64, pageNum int64, pageSize int64) ([]Website, error) { + stmt := `SELECT w.Id, w.ShortId, w.Name, w.SiteUrl, w.AuthorName, w.UserId, w.Created, + g.Id, g.ShortId, g.Created, g.IsActive + FROM websites AS w INNER JOIN guestbooks AS g ON w.Id = g.WebsiteId + WHERE w.UserId = ? AND w.Deleted IS NULL + LIMIT ? OFFSET ?` + rows, err := m.DB.Query(stmt, userId, pageSize, (pageNum-1)*pageSize) + if err != nil { + return nil, err + } + var websites []Website + for rows.Next() { + var w Website + var u string + err := rows.Scan(&w.ID, &w.ShortId, &w.Name, &u, &w.AuthorName, &w.UserId, &w.Created, + &w.Guestbook.ID, &w.Guestbook.ShortId, &w.Guestbook.Created, &w.Guestbook.IsActive) + if err != nil { + return nil, err + } + w.Url, err = url.Parse(u) + if err != nil { + return nil, err + } + websites = append(websites, w) + } + if err = rows.Err(); err != nil { + return nil, err + } + return websites, nil +} + func (m *WebsiteModel) GetAll() ([]Website, error) { stmt := `SELECT w.Id, w.ShortId, w.Name, w.SiteUrl, w.AuthorName, w.UserId, w.Created, g.Id, g.ShortId, g.Created, g.IsActive @@ -299,6 +348,50 @@ func (m *WebsiteModel) GetAll() ([]Website, error) { return websites, nil } +func (m *WebsiteModel) GetCount() (int64, error) { + stmt := `SELECT COUNT(*) FROM websites WHERE Deleted IS NULL` + row := m.DB.QueryRow(stmt) + var result int64 + err := row.Scan(&result) + if err != nil { + return -1, err + } + if err = row.Err(); err != nil { + return -1, err + } + return result, nil +} + +func (m *WebsiteModel) GetAllPage(pageNum int64, pageSize int64) ([]Website, error) { + stmt := `SELECT w.Id, w.ShortId, w.Name, w.SiteUrl, w.AuthorName, w.UserId, w.Created, + g.Id, g.ShortId, g.Created, g.IsActive + FROM websites AS w INNER JOIN guestbooks AS g ON w.Id = g.WebsiteId WHERE w.Deleted IS NULL + LIMIT ? OFFSET ?` + rows, err := m.DB.Query(stmt, pageSize, (pageNum-1)*pageSize) + if err != nil { + return nil, err + } + var websites []Website + for rows.Next() { + var w Website + var u string + err := rows.Scan(&w.ID, &w.ShortId, &w.Name, &u, &w.AuthorName, &w.UserId, &w.Created, + &w.Guestbook.ID, &w.Guestbook.ShortId, &w.Guestbook.Created, &w.Guestbook.IsActive) + if err != nil { + return nil, err + } + w.Url, err = url.Parse(u) + if err != nil { + return nil, err + } + websites = append(websites, w) + } + if err = rows.Err(); err != nil { + return nil, err + } + return websites, nil +} + func (m *WebsiteModel) Update(w Website) error { stmt := `UPDATE websites SET Name = ?, SiteUrl = ?, AuthorName = ? WHERE ID = ?` r, err := m.DB.Exec(stmt, w.Name, w.Url.String(), w.AuthorName, w.ID) diff --git a/ui/static/css/style.css b/ui/static/css/style.css index af544a5..7872d7a 100644 --- a/ui/static/css/style.css +++ b/ui/static/css/style.css @@ -709,6 +709,54 @@ ul#websites li { margin-top: var(--space-2xl); } +/* Admin Panel */ +.admin-section { + background-color: var(--color-surface); + padding: var(--space-xl); + border-radius: var(--radius-lg); + border: 1px solid var(--color-border); + margin-bottom: var(--space-xl); +} + +.admin-section table { + width: 100%; +} + +.admin-section th { + border-bottom: 1px solid var(--color-border); +} + +.admin-section td { + padding: var(--space-sm); + text-align: center; +} + +.admin-flex { + display: flex; + justify-content: space-between; + gap: var(--space-sm); +} + +.admin-info { + flex: 1; + text-align: center; + font-size: x-large; +} + +/* Pagination */ +.pagination { + width: 100%; + display: flex; + justify-content: center; +} +.pagination-btn { + background-color: var(--color-surface); + padding: var(--space-sm); + border-radius: var(--radius-lg); + border: 1px solid var(--color-border); + margin: var(--space-xs); +} + /* Utilities */ hr { border: none; diff --git a/ui/views/admin.templ b/ui/views/admin.templ index f8ab1c3..e94dad8 100644 --- a/ui/views/admin.templ +++ b/ui/views/admin.templ @@ -8,6 +8,12 @@ import ( "time" ) +type AdminStat struct { + WebsiteCount int64 + UserCount int64 + CommentCount int64 +} + templ adminBase(title string, data CommonData) { @@ -41,20 +47,36 @@ templ adminSidebar() { } -templ AdminPanelLandingView(title string, data CommonData) { +templ AdminPanelLandingView(title string, data CommonData, stats AdminStat) { @adminBase(title, data) {
    @adminSidebar()
    -

    { title }

    -

    Welcome to the admin panel

    +
    +

    { title }

    +

    Welcome to the admin panel

    +
    +
    +
    +

    Users

    +

    { fmt.Sprintf("%d", stats.UserCount) }

    +
    +
    +

    Websites

    +

    { fmt.Sprintf("%d", stats.WebsiteCount) }

    +
    +
    +

    Comments

    +

    { fmt.Sprintf("%d", stats.CommentCount) }

    +
    +
    } @@ -65,7 +87,7 @@ templ AdminPanelUsersView(title string, data CommonData, users []models.User) {
    @adminSidebar()
    -
    +
    @@ -180,3 +202,62 @@ templ AdminPanelUserMgmtEditForm(csrfToken string, form forms.AdminUserMgmtForm, } + +templ AdminPanelAllWebsitesView(title string, data CommonData, websites []models.Website, pageNum int64, total int64) { + @adminBase(title, data) { +
    + @adminSidebar() +
    +
    +
    Username Joined
    + + + + + + for _, w := range websites { + + {{ detailUrl := fmt.Sprintf("/admin/websites/%s", shortIdToSlug(w.ShortId)) }} + + + + + {{ gbUrl := fmt.Sprintf("/websites/%s/guestbook", shortIdToSlug(w.ShortId)) }} + + + } +
    Site NameOwnerURLCreatedGuestbook
    { w.Name }{ w.AuthorName }{ w.Url.String() }{ w.Created.Format(time.RFC1123) }View
    +
    + @pagination("/admin/websites", pageNum, total, 5) +
    +
    + } +} + +templ AdminPanelWebsiteDetailView(title string, data CommonData, website models.Website, comments []models.GuestbookComment) { + @adminBase(title, data) { +
    + @adminSidebar() +
    +
    + + + + + + + for _, c := range comments { + + + + + + + + } +
    AuthorCreatedEmailHomepageComment
    { c.AuthorName }{ c.Created.Format(time.RFC1123) }{ c.AuthorEmail }{ c.AuthorSite }{ c.CommentText }
    +
    +
    +
    + } +} diff --git a/ui/views/admin_templ.go b/ui/views/admin_templ.go index d2884b2..a175b15 100644 --- a/ui/views/admin_templ.go +++ b/ui/views/admin_templ.go @@ -16,6 +16,12 @@ import ( "time" ) +type AdminStat struct { + WebsiteCount int64 + UserCount int64 + CommentCount int64 +} + func adminBase(title string, data CommonData) 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 @@ -44,7 +50,7 @@ func adminBase(title string, data CommonData) templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 15, Col: 17} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 21, Col: 17} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -57,7 +63,7 @@ func adminBase(title string, data CommonData) templ.Component { var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(`{"includeIndicatorStyles":false}`) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 18, Col: 72} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 24, Col: 72} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -108,7 +114,7 @@ func adminSidebar() templ.Component { templ_7745c5c3_Var4 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -116,7 +122,7 @@ func adminSidebar() templ.Component { }) } -func AdminPanelLandingView(title string, data CommonData) templ.Component { +func AdminPanelLandingView(title string, data CommonData, stats AdminStat) 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 { @@ -157,20 +163,59 @@ func AdminPanelLandingView(title string, data CommonData) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "

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

    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 56, Col: 15} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 63, Col: 16} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "

    Welcome to the admin panel

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

    Welcome to the admin panel

    Users

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var8 string + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", stats.UserCount)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 69, Col: 45} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "

    Websites

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var9 string + templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", stats.WebsiteCount)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 73, Col: 48} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "

    Comments

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var10 string + templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", stats.CommentCount)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 77, Col: 48} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "

    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -200,12 +245,12 @@ func AdminPanelUsersView(title string, data CommonData, users []models.User) tem }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var8 := templ.GetChildren(ctx) - if templ_7745c5c3_Var8 == nil { - templ_7745c5c3_Var8 = templ.NopComponent + templ_7745c5c3_Var11 := templ.GetChildren(ctx) + if templ_7745c5c3_Var11 == nil { + templ_7745c5c3_Var11 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var9 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_Var12 := 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 { @@ -217,7 +262,7 @@ func AdminPanelUsersView(title string, data CommonData, users []models.User) tem }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -225,76 +270,76 @@ func AdminPanelUsersView(title string, data CommonData, users []models.User) tem if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "
    UsernameJoinedEmail
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } for _, u := range users { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } url := fmt.Sprintf("/admin/users/%s", shortIdToSlug(u.ShortId)) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "
    UsernameJoinedEmail
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var11 string - templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(u.Username) + var templ_7745c5c3_Var14 string + templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(u.Username) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 76, Col: 51} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 98, Col: 51} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var12 string - templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(u.Created.Format(time.RFC3339)) + var templ_7745c5c3_Var15 string + templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(u.Created.Format(time.RFC3339)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 77, Col: 44} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 99, Col: 44} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var13 string - templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(u.Email) + var templ_7745c5c3_Var16 string + templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(u.Email) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 78, Col: 21} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 100, Col: 21} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } return nil }) - templ_7745c5c3_Err = adminBase(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var9), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = adminBase(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var12), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -318,87 +363,87 @@ func AdminPanelUserMgmtDetail(csrfToken string, user models.User) templ.Componen }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var14 := templ.GetChildren(ctx) - if templ_7745c5c3_Var14 == nil { - templ_7745c5c3_Var14 = templ.NopComponent + templ_7745c5c3_Var17 := templ.GetChildren(ctx) + if templ_7745c5c3_Var17 == nil { + templ_7745c5c3_Var17 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "

    User Info

    Username

    ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var16 string - templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(user.Username) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 96, Col: 23} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "

    Email

    ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var17 string - templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(user.Email) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 100, Col: 20} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "

    Joined

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

    Groups

      ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "\">

      User Info

      Username

      ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var19 string + templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(user.Username) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 118, Col: 23} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "

      Email

      ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var20 string + templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(user.Email) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 122, Col: 20} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "

      Joined

      ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var21 string + templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(user.Created.Format(time.RFC3339)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 126, Col: 43} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "

      Groups

        ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } for _, g := range user.Groups { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
      • ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "
      • ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var19 string - templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s", getGroupName(g))) + var templ_7745c5c3_Var22 string + templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s", getGroupName(g))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 111, Col: 46} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 133, Col: 46} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "
      • ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "

      Actions

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

    Actions

    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -406,80 +451,80 @@ func AdminPanelUserMgmtDetail(csrfToken string, user models.User) templ.Componen putBanUrl := fmt.Sprintf("/admin/users/%s/ban", shortIdToSlug(user.ShortId)) putUnbanUrl := fmt.Sprintf("/admin/users/%s/unban", shortIdToSlug(user.ShortId)) deleteUrl := fmt.Sprintf("/admin/users/%s", shortIdToSlug(user.ShortId)) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "\" hx-target=\"#user-info\">Edit ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if user.ID != 1 { if user.Banned.IsZero() { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "\" hx-confirm=\"Are you sure you want to ban this user?\" hx-target=\"#user-info\" class=\"danger\">Ban") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "\" hx-confirm=\"Are you sure you want to unban this user?\" hx-target=\"#user-info\">Unban") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "\" hx-confirm=\"Are you sure you want to delete this user? This is irreversible\" class=\"danger\">Delete") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -503,12 +548,12 @@ func AdminPanelUserMgmtView(title string, data CommonData, user models.User) tem }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var24 := templ.GetChildren(ctx) - if templ_7745c5c3_Var24 == nil { - templ_7745c5c3_Var24 = templ.NopComponent + templ_7745c5c3_Var27 := templ.GetChildren(ctx) + if templ_7745c5c3_Var27 == nil { + templ_7745c5c3_Var27 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var25 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_Var28 := 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 { @@ -520,7 +565,7 @@ func AdminPanelUserMgmtView(title string, data CommonData, user models.User) tem }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -532,13 +577,13 @@ func AdminPanelUserMgmtView(title string, data CommonData, user models.User) tem if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } return nil }) - templ_7745c5c3_Err = adminBase(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var25), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = adminBase(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var28), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -562,117 +607,409 @@ func AdminPanelUserMgmtEditForm(csrfToken string, form forms.AdminUserMgmtForm, }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var26 := templ.GetChildren(ctx) - if templ_7745c5c3_Var26 == nil { - templ_7745c5c3_Var26 = templ.NopComponent + templ_7745c5c3_Var29 := templ.GetChildren(ctx) + if templ_7745c5c3_Var29 == nil { + templ_7745c5c3_Var29 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "

    User Info

    Username
    Email
    Joined

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

    Groups

    ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - isAdmin := slices.Contains(user.Groups, models.AdminGroup) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "

    Actions

    ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - putFormUrl := fmt.Sprintf("/admin/users/%s/edit", shortIdToSlug(user.ShortId)) - getDetailUrl := fmt.Sprintf("/admin/users/%s/detail", shortIdToSlug(user.ShortId)) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "
    Email
    Cancel
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "\" required>
    Joined

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var33 string + templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(user.Created.Format(time.RFC3339)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 182, Col: 43} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "

    Groups

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + isAdmin := slices.Contains(user.Groups, models.AdminGroup) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "

    Actions

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + putFormUrl := fmt.Sprintf("/admin/users/%s/edit", shortIdToSlug(user.ShortId)) + getDetailUrl := fmt.Sprintf("/admin/users/%s/detail", shortIdToSlug(user.ShortId)) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func AdminPanelAllWebsitesView(title string, data CommonData, websites []models.Website, pageNum int64, total int64) 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_Var36 := templ.GetChildren(ctx) + if templ_7745c5c3_Var36 == nil { + templ_7745c5c3_Var36 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var37 := 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, 53, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = adminSidebar().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, w := range websites { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + detailUrl := fmt.Sprintf("/admin/websites/%s", shortIdToSlug(w.ShortId)) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + gbUrl := fmt.Sprintf("/websites/%s/guestbook", shortIdToSlug(w.ShortId)) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "
    Site NameOwnerURLCreatedGuestbook
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var39 string + templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(w.Name) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 221, Col: 57} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var40 string + templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs(w.AuthorName) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 222, Col: 26} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var42 string + templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.JoinStringErrs(w.Url.String()) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 223, Col: 70} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var42)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var43 string + templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.JoinStringErrs(w.Created.Format(time.RFC1123)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 224, Col: 44} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var43)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "View
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = pagination("/admin/websites", pageNum, total, 5).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = adminBase(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var37), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func AdminPanelWebsiteDetailView(title string, data CommonData, website models.Website, comments []models.GuestbookComment) 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_Var45 := templ.GetChildren(ctx) + if templ_7745c5c3_Var45 == nil { + templ_7745c5c3_Var45 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var46 := 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, 67, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = adminSidebar().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, c := range comments { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "
    AuthorCreatedEmailHomepageComment
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var47 string + templ_7745c5c3_Var47, templ_7745c5c3_Err = templ.JoinStringErrs(c.AuthorName) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 251, Col: 26} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var47)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var48 string + templ_7745c5c3_Var48, templ_7745c5c3_Err = templ.JoinStringErrs(c.Created.Format(time.RFC1123)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 252, Col: 44} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var48)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var49 string + templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinStringErrs(c.AuthorEmail) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 253, Col: 27} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var50 string + templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.JoinStringErrs(c.AuthorSite) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 254, Col: 26} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var50)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var51 string + templ_7745c5c3_Var51, templ_7745c5c3_Err = templ.JoinStringErrs(c.CommentText) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 255, Col: 27} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var51)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = adminBase(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var46), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/ui/views/common.templ b/ui/views/common.templ index 720c840..3fd38c5 100644 --- a/ui/views/common.templ +++ b/ui/views/common.templ @@ -3,6 +3,7 @@ package views import ( "fmt" "git.32bit.cafe/32bitcafe/guestbook/internal/models" + "math" "slices" "strconv" "strings" @@ -117,3 +118,35 @@ templ base(title string, data CommonData) { } + +templ pagination(baseUrl string, currentPage int64, recordsAmount int64, recordsPerPage int64) { + {{ totalPages := int64(math.Ceil(float64(recordsAmount) / float64(recordsPerPage))) }} + +} diff --git a/ui/views/common_templ.go b/ui/views/common_templ.go index 7546f26..8a7c729 100644 --- a/ui/views/common_templ.go +++ b/ui/views/common_templ.go @@ -11,6 +11,7 @@ import templruntime "github.com/a-h/templ/runtime" import ( "fmt" "git.32bit.cafe/32bitcafe/guestbook/internal/models" + "math" "slices" "strconv" "strings" @@ -117,7 +118,7 @@ func topNav(data CommonData) templ.Component { var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.CurrentUser.Username) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 61, Col: 41} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 62, Col: 41} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -146,7 +147,7 @@ func topNav(data CommonData) templ.Component { var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(hxHeaders) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 73, Col: 66} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 74, Col: 66} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -233,7 +234,7 @@ func base(title string, data CommonData) templ.Component { var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 98, Col: 17} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 99, Col: 17} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -246,7 +247,7 @@ func base(title string, data CommonData) templ.Component { var templ_7745c5c3_Var8 string templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(`{"includeIndicatorStyles":false}`) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 101, Col: 72} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 102, Col: 72} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { @@ -276,7 +277,7 @@ func base(title string, data CommonData) templ.Component { var templ_7745c5c3_Var9 string templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(data.Flash) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 112, Col: 43} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 113, Col: 43} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { @@ -307,4 +308,137 @@ func base(title string, data CommonData) templ.Component { }) } +func pagination(baseUrl string, currentPage int64, recordsAmount int64, recordsPerPage int64) 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_Var10 := templ.GetChildren(ctx) + if templ_7745c5c3_Var10 == nil { + templ_7745c5c3_Var10 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + totalPages := int64(math.Ceil(float64(recordsAmount) / float64(recordsPerPage))) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if currentPage > 1 { + url := fmt.Sprintf("%s?page=%d", baseUrl, currentPage-1) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "Prev") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "Prev") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for i := range totalPages { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + url := fmt.Sprintf("%s?page=%d", baseUrl, i+1) + if i+1 == currentPage { + var templ_7745c5c3_Var12 string + templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", i+1)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 137, Col: 28} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var14 string + templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", i+1)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 139, Col: 55} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if currentPage < totalPages { + url := fmt.Sprintf("%s?page=%d", baseUrl, currentPage+1) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "Next") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "Next") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + var _ = templruntime.GeneratedTemplate -- 2.30.2 From a236d9cea062bef64ccfe17377ae677adf89163c Mon Sep 17 00:00:00 2001 From: yequari Date: Sat, 3 Jan 2026 11:46:01 -0700 Subject: [PATCH 8/8] pagination in admin panel views --- cmd/web/handlers_admin.go | 77 ++++++--- internal/models/guestbookcomment.go | 43 +++++ internal/models/user.go | 22 +++ ui/views/admin.templ | 24 ++- ui/views/admin_templ.go | 244 ++++++++++++++++------------ ui/views/common.templ | 6 +- ui/views/common_templ.go | 6 +- 7 files changed, 279 insertions(+), 143 deletions(-) diff --git a/cmd/web/handlers_admin.go b/cmd/web/handlers_admin.go index ba47bf6..9b32fe8 100644 --- a/cmd/web/handlers_admin.go +++ b/cmd/web/handlers_admin.go @@ -39,13 +39,37 @@ func (app *application) getAdminPanelLanding(w http.ResponseWriter, r *http.Requ } func (app *application) getAdminPanelAllUsers(w http.ResponseWriter, r *http.Request) { - users, err := app.users.GetAll() + page := r.URL.Query().Get("page") + count := r.URL.Query().Get("count") + var pageNum int64 = 1 + var pageSize int64 = 5 + var err error + if page != "" { + pageNum, err = strconv.ParseInt(page, 10, 0) + if err != nil { + app.clientError(w, http.StatusBadRequest) + return + } + } + if count != "" { + pageSize, err = strconv.ParseInt(count, 10, 0) + if err != nil { + app.clientError(w, http.StatusBadRequest) + return + } + } + users, err := app.users.GetAllPage(pageNum, pageSize) + if err != nil { + app.serverError(w, r, err) + return + } + total, err := app.users.GetCount() if err != nil { app.serverError(w, r, err) return } data := app.newCommonData(r) - views.AdminPanelUsersView("All Users - Admin", data, users).Render(r.Context(), w) + views.AdminPanelUsersView("All Users - Admin", data, users, pageNum, pageSize, total).Render(r.Context(), w) } func (app *application) getAdminPanelUser(w http.ResponseWriter, r *http.Request) { @@ -209,40 +233,45 @@ func (app *application) getAdminPanelWebsites(w http.ResponseWriter, r *http.Req return } commonData := app.newCommonData(r) - views.AdminPanelAllWebsitesView("All websites", commonData, websites, pageNum, total).Render(r.Context(), w) + views.AdminPanelAllWebsitesView("All websites", commonData, websites, pageNum, pageSize, total).Render(r.Context(), w) } func (app *application) getAdminPanelWebsiteDetails(w http.ResponseWriter, r *http.Request) { slug := r.PathValue("id") - // page := r.URL.Query().Get("page") - // count := r.URL.Query().Get("count") - // var pageNum int64 = 1 - // var pageSize int64 = 5 - // var err error - // if page != "" { - // pageNum, err = strconv.ParseInt(page, 10, 0) - // if err != nil { - // app.clientError(w, http.StatusBadRequest) - // return - // } - // } - // if count != "" { - // pageSize, err = strconv.ParseInt(count, 10, 0) - // if err != nil { - // app.clientError(w, http.StatusBadRequest) - // return - // } - // } + page := r.URL.Query().Get("page") + count := r.URL.Query().Get("count") + var pageNum int64 = 1 + var pageSize int64 = 25 + var err error + if page != "" { + pageNum, err = strconv.ParseInt(page, 10, 0) + if err != nil { + app.clientError(w, http.StatusBadRequest) + return + } + } + if count != "" { + pageSize, err = strconv.ParseInt(count, 10, 0) + if err != nil { + app.clientError(w, http.StatusBadRequest) + return + } + } website, err := app.websites.Get(slugToShortId(slug)) if err != nil { app.serverError(w, r, err) return } - comments, err := app.guestbookComments.GetAll(website.Guestbook.ID) + total, err := app.guestbookComments.GetAllCount(website.Guestbook.ID) + if err != nil { + app.serverError(w, r, err) + return + } + comments, err := app.guestbookComments.GetAllPage(website.Guestbook.ID, pageNum, pageSize) if err != nil { app.serverError(w, r, err) return } commonData := app.newCommonData(r) - views.AdminPanelWebsiteDetailView(fmt.Sprintf("Admin - %s", website.Name), commonData, website, comments).Render(r.Context(), w) + views.AdminPanelWebsiteDetailView(fmt.Sprintf("Admin - %s", website.Name), commonData, website, comments, pageNum, pageSize, total).Render(r.Context(), w) } diff --git a/internal/models/guestbookcomment.go b/internal/models/guestbookcomment.go index 00ba1a4..a0431d1 100644 --- a/internal/models/guestbookcomment.go +++ b/internal/models/guestbookcomment.go @@ -38,6 +38,8 @@ type GuestbookCommentModelInterface interface { GetVisibleSerialized(guestbookId int64) ([]GuestbookCommentSerialized, error) GetDeleted(guestbookId int64) ([]GuestbookComment, error) GetAll(guestbookId int64) ([]GuestbookComment, error) + GetAllCount(guestbookId int64) (int64, error) + GetAllPage(guestbookId int64, pageNum int64, pageSize int64) ([]GuestbookComment, error) UpdateComment(comment *GuestbookComment) error } @@ -195,6 +197,47 @@ func (m *GuestbookCommentModel) GetAll(guestbookId int64) ([]GuestbookComment, e return comments, nil } +func (m *GuestbookCommentModel) GetAllCount(guestbookId int64) (int64, error) { + stmt := `SELECT COUNT(*) FROM guestbook_comments WHERE GuestbookId = ? AND Deleted IS NULL` + row := m.DB.QueryRow(stmt, guestbookId) + var result int64 + err := row.Scan(&result) + if err != nil { + return -1, err + } + if err = row.Err(); err != nil { + return -1, err + } + return result, nil +} + +func (m *GuestbookCommentModel) GetAllPage(guestbookId int64, pageNum int64, pageSize int64) ([]GuestbookComment, error) { + stmt := `SELECT Id, ShortId, GuestbookId, ParentId, AuthorName, AuthorEmail, AuthorSite, + CommentText, PageUrl, Created, IsPublished + FROM guestbook_comments + WHERE GuestbookId = ? AND Deleted IS NULL + ORDER BY Created DESC + LIMIT ? OFFSET ?` + rows, err := m.DB.Query(stmt, guestbookId, pageSize, (pageNum-1)*pageSize) + if err != nil { + return nil, err + } + var comments []GuestbookComment + for rows.Next() { + var c GuestbookComment + err = rows.Scan(&c.ID, &c.ShortId, &c.GuestbookId, &c.ParentId, &c.AuthorName, &c.AuthorEmail, &c.AuthorSite, + &c.CommentText, &c.PageUrl, &c.Created, &c.IsPublished) + if err != nil { + return nil, err + } + comments = append(comments, c) + } + if err = rows.Err(); err != nil { + return nil, err + } + return comments, nil +} + func (m *GuestbookCommentModel) UpdateComment(comment *GuestbookComment) error { stmt := `UPDATE guestbook_comments SET CommentText = ?, diff --git a/internal/models/user.go b/internal/models/user.go index a7b4e78..6643c62 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -61,6 +61,7 @@ type UserModelInterface interface { GetByEmail(email string) (int64, error) GetBySubject(subject string) (int64, error) GetAll() ([]User, error) + GetAllPage(pageNum, pageSize int64) ([]User, error) Authenticate(email, password string) (int64, error) Exists(id int64) (bool, error) UpdateUserSettings(userId int64, settings UserSettings) error @@ -328,6 +329,27 @@ func (m *UserModel) GetAll() ([]User, error) { return users, nil } +func (m *UserModel) GetAllPage(pageNum, pageSize int64) ([]User, error) { + stmt := `SELECT Id, ShortId, Username, Email, Created FROM users WHERE DELETED IS NULL LIMIT ? OFFSET ?` + rows, err := m.DB.Query(stmt, pageSize, (pageNum-1)*pageSize) + if err != nil { + return nil, err + } + var users []User + for rows.Next() { + var u User + err = rows.Scan(&u.ID, &u.ShortId, &u.Username, &u.Email, &u.Created) + if err != nil { + return nil, err + } + users = append(users, u) + } + if err = rows.Err(); err != nil { + return nil, err + } + return users, nil +} + func (m *UserModel) Authenticate(email, password string) (int64, error) { var id int64 var hashedPassword []byte diff --git a/ui/views/admin.templ b/ui/views/admin.templ index e94dad8..52c2b11 100644 --- a/ui/views/admin.templ +++ b/ui/views/admin.templ @@ -5,6 +5,7 @@ import ( "git.32bit.cafe/32bitcafe/guestbook/internal/forms" "git.32bit.cafe/32bitcafe/guestbook/internal/models" "slices" + "strings" "time" ) @@ -82,7 +83,7 @@ templ AdminPanelLandingView(title string, data CommonData, stats AdminStat) { } } -templ AdminPanelUsersView(title string, data CommonData, users []models.User) { +templ AdminPanelUsersView(title string, data CommonData, users []models.User, pageNum, pageSize, total int64) { @adminBase(title, data) {
    @adminSidebar() @@ -102,6 +103,7 @@ templ AdminPanelUsersView(title string, data CommonData, users []models.User) { } + @pagination("/admin/users", pageNum, total, pageSize)
    } @@ -203,7 +205,7 @@ templ AdminPanelUserMgmtEditForm(csrfToken string, form forms.AdminUserMgmtForm, } -templ AdminPanelAllWebsitesView(title string, data CommonData, websites []models.Website, pageNum int64, total int64) { +templ AdminPanelAllWebsitesView(title string, data CommonData, websites []models.Website, pageNum, pageCount, total int64) { @adminBase(title, data) {
    @adminSidebar() @@ -228,13 +230,23 @@ templ AdminPanelAllWebsitesView(title string, data CommonData, websites []models } - @pagination("/admin/websites", pageNum, total, 5) + @pagination("/admin/websites", pageNum, total, pageCount)
    } } -templ AdminPanelWebsiteDetailView(title string, data CommonData, website models.Website, comments []models.GuestbookComment) { +func truncateComment(s string, n int) string { + words := strings.Fields(s) + if len(words) < n { + return s + } + truncated := words[:n] + truncated = append(truncated, "...") + return strings.Join(truncated, " ") +} + +templ AdminPanelWebsiteDetailView(title string, data CommonData, website models.Website, comments []models.GuestbookComment, pageNum, pageAmount, total int64) { @adminBase(title, data) {
    @adminSidebar() @@ -252,11 +264,13 @@ templ AdminPanelWebsiteDetailView(title string, data CommonData, website models. { c.Created.Format(time.RFC1123) } { c.AuthorEmail } { c.AuthorSite } - { c.CommentText } + { truncateComment(c.CommentText, 15) } } + {{ url := fmt.Sprintf("/admin/websites/%s", shortIdToSlug(website.ShortId)) }} + @pagination(url, pageNum, total, pageAmount)
    } diff --git a/ui/views/admin_templ.go b/ui/views/admin_templ.go index a175b15..36617c9 100644 --- a/ui/views/admin_templ.go +++ b/ui/views/admin_templ.go @@ -13,6 +13,7 @@ import ( "git.32bit.cafe/32bitcafe/guestbook/internal/forms" "git.32bit.cafe/32bitcafe/guestbook/internal/models" "slices" + "strings" "time" ) @@ -50,7 +51,7 @@ func adminBase(title string, data CommonData) templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 21, Col: 17} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 22, Col: 17} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -63,7 +64,7 @@ func adminBase(title string, data CommonData) templ.Component { var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(`{"includeIndicatorStyles":false}`) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 24, Col: 72} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 25, Col: 72} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -170,7 +171,7 @@ func AdminPanelLandingView(title string, data CommonData, stats AdminStat) templ var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 63, Col: 16} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 64, Col: 16} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -183,7 +184,7 @@ func AdminPanelLandingView(title string, data CommonData, stats AdminStat) templ var templ_7745c5c3_Var8 string templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", stats.UserCount)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 69, Col: 45} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 70, Col: 45} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { @@ -196,7 +197,7 @@ func AdminPanelLandingView(title string, data CommonData, stats AdminStat) templ var templ_7745c5c3_Var9 string templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", stats.WebsiteCount)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 73, Col: 48} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 74, Col: 48} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { @@ -209,7 +210,7 @@ func AdminPanelLandingView(title string, data CommonData, stats AdminStat) templ var templ_7745c5c3_Var10 string templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", stats.CommentCount)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 77, Col: 48} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 78, Col: 48} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { @@ -229,7 +230,7 @@ func AdminPanelLandingView(title string, data CommonData, stats AdminStat) templ }) } -func AdminPanelUsersView(title string, data CommonData, users []models.User) templ.Component { +func AdminPanelUsersView(title string, data CommonData, users []models.User, pageNum, pageSize, total int64) 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 { @@ -296,7 +297,7 @@ func AdminPanelUsersView(title string, data CommonData, users []models.User) tem var templ_7745c5c3_Var14 string templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(u.Username) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 98, Col: 51} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 99, Col: 51} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { @@ -309,7 +310,7 @@ func AdminPanelUsersView(title string, data CommonData, users []models.User) tem var templ_7745c5c3_Var15 string templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(u.Created.Format(time.RFC3339)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 99, Col: 44} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 100, Col: 44} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) if templ_7745c5c3_Err != nil { @@ -322,7 +323,7 @@ func AdminPanelUsersView(title string, data CommonData, users []models.User) tem var templ_7745c5c3_Var16 string templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(u.Email) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 100, Col: 21} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 101, Col: 21} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) if templ_7745c5c3_Err != nil { @@ -333,7 +334,15 @@ func AdminPanelUsersView(title string, data CommonData, users []models.User) tem return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = pagination("/admin/users", pageNum, total, pageSize).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -368,82 +377,82 @@ func AdminPanelUserMgmtDetail(csrfToken string, user models.User) templ.Componen templ_7745c5c3_Var17 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "

    User Info

    Username

    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\">

    User Info

    Username

    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var19 string templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(user.Username) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 118, Col: 23} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 120, Col: 23} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "

    Email

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

    Email

    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var20 string templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(user.Email) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 122, Col: 20} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 124, Col: 20} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "

    Joined

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

    Joined

    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var21 string templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(user.Created.Format(time.RFC3339)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 126, Col: 43} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 128, Col: 43} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "

    Groups

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

    Groups

      ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } for _, g := range user.Groups { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "
    • ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "
    • ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var22 string templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s", getGroupName(g))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 133, Col: 46} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 135, Col: 46} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "
    • ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "

    Actions

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

    Actions

    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -451,80 +460,80 @@ func AdminPanelUserMgmtDetail(csrfToken string, user models.User) templ.Componen putBanUrl := fmt.Sprintf("/admin/users/%s/ban", shortIdToSlug(user.ShortId)) putUnbanUrl := fmt.Sprintf("/admin/users/%s/unban", shortIdToSlug(user.ShortId)) deleteUrl := fmt.Sprintf("/admin/users/%s", shortIdToSlug(user.ShortId)) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "\" hx-target=\"#user-info\">Edit ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if user.ID != 1 { if user.Banned.IsZero() { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "\" hx-confirm=\"Are you sure you want to ban this user?\" hx-target=\"#user-info\" class=\"danger\">Ban") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "\" hx-confirm=\"Are you sure you want to unban this user?\" hx-target=\"#user-info\">Unban") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "\" hx-confirm=\"Are you sure you want to delete this user? This is irreversible\" class=\"danger\">Delete") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -565,7 +574,7 @@ func AdminPanelUserMgmtView(title string, data CommonData, user models.User) tem }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -577,7 +586,7 @@ func AdminPanelUserMgmtView(title string, data CommonData, user models.User) tem if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -612,112 +621,112 @@ func AdminPanelUserMgmtEditForm(csrfToken string, form forms.AdminUserMgmtForm, templ_7745c5c3_Var29 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "

    User Info

    Username

    User Info

    Username
    Email
    Email
    Joined

    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "\" required>

    Joined

    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var33 string templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(user.Created.Format(time.RFC3339)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 182, Col: 43} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 184, Col: 43} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "

    Groups

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

    Groups

    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } isAdmin := slices.Contains(user.Groups, models.AdminGroup) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "

    Actions

    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, ">

    Actions

    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } putFormUrl := fmt.Sprintf("/admin/users/%s/edit", shortIdToSlug(user.ShortId)) getDetailUrl := fmt.Sprintf("/admin/users/%s/detail", shortIdToSlug(user.ShortId)) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "\" hx-target=\"#user-info\">Cancel") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -725,7 +734,7 @@ func AdminPanelUserMgmtEditForm(csrfToken string, form forms.AdminUserMgmtForm, }) } -func AdminPanelAllWebsitesView(title string, data CommonData, websites []models.Website, pageNum int64, total int64) templ.Component { +func AdminPanelAllWebsitesView(title string, data CommonData, websites []models.Website, pageNum, pageCount, total int64) 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 { @@ -758,7 +767,7 @@ func AdminPanelAllWebsitesView(title string, data CommonData, websites []models. }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -766,17 +775,17 @@ func AdminPanelAllWebsitesView(title string, data CommonData, websites []models. if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "
    Site NameOwnerURLCreatedGuestbook
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } for _, w := range websites { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } detailUrl := fmt.Sprintf("/admin/websites/%s", shortIdToSlug(w.ShortId)) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } gbUrl := fmt.Sprintf("/websites/%s/guestbook", shortIdToSlug(w.ShortId)) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "\">View") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "
    Site NameOwnerURLCreatedGuestbook
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var39 string templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(w.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 221, Col: 57} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 223, Col: 57} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var40 string templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs(w.AuthorName) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 222, Col: 26} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 224, Col: 26} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var42 string templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.JoinStringErrs(w.Url.String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 223, Col: 70} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 225, Col: 70} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var42)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var43 string templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.JoinStringErrs(w.Created.Format(time.RFC1123)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 224, Col: 44} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 226, Col: 44} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var43)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "View
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = pagination("/admin/websites", pageNum, total, 5).Render(ctx, templ_7745c5c3_Buffer) + templ_7745c5c3_Err = pagination("/admin/websites", pageNum, total, pageCount).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -887,7 +896,17 @@ func AdminPanelAllWebsitesView(title string, data CommonData, websites []models. }) } -func AdminPanelWebsiteDetailView(title string, data CommonData, website models.Website, comments []models.GuestbookComment) templ.Component { +func truncateComment(s string, n int) string { + words := strings.Fields(s) + if len(words) < n { + return s + } + truncated := words[:n] + truncated = append(truncated, "...") + return strings.Join(truncated, " ") +} + +func AdminPanelWebsiteDetailView(title string, data CommonData, website models.Website, comments []models.GuestbookComment, pageNum, pageAmount, total int64) 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 { @@ -920,7 +939,7 @@ func AdminPanelWebsiteDetailView(title string, data CommonData, website models.W }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -928,47 +947,34 @@ func AdminPanelWebsiteDetailView(title string, data CommonData, website models.W if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "
    AuthorCreatedEmailHomepageComment
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } for _, c := range comments { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "
    AuthorCreatedEmailHomepageComment
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var47 string templ_7745c5c3_Var47, templ_7745c5c3_Err = templ.JoinStringErrs(c.AuthorName) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 251, Col: 26} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 263, Col: 26} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var47)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var48 string - templ_7745c5c3_Var48, templ_7745c5c3_Err = templ.JoinStringErrs(c.Created.Format(time.RFC1123)) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 252, Col: 44} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var48)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var49 string - templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinStringErrs(c.AuthorEmail) + var templ_7745c5c3_Var48 string + templ_7745c5c3_Var48, templ_7745c5c3_Err = templ.JoinStringErrs(c.Created.Format(time.RFC1123)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 253, Col: 27} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 264, Col: 44} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var48)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -976,12 +982,12 @@ func AdminPanelWebsiteDetailView(title string, data CommonData, website models.W if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var50 string - templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.JoinStringErrs(c.AuthorSite) + var templ_7745c5c3_Var49 string + templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinStringErrs(c.AuthorEmail) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 254, Col: 26} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 265, Col: 27} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var50)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -989,21 +995,43 @@ func AdminPanelWebsiteDetailView(title string, data CommonData, website models.W if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var51 string - templ_7745c5c3_Var51, templ_7745c5c3_Err = templ.JoinStringErrs(c.CommentText) + var templ_7745c5c3_Var50 string + templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.JoinStringErrs(c.AuthorSite) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 255, Col: 27} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 266, Col: 26} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var50)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var51 string + templ_7745c5c3_Var51, templ_7745c5c3_Err = templ.JoinStringErrs(truncateComment(c.CommentText, 15)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/admin.templ`, Line: 267, Col: 48} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var51)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + url := fmt.Sprintf("/admin/websites/%s", shortIdToSlug(website.ShortId)) + templ_7745c5c3_Err = pagination(url, pageNum, total, pageAmount).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/ui/views/common.templ b/ui/views/common.templ index 3fd38c5..c0463fb 100644 --- a/ui/views/common.templ +++ b/ui/views/common.templ @@ -124,7 +124,7 @@ templ pagination(baseUrl string, currentPage int64, recordsAmount int64, records