ai slop ah

This commit is contained in:
2026-08-22 17:27:55 +02:00
parent 6a5bb1d699
commit dc124d0d77
64 changed files with 7308 additions and 2448 deletions
+3 -3
View File
@@ -29,7 +29,7 @@ func main() {
if err := seed(ctx, database); err != nil {
log.Fatal(err)
}
log.Printf("seeded %s with the Northline Studio demo gallery", demoEmail)
log.Printf("seeded %s with the Noah Bianchi demo gallery", demoEmail)
}
func seed(ctx context.Context, database *sql.DB) error {
@@ -41,7 +41,7 @@ func seed(ctx context.Context, database *sql.DB) error {
INSERT INTO users (id, email, password_hash, name)
VALUES ($1, $2, $3, $4)
ON CONFLICT (email) DO UPDATE SET password_hash = EXCLUDED.password_hash, name = EXCLUDED.name, updated_at = CURRENT_TIMESTAMP
`, demoUserID, demoEmail, string(passwordHash), "Northline Studio"); err != nil {
`, demoUserID, demoEmail, string(passwordHash), "Noah Bianchi"); err != nil {
return fmt.Errorf("seed demo user: %w", err)
}
@@ -62,7 +62,7 @@ func seed(ctx context.Context, database *sql.DB) error {
updated_at = CURRENT_TIMESTAMP
`, demoGalleryID, demoUserID, "emma-james-wedding", "Emma & James", "Emma & James", "An early summer wedding, held close to the water and the people who make it home.", demoCoverID,
`{"mode":"light","layout":"editorial","accent":"#ad695b","font":"serif"}`,
`{"studioName":"Northline Studio","tagline":"Photographs for keeps.","websiteUrl":"https://example.com","instagramUrl":"https://instagram.com"}`); err != nil {
`{"studioName":"Noah Bianchi","tagline":"Photographs for keeps.","websiteUrl":"https://example.com","instagramUrl":"https://instagram.com"}`); err != nil {
return fmt.Errorf("seed demo gallery: %w", err)
}
+55 -36
View File
@@ -10,17 +10,29 @@ import (
"syscall"
"time"
_ "github.com/example/sndit/backend/docs"
"github.com/example/sndit/backend/internal/auth"
"github.com/example/sndit/backend/internal/config"
"github.com/example/sndit/backend/internal/db"
devtools "github.com/example/sndit/backend/internal/dev"
"github.com/example/sndit/backend/internal/downloads"
"github.com/example/sndit/backend/internal/galleries"
"github.com/example/sndit/backend/internal/gifts"
"github.com/example/sndit/backend/internal/media"
"github.com/example/sndit/backend/internal/storage"
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
)
// @title Noah Bianchi Studio API
// @version 1.0
// @description REST API for photographer galleries and client delivery.
// @schemes http https
// @BasePath /
// @securityDefinitions.apikey studioSession
// @in header
// @name Cookie
// @description Use the studio_session cookie as studio_session=<value>.
func main() {
cfg := config.Load()
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
@@ -73,23 +85,25 @@ func main() {
CookieSecure: cfg.CookieSecure,
})
mux := http.NewServeMux()
mux.HandleFunc("GET /health", health)
authHandler.RegisterRoutes(mux)
galleryHandler.RegisterProtectedRoutes(mux, authService.Require)
mediaHandler.RegisterRoutes(mux, authService.Require)
galleryHandler.RegisterPublicRoutes(mux)
downloadHandler.RegisterRoutes(mux)
devHandler.RegisterRoutes(mux, authService.Require)
// Keep the original public gift endpoint available while the gallery product
// uses /api/public/galleries/:slug.
legacyGifts := gifts.NewHandler(gifts.NewService(gifts.NewRepository(database)))
mux.Handle("/api/gifts/", legacyGifts.Routes())
router := gin.New()
router.Use(gin.Recovery(), withCORS(cfg.CORSOrigin), withLogging())
router.GET("/health", health)
router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler,
ginSwagger.DocExpansion("none"),
ginSwagger.PersistAuthorization(true),
))
require := authService.Require()
authHandler.RegisterRoutes(router)
authHandler.RegisterProtectedRoutes(router, require)
galleryHandler.RegisterProtectedRoutes(router, require)
mediaHandler.RegisterRoutes(router, require)
galleryHandler.RegisterPublicRoutes(router)
downloadHandler.RegisterRoutes(router)
devHandler.RegisterRoutes(router, require)
server := &http.Server{
Addr: ":" + cfg.Port,
Handler: withCORS(withLogging(mux), cfg.CORSOrigin),
Handler: router,
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
@@ -111,28 +125,33 @@ func main() {
}
}
func health(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`{"status":"ok"}` + "\n"))
// health godoc
// @Summary Check API health
// @Tags system
// @Produce json
// @Success 200 {object} map[string]string
// @Router /health [get]
func health(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"status": "ok"})
}
func withCORS(next http.Handler, allowedOrigin string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
origin := r.Header.Get("Origin")
func withCORS(allowedOrigin string) gin.HandlerFunc {
return func(c *gin.Context) {
origin := c.GetHeader("Origin")
if origin != "" && originAllowed(origin, allowedOrigin) {
w.Header().Set("Access-Control-Allow-Origin", origin)
w.Header().Set("Access-Control-Allow-Credentials", "true")
w.Header().Set("Vary", "Origin")
c.Header("Access-Control-Allow-Origin", origin)
c.Header("Access-Control-Allow-Credentials", "true")
c.Header("Vary", "Origin")
}
if r.Method == http.MethodOptions {
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PATCH, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
w.WriteHeader(http.StatusNoContent)
if c.Request.Method == http.MethodOptions {
c.Header("Access-Control-Allow-Methods", "GET, POST, PATCH, DELETE, OPTIONS")
c.Header("Access-Control-Allow-Headers", "Content-Type")
c.Status(http.StatusNoContent)
c.Abort()
return
}
next.ServeHTTP(w, r)
})
c.Next()
}
}
func originAllowed(origin, configured string) bool {
@@ -144,10 +163,10 @@ func originAllowed(origin, configured string) bool {
return false
}
func withLogging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
func withLogging() gin.HandlerFunc {
return func(c *gin.Context) {
started := time.Now()
next.ServeHTTP(w, r)
log.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(started).Round(time.Millisecond))
})
c.Next()
log.Printf("%s %s %d %s", c.Request.Method, c.Request.URL.Path, c.Writer.Status(), time.Since(started).Round(time.Millisecond))
}
}