Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion cmd/build/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ func main() {
// Stats for static build
staticStats := layouts.AdminStats{
UnreadContacts: 0,
PageImages: 18,
EditableContent: int64(sitecontent.TotalFieldCount()),
}

Expand Down
11 changes: 0 additions & 11 deletions internal/database/models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,3 @@ func FromSqlcContactSubmission(item sqlc.ContactSubmission) ContactSubmission {
CreatedAt: createdAt,
}
}

// PageImage represents a page image for templates
type PageImage struct {
ID int64 `json:"id"`
PageName string `json:"page_name"`
ImageKey string `json:"image_key"`
ImageUrl string `json:"image_url"`
Label string `json:"label"`
AltText string `json:"alt_text"`
SortOrder int64 `json:"sort_order"`
}
42 changes: 0 additions & 42 deletions internal/handler/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,6 @@ func (h *Handler) getAdminStats(ctx echo.Context) layouts.AdminStats {
stats.UnreadContacts = unread
}

// Count page images
images, err := h.db.Queries.ListAllPageImages(c)
if err == nil {
stats.PageImages = int64(len(images))
}

stats.EditableContent = int64(sitecontent.TotalFieldCount())

return stats
Expand Down Expand Up @@ -161,39 +155,3 @@ func (h *Handler) AdminUsers(c echo.Context) error {

return pages.AdminUsers(users, totalCount, stats, clerkEnabled).Render(ctx, c.Response().Writer)
}

// AdminImages renders the page images management page
func (h *Handler) AdminImages(c echo.Context) error {
ctx := c.Request().Context()
stats := h.getAdminStats(c)

// Get all page images grouped by page
sqlcImages, err := h.db.Queries.ListAllPageImages(ctx)
if err != nil {
slog.Error("failed to list page images", "error", err)
sqlcImages = nil
}

// Group images by page name
imagesByPage := make(map[string][]models.PageImage)
pageOrder := []string{} // Track order of pages

for _, img := range sqlcImages {
pageImg := models.PageImage{
ID: img.ID,
PageName: img.PageName,
ImageKey: img.ImageKey,
ImageUrl: img.ImageUrl,
Label: img.Label,
AltText: img.AltText,
SortOrder: img.SortOrder.Int64,
}

if _, exists := imagesByPage[img.PageName]; !exists {
pageOrder = append(pageOrder, img.PageName)
}
imagesByPage[img.PageName] = append(imagesByPage[img.PageName], pageImg)
}

return pages.AdminImages(imagesByPage, pageOrder, stats).Render(ctx, c.Response().Writer)
}
165 changes: 0 additions & 165 deletions internal/handler/admin_api.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,9 @@
package handler

import (
"crypto/rand"
"database/sql"
"encoding/hex"
"fmt"
"io"
"log/slog"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"

"rowetech/internal/database/models"
"rowetech/internal/database/sqlc"
Expand Down Expand Up @@ -93,93 +85,6 @@ func (h *Handler) APIDeleteContact(c echo.Context) error {
return c.String(http.StatusOK, "")
}

// APIUpdateImageURL updates a page image URL
func (h *Handler) APIUpdateImageURL(c echo.Context) error {
ctx := c.Request().Context()

idStr := c.Param("id")
imageID, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
return c.String(http.StatusBadRequest, "Invalid ID")
}

var body struct {
URL string `json:"url"`
}
if err := c.Bind(&body); err != nil {
return c.String(http.StatusBadRequest, "Invalid request body")
}

err = h.db.Queries.UpdatePageImageURL(ctx, sqlc.UpdatePageImageURLParams{
ImageUrl: body.URL,
ID: imageID,
})
if err != nil {
slog.Error("failed to update image URL", "error", err)
return c.String(http.StatusInternalServerError, "Failed to update image")
}

return c.JSON(http.StatusOK, map[string]string{"status": "ok"})
}

// APIUpdateImageAlt updates a page image alt text
func (h *Handler) APIUpdateImageAlt(c echo.Context) error {
ctx := c.Request().Context()

idStr := c.Param("id")
imageID, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
return c.String(http.StatusBadRequest, "Invalid ID")
}

var body struct {
AltText string `json:"alt_text"`
}
if err := c.Bind(&body); err != nil {
return c.String(http.StatusBadRequest, "Invalid request body")
}

err = h.db.Queries.UpdatePageImageAlt(ctx, sqlc.UpdatePageImageAltParams{
AltText: body.AltText,
ID: imageID,
})
if err != nil {
slog.Error("failed to update image alt text", "error", err)
return c.String(http.StatusInternalServerError, "Failed to update image")
}

return c.JSON(http.StatusOK, map[string]string{"status": "ok"})
}

// APIUpdateImageSortOrder updates a page image sort order
func (h *Handler) APIUpdateImageSortOrder(c echo.Context) error {
ctx := c.Request().Context()

idStr := c.Param("id")
imageID, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
return c.String(http.StatusBadRequest, "Invalid ID")
}

var body struct {
SortOrder int `json:"sort_order"`
}
if err := c.Bind(&body); err != nil {
return c.String(http.StatusBadRequest, "Invalid request body")
}

err = h.db.Queries.UpdatePageImageSortOrder(ctx, sqlc.UpdatePageImageSortOrderParams{
SortOrder: sql.NullInt64{Int64: int64(body.SortOrder), Valid: true},
ID: imageID,
})
if err != nil {
slog.Error("failed to update image sort order", "error", err)
return c.String(http.StatusInternalServerError, "Failed to update image")
}

return c.JSON(http.StatusOK, map[string]string{"status": "ok"})
}

// APIUpdateSetting updates a site setting
func (h *Handler) APIUpdateSetting(c echo.Context) error {
ctx := c.Request().Context()
Expand Down Expand Up @@ -208,76 +113,6 @@ func (h *Handler) APIUpdateSetting(c echo.Context) error {
return pages.SettingRowPartial(key, value).Render(ctx, c.Response().Writer)
}

// APIUploadPageImage handles image upload for page images
func (h *Handler) APIUploadPageImage(c echo.Context) error {
ctx := c.Request().Context()

idStr := c.Param("id")
imageID, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid ID"})
}

file, err := c.FormFile("image")
if err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "No image file provided"})
}

// Validate file type
ext := strings.ToLower(filepath.Ext(file.Filename))
allowedExts := map[string]bool{".jpg": true, ".jpeg": true, ".png": true, ".gif": true, ".webp": true}
if !allowedExts[ext] {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid file type"})
}

if file.Size > 10*1024*1024 {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "File too large (max 10MB)"})
}

// Generate unique filename
randomBytes := make([]byte, 16)
if _, err := rand.Read(randomBytes); err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to process upload"})
}
filename := hex.EncodeToString(randomBytes) + ext

uploadDir := "static/uploads/pages"
if err := os.MkdirAll(uploadDir, 0755); err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to process upload"})
}

src, err := file.Open()
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to process upload"})
}
defer func() { _ = src.Close() }()

dstPath := filepath.Join(uploadDir, filename)
dst, err := os.Create(dstPath)
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to save file"})
}
defer func() { _ = dst.Close() }()

if _, err := io.Copy(dst, src); err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to save file"})
}

imageURL := fmt.Sprintf("/static/uploads/pages/%s", filename)

// Update the page image record
err = h.db.Queries.UpdatePageImageUpload(ctx, sqlc.UpdatePageImageUploadParams{
ImageUrl: imageURL,
ID: imageID,
})
if err != nil {
slog.Error("failed to update page image", "error", err)
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to update image record"})
}

return c.JSON(http.StatusOK, map[string]string{"url": imageURL})
}

// APIIsAdmin checks if the current user is an admin
func (h *Handler) APIIsAdmin(c echo.Context) error {
ctx := c.Request().Context()
Expand Down
9 changes: 0 additions & 9 deletions internal/handler/handler.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package handler

import (
"net/http"

"rowetech/internal/config"
"rowetech/internal/database"
"rowetech/internal/middleware"
Expand Down Expand Up @@ -45,9 +43,6 @@ func (h *Handler) RegisterRoutes(e *echo.Echo) {
e.GET("/sign-in", h.SignIn)
e.GET("/sign-up", h.SignUp)
e.GET("/unauthorized", h.Unauthorized)
e.GET("/admin/images", func(c echo.Context) error {
return c.Redirect(http.StatusFound, "/admin")
})

// Dev-only auth bypass (loopback-only; compiled out of production builds).
middleware.RegisterDevAuthRoutes(e, h.cfg)
Expand All @@ -67,10 +62,6 @@ func (h *Handler) RegisterRoutes(e *echo.Echo) {
admin.POST("/api/contacts/:id/read", h.APIMarkContactRead)
admin.POST("/api/contacts/:id/unread", h.APIMarkContactUnread)
admin.DELETE("/api/contacts/:id", h.APIDeleteContact)
admin.PUT("/api/images/:id/url", h.APIUpdateImageURL)
admin.PUT("/api/images/:id/alt", h.APIUpdateImageAlt)
admin.PUT("/api/images/:id/sort", h.APIUpdateImageSortOrder)
admin.POST("/api/upload/page-image/:id", h.APIUploadPageImage)
admin.POST("/api/settings", h.APIUpdateSetting)

// Public API routes
Expand Down
4 changes: 0 additions & 4 deletions internal/middleware/clerk.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,6 @@ var clerkJWKS jwksCache
func RequireAdminAccess(cfg *config.Config) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if c.Request().URL.Path == "/admin/images" {
return c.Redirect(http.StatusFound, "/admin")
}

// Dev-only impersonation: under `-tags dev`, a loopback request with
// a valid dev session cookie is treated as the named admin without
// Clerk. Compiled out of production builds (see auth_dev_stub.go).
Expand Down
1 change: 0 additions & 1 deletion templates/layouts/admin.templ
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
// AdminStats holds the dashboard statistics
type AdminStats struct {
UnreadContacts int64
PageImages int64
EditableContent int64
}

Expand Down
Loading