diff --git a/cmd/web/handlers_admin.go b/cmd/web/handlers_admin.go new file mode 100644 index 0000000..9b32fe8 --- /dev/null +++ b/cmd/web/handlers_admin.go @@ -0,0 +1,277 @@ +package main + +import ( + "errors" + "fmt" + "net/http" + "strconv" + "time" + + "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" +) + +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, stats).Render(r.Context(), w) +} + +func (app *application) getAdminPanelAllUsers(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 + } + } + 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, pageNum, pageSize, total).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) 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 + } + commonData := app.newCommonData(r) + views.AdminPanelUserMgmtDetail(commonData.CSRFToken, 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 + } + commonData := app.newCommonData(r) + views.AdminPanelUserMgmtDetail(commonData.CSRFToken, 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)) + 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 + } + 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) { + 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, 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 = 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 + } + 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, pageNum, pageSize, total).Render(r.Context(), w) +} 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..932e778 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,17 @@ 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)) + 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)) + 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/forms/forms.go b/internal/forms/forms.go index ebb7b25..85fddad 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"` @@ -25,9 +28,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 +53,17 @@ type WebsiteSettingsForm struct { WidgetsEnabled string `schema:"gb_remote"` validator.Validator `schema:"-"` } + +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 { + Name string `schema:"username"` + Email string `schema:"email"` + Password string `schema:"password"` + validator.Validator `schema:"-"` +} diff --git a/internal/models/guestbookcomment.go b/internal/models/guestbookcomment.go index eb94614..a0431d1 100644 --- a/internal/models/guestbookcomment.go +++ b/internal/models/guestbookcomment.go @@ -33,10 +33,13 @@ 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) GetAll(guestbookId int64) ([]GuestbookComment, error) + GetAllCount(guestbookId int64) (int64, error) + GetAllPage(guestbookId int64, pageNum int64, pageSize int64) ([]GuestbookComment, error) UpdateComment(comment *GuestbookComment) error } @@ -74,6 +77,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 @@ -180,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/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..c865e0f 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, } @@ -26,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 } @@ -120,3 +144,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/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 13eacc5..6643c62 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 { @@ -48,15 +56,24 @@ 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) 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 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 + UnbanUser(userId int64) error + UpdateUser(u User) error + UpdatePassword(userId int64, password string) error + Delete(userId int64) error } func (m *UserModel) InitializeSettingsMap() error { @@ -96,8 +113,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 +124,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 +148,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 +171,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 +195,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 +205,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 +223,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 +234,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 @@ -214,15 +249,30 @@ 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 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 +282,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 +293,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 @@ -268,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 @@ -400,3 +482,115 @@ 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 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 + } + 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) 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) + 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) + 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/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/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..df0ca97 --- /dev/null +++ b/migrations/000008_add_user_groups.up.sql @@ -0,0 +1,20 @@ +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 + UNIQUE(UserId, GroupId) +); 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/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 diff --git a/ui/static/css/style.css b/ui/static/css/style.css index 78fdb6d..7872d7a 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%; @@ -708,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 new file mode 100644 index 0000000..52c2b11 --- /dev/null +++ b/ui/views/admin.templ @@ -0,0 +1,277 @@ +package views + +import ( + "fmt" + "git.32bit.cafe/32bitcafe/guestbook/internal/forms" + "git.32bit.cafe/32bitcafe/guestbook/internal/models" + "slices" + "strings" + "time" +) + +type AdminStat struct { + WebsiteCount int64 + UserCount int64 + CommentCount int64 +} + +templ adminBase(title string, data CommonData) { + + + + { title } - webweav.ing + + + + + + + + + +
+ Back to webweav.ing +
+
+ { children... } +
+ @commonFooter() + + +} + +templ adminSidebar() { + +} + +templ AdminPanelLandingView(title string, data CommonData, stats AdminStat) { + @adminBase(title, data) { +
+ @adminSidebar() +
+
+

{ title }

+

Welcome to the admin panel

+
+
+
+

Users

+

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

+
+
+

Websites

+

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

+
+
+

Comments

+

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

+
+
+
+
+ } +} + +templ AdminPanelUsersView(title string, data CommonData, users []models.User, pageNum, pageSize, total int64) { + @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 }
+
+ @pagination("/admin/users", pageNum, total, pageSize) +
+
+ } +} + +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(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

+ {{ 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)) }} + + +
+
+
+} + +templ AdminPanelAllWebsitesView(title string, data CommonData, websites []models.Website, pageNum, pageCount, total int64) { + @adminBase(title, data) { +
+ @adminSidebar() +
+
+ + + + + + + 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, pageCount) +
+
+ } +} + +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() +
+
+ + + + + + + for _, c := range comments { + + + + + + + + } +
AuthorCreatedEmailHomepageComment
{ c.AuthorName }{ c.Created.Format(time.RFC1123) }{ c.AuthorEmail }{ c.AuthorSite }{ 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 new file mode 100644 index 0000000..36617c9 --- /dev/null +++ b/ui/views/admin_templ.go @@ -0,0 +1,1048 @@ +// 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/forms" + "git.32bit.cafe/32bitcafe/guestbook/internal/models" + "slices" + "strings" + "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 + 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: 22, 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, 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 { + 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: 64, 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

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: 70, 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: 74, 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: 78, 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 + } + 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, 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 { + 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_Var11 := templ.GetChildren(ctx) + if templ_7745c5c3_Var11 == nil { + templ_7745c5c3_Var11 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + 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 { + 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, 13, "
") + 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, 14, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, u := range users { + 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, 16, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "
UsernameJoinedEmail
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + 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: 99, Col: 51} + } + _, 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, 18, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + 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: 100, Col: 44} + } + _, 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, 19, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + 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: 101, Col: 21} + } + _, 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, "
") + 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 + } + return nil + }) + 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 + } + return nil + }) +} + +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 { + 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_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, 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: 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, 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: 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, 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: 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, 27, "

Groups

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, g := range user.Groups { + 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: 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, 29, "
  • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "

Actions

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + 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)) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, " ") + 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, 33, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "
") + 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_Var27 := templ.GetChildren(ctx) + if templ_7745c5c3_Var27 == nil { + templ_7745c5c3_Var27 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + 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 { + 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, 40, "
") + 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 = 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, 41, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + 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 + } + 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_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, 42, "

User Info

Username
Email
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: 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, 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, 47, "

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, 51, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +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 { + 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, 54, "
") + 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, 55, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, w := range websites { + 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, 57, "") + 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, 64, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "
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: 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, 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: 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, 60, "") + 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: 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, 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: 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, 63, "View
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + 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, 67, "
") + 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 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 { + 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, 68, "
") + 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, 69, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, c := range comments { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "
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: 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, 71, "") + 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: 264, 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, 72, "") + 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: 265, 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, 73, "") + 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: 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, 75, "
") + 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 + } + 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 + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/ui/views/common.templ b/ui/views/common.templ index 666b370..c0463fb 100644 --- a/ui/views/common.templ +++ b/ui/views/common.templ @@ -1,9 +1,13 @@ 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" + "math" + "slices" + "strconv" + "strings" +) type CommonData struct { CurrentYear int @@ -33,6 +37,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 +68,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 { @@ -101,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 6391240..82a92c6 100644 --- a/ui/views/common_templ.go +++ b/ui/views/common_templ.go @@ -8,10 +8,14 @@ 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" + "math" + "slices" + "strconv" + "strings" +) type CommonData struct { CurrentYear int @@ -41,6 +45,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 +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: 48, 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 { @@ -116,36 +130,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 +198,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 +227,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: 99, 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 +265,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: 113, 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 +292,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 +300,140 @@ 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 + } + return nil + }) +} + +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&count=%d", baseUrl, currentPage-1, recordsPerPage) + 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&count=%d", baseUrl, i+1, recordsPerPage) + 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&count=%d", baseUrl, currentPage+1, recordsPerPage) + 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 } 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 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 {