Add photo galleries API and frontend for public and admin viewing.
Introduces the Go photo-api service, nginx/systemd deploy wiring, and Next.js gallery/lightbox pages so event photos can be managed and browsed. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.azzamo.net/Michilis/Spanglish/photo-api/internal/store"
|
||||
)
|
||||
|
||||
// listPublicGalleries is the public index: only visibility='public'
|
||||
// galleries ever appear here.
|
||||
func (s *Server) listPublicGalleries(w http.ResponseWriter, r *http.Request) {
|
||||
galleries, err := s.db.ListPublicGalleries(r.Context())
|
||||
if err != nil {
|
||||
writeStoreError(w, err, "")
|
||||
return
|
||||
}
|
||||
out := make([]galleryJSON, 0, len(galleries))
|
||||
for _, g := range galleries {
|
||||
out = append(out, s.galleryToJSON(r.Context(), g, "", false))
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{"galleries": out})
|
||||
}
|
||||
|
||||
// getPublicGallery serves a single gallery to any authorized viewer.
|
||||
// ?token= carries the share token for link/ticket modes.
|
||||
func (s *Server) getPublicGallery(w http.ResponseWriter, r *http.Request) {
|
||||
g, err := s.db.GetGalleryBySlug(r.Context(), r.PathValue("slug"))
|
||||
if err != nil {
|
||||
writeStoreError(w, err, "Gallery not found")
|
||||
return
|
||||
}
|
||||
s.respondGalleryView(w, r, g)
|
||||
}
|
||||
|
||||
// getEventGallery serves the newest gallery linked to an event, backing the
|
||||
// /events/{slug}/gallery public page. Same access rules as by-slug.
|
||||
func (s *Server) getEventGallery(w http.ResponseWriter, r *http.Request) {
|
||||
ev, err := s.db.GetEventSummaryBySlug(r.Context(), r.PathValue("eventSlug"))
|
||||
if err != nil {
|
||||
writeStoreError(w, err, "Gallery not found")
|
||||
return
|
||||
}
|
||||
g, err := s.db.GetGalleryByEventID(r.Context(), ev.ID)
|
||||
if err != nil {
|
||||
writeStoreError(w, err, "Gallery not found")
|
||||
return
|
||||
}
|
||||
s.respondGalleryView(w, r, g)
|
||||
}
|
||||
|
||||
func (s *Server) respondGalleryView(w http.ResponseWriter, r *http.Request, g store.Gallery) {
|
||||
user := s.optionalUser(r)
|
||||
token := r.URL.Query().Get("token")
|
||||
if denial := s.authorize(r, g, user, token); denial != nil {
|
||||
writeError(w, denial.status, denial.msg)
|
||||
return
|
||||
}
|
||||
|
||||
// Only pass the token through to file URLs when it was the granting
|
||||
// credential, so it doesn't leak into public/ticket responses.
|
||||
urlToken := ""
|
||||
if token != "" && token == g.ShareToken {
|
||||
urlToken = token
|
||||
}
|
||||
|
||||
photos, err := s.db.ListPhotos(r.Context(), g.ID, true)
|
||||
if err != nil {
|
||||
writeStoreError(w, err, "")
|
||||
return
|
||||
}
|
||||
out := make([]photoJSON, 0, len(photos))
|
||||
for _, p := range photos {
|
||||
out = append(out, s.photoToJSON(p, urlToken, false))
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
"gallery": s.galleryToJSON(r.Context(), g, urlToken, false),
|
||||
"photos": out,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user