init
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cfg := config.Load()
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||
defer stop()
|
||||
|
||||
database, err := db.New(ctx, cfg.DBDriver, cfg.DatabaseDSN())
|
||||
if err != nil {
|
||||
log.Fatalf("database unavailable: %v", err)
|
||||
}
|
||||
defer database.Close()
|
||||
|
||||
objectStorage, err := storage.NewMinIO(storage.Config{
|
||||
Endpoint: cfg.StorageEndpoint,
|
||||
AccessKey: cfg.StorageAccessKey,
|
||||
SecretKey: cfg.StorageSecretKey,
|
||||
Bucket: cfg.StorageBucket,
|
||||
UseSSL: cfg.StorageUseSSL,
|
||||
CORSOrigins: cfg.CORSOrigin,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("storage unavailable: %v", err)
|
||||
}
|
||||
if err := objectStorage.EnsureBucket(ctx); err != nil {
|
||||
log.Fatalf("storage unavailable: %v", err)
|
||||
}
|
||||
|
||||
authRepository := auth.NewRepository(database)
|
||||
authService, err := auth.NewService(authRepository, cfg.SessionSecret, cfg.CookieSecure)
|
||||
if err != nil {
|
||||
log.Fatalf("authentication unavailable: %v", err)
|
||||
}
|
||||
galleryRepository := galleries.NewRepository(database)
|
||||
mediaRepository := media.NewRepository(database)
|
||||
mediaProcessor := media.NewProcessor(mediaRepository, objectStorage, 2)
|
||||
defer mediaProcessor.Close()
|
||||
downloadRepository := downloads.NewRepository(database)
|
||||
downloadService := downloads.NewService(downloadRepository, mediaRepository, objectStorage, 1)
|
||||
defer downloadService.Close()
|
||||
|
||||
authHandler := auth.NewHandler(authService)
|
||||
galleryHandler := galleries.NewHandler(galleryRepository, mediaRepository, objectStorage, authService)
|
||||
mediaHandler := media.NewHandler(mediaRepository, objectStorage, mediaProcessor, authService)
|
||||
downloadHandler := downloads.NewHandler(galleryRepository, mediaRepository, objectStorage, authService, downloadService)
|
||||
devHandler := devtools.NewHandler(database, objectStorage, authService, devtools.Config{
|
||||
DBDriver: cfg.DBDriver,
|
||||
StorageEndpoint: cfg.StorageEndpoint,
|
||||
StorageBucket: cfg.StorageBucket,
|
||||
StorageUseSSL: cfg.StorageUseSSL,
|
||||
CORSOrigins: cfg.CORSOrigin,
|
||||
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())
|
||||
|
||||
server := &http.Server{
|
||||
Addr: ":" + cfg.Port,
|
||||
Handler: withCORS(withLogging(mux), cfg.CORSOrigin),
|
||||
ReadHeaderTimeout: 5 * time.Second,
|
||||
ReadTimeout: 10 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
IdleTimeout: 60 * time.Second,
|
||||
}
|
||||
|
||||
go func() {
|
||||
log.Printf("API listening on http://localhost%s", server.Addr)
|
||||
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
log.Fatalf("server failed: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
<-ctx.Done()
|
||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
if err := server.Shutdown(shutdownCtx); err != nil {
|
||||
log.Printf("server shutdown failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
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"))
|
||||
}
|
||||
|
||||
func withCORS(next http.Handler, allowedOrigin string) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
origin := r.Header.Get("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")
|
||||
}
|
||||
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)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func originAllowed(origin, configured string) bool {
|
||||
for _, allowed := range strings.Split(configured, ",") {
|
||||
if strings.TrimSpace(allowed) == "*" || strings.TrimSpace(allowed) == origin {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func withLogging(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
started := time.Now()
|
||||
next.ServeHTTP(w, r)
|
||||
log.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(started).Round(time.Millisecond))
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user