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>
80 lines
2.3 KiB
Go
80 lines
2.3 KiB
Go
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,
|
|
})
|
|
}
|