6.0 KB
users.go
package controllers
import (
"fmt"
"net/http"
"github.com/readysite/readysite/pkg/application"
"github.com/readysite/readysite/website/internal/access"
"github.com/readysite/readysite/website/internal/helpers"
"github.com/readysite/readysite/website/models"
)
// Users returns the users controller.
func Users() (string, *UsersController) {
return "users", &UsersController{}
}
// UsersController handles admin user management.
type UsersController struct {
application.BaseController
}
// Setup registers routes.
func (c *UsersController) Setup(app *application.App) {
c.BaseController.Setup(app)
}
// Handle implements Controller interface with value receiver for request isolation.
func (c UsersController) Handle(r *http.Request) application.Controller {
c.Request = r
return &c
}
// User returns the current user from path parameter.
func (c *UsersController) User() *models.User {
if c.Request == nil {
return nil
}
id := c.PathValue("id")
if id == "" {
return nil
}
user, err := models.Users.Get(id)
if err != nil {
return nil
}
return user
}
// Users returns all users.
func (c *UsersController) Users() []*models.User {
users, _ := models.Users.Search("ORDER BY CreatedAt DESC")
return users
}
// UserPagination returns pagination state for users list.
func (c *UsersController) UserPagination() *helpers.Pagination {
total := models.Users.Count("")
return helpers.NewPagination(c.Request, total)
}
// PaginatedUsers returns paginated users.
func (c *UsersController) PaginatedUsers() []*models.User {
p := c.UserPagination()
users, _ := models.Users.Search("ORDER BY CreatedAt DESC LIMIT ? OFFSET ?", p.PageSize, p.Offset)
return users
}
// IsNewUser returns true if creating a new user.
func (c *UsersController) IsNewUser() bool {
if c.Request == nil {
return true
}
id := c.PathValue("id")
return id == ""
}
// Roles returns available user roles.
func (c *UsersController) Roles() []string {
return []string{"admin", "user", "viewer"}
}
// Create creates a new user.
func (c *UsersController) Create(w http.ResponseWriter, r *http.Request) {
email := r.FormValue("email")
name := r.FormValue("name")
password := r.FormValue("password")
role := r.FormValue("role")
if email == "" {
c.RenderError(w, r, fmt.Errorf("Email is required"))
return
}
if password == "" {
c.RenderError(w, r, fmt.Errorf("Password is required"))
return
}
if len(password) < 8 {
c.RenderError(w, r, fmt.Errorf("Password must be at least 8 characters"))
return
}
// Default role to "user" if not specified or invalid
if role != "admin" && role != "user" && role != "viewer" {
role = "user"
}
// Check if email already exists
existing, _ := models.Users.First("WHERE Email = ?", email)
if existing != nil {
c.RenderError(w, r, fmt.Errorf("A user with this email already exists"))
return
}
// Hash password
hash, err := access.HashPassword(password)
if err != nil {
c.RenderError(w, r, fmt.Errorf("Failed to hash password"))
return
}
user := &models.User{
Email: email,
Name: name,
PasswordHash: hash,
Role: role,
}
id, err := models.Users.Insert(user)
if err != nil {
c.RenderError(w, r, fmt.Errorf("Failed to create user: %w", err))
return
}
// Audit log
adminUser := access.GetUserFromJWT(r)
adminID := ""
if adminUser != nil {
adminID = adminUser.ID
}
helpers.AuditCreate(r, adminID, helpers.ResourceUser, id, email)
w.Header().Set("HX-Trigger", `{"showToast":"User created"}`)
c.Refresh(w, r)
}
// Update updates an existing user.
func (c *UsersController) Update(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
email := r.FormValue("email")
name := r.FormValue("name")
password := r.FormValue("password")
role := r.FormValue("role")
user, err := models.Users.Get(id)
if err != nil {
c.RenderError(w, r, fmt.Errorf("User not found"))
return
}
if email == "" {
c.RenderError(w, r, fmt.Errorf("Email is required"))
return
}
// Check if email is taken by another user
existing, _ := models.Users.First("WHERE Email = ? AND ID != ?", email, id)
if existing != nil {
c.RenderError(w, r, fmt.Errorf("A user with this email already exists"))
return
}
// Validate role
if role != "admin" && role != "user" && role != "viewer" {
role = user.Role // Keep existing role if invalid
}
user.Email = email
user.Name = name
user.Role = role
// Only update password if provided
if password != "" {
if len(password) < 8 {
c.RenderError(w, r, fmt.Errorf("Password must be at least 8 characters"))
return
}
hash, err := access.HashPassword(password)
if err != nil {
c.RenderError(w, r, fmt.Errorf("Failed to hash password"))
return
}
user.PasswordHash = hash
}
if err := models.Users.Update(user); err != nil {
c.RenderError(w, r, fmt.Errorf("Failed to update user: %w", err))
return
}
// Audit log
adminUser := access.GetUserFromJWT(r)
adminID := ""
if adminUser != nil {
adminID = adminUser.ID
}
helpers.AuditUpdate(r, adminID, helpers.ResourceUser, id, email, nil)
w.Header().Set("HX-Trigger", `{"showToast":"User saved"}`)
c.Refresh(w, r)
}
// Delete deletes a user.
func (c *UsersController) Delete(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
user, err := models.Users.Get(id)
if err != nil {
c.RenderError(w, r, fmt.Errorf("User not found"))
return
}
// Prevent deleting yourself
adminUser := access.GetUserFromJWT(r)
if adminUser != nil && adminUser.ID == id {
c.RenderError(w, r, fmt.Errorf("You cannot delete your own account"))
return
}
// Prevent deleting the last admin
if user.Role == "admin" {
adminCount := models.Users.Count("WHERE Role = 'admin'")
if adminCount <= 1 {
c.RenderError(w, r, fmt.Errorf("Cannot delete the last admin user"))
return
}
}
if err := models.Users.Delete(user); err != nil {
c.RenderError(w, r, fmt.Errorf("Failed to delete user: %w", err))
return
}
// Audit log
adminID := ""
if adminUser != nil {
adminID = adminUser.ID
}
helpers.AuditDelete(r, adminID, helpers.ResourceUser, id, user.Email)
c.Redirect(w, r, "/admin/users")
}