mirror of
https://github.com/Dvorinka/Productier.git
synced 2026-06-03 20:13:01 +00:00
149 lines
3.6 KiB
Go
149 lines
3.6 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
"productier/apps/backend/internal/authsession"
|
|
"productier/apps/backend/internal/filestorage"
|
|
"productier/apps/backend/internal/httpapi"
|
|
"productier/apps/backend/internal/mailruntime"
|
|
"productier/apps/backend/internal/store"
|
|
)
|
|
|
|
type App struct {
|
|
server *httpapi.Server
|
|
port string
|
|
shutdownTimeout time.Duration
|
|
stopMailRuntime context.CancelFunc
|
|
}
|
|
|
|
func New(logger *zap.Logger) (*App, error) {
|
|
runtimeConfig, err := loadRuntimeConfig()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var dataStore store.Store
|
|
databaseURL := os.Getenv("DATABASE_URL")
|
|
if databaseURL != "" {
|
|
persistentStore, err := store.NewPostgresStore(databaseURL, runtimeConfig.mode)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataStore = persistentStore
|
|
} else {
|
|
if err := validateStoreRuntimeMode(runtimeConfig.mode, inMemoryStoreAllowed()); err != nil {
|
|
return nil, err
|
|
}
|
|
dataStore = store.NewSeededState(runtimeConfig.mode)
|
|
}
|
|
|
|
mailService, err := mailruntime.New(dataStore, logger, runtimeConfig.mailSecret)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
files, err := filestorage.NewFromEnv()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
probeCtx, cancelProbe := context.WithTimeout(context.Background(), 3*time.Second)
|
|
defer cancelProbe()
|
|
if err := files.Probe(probeCtx); err != nil {
|
|
return nil, fmt.Errorf("file storage startup probe failed: %w", err)
|
|
}
|
|
|
|
mailRuntimeCtx, stopMailRuntime := context.WithCancel(context.Background())
|
|
mailService.Start(mailRuntimeCtx)
|
|
|
|
return &App{
|
|
server: httpapi.NewServer(
|
|
dataStore,
|
|
authsession.NewClient(runtimeConfig.authServiceURL),
|
|
mailService,
|
|
files,
|
|
runtimeConfig.mode,
|
|
runtimeConfig.corsAllowOrigins,
|
|
runtimeConfig.metricsAuthToken,
|
|
logger,
|
|
),
|
|
port: runtimeConfig.apiPort,
|
|
shutdownTimeout: runtimeConfig.shutdownTimeout,
|
|
stopMailRuntime: stopMailRuntime,
|
|
}, nil
|
|
}
|
|
|
|
func (a *App) Run() error {
|
|
return a.RunContext(context.Background())
|
|
}
|
|
|
|
func (a *App) RunContext(ctx context.Context) error {
|
|
httpServer := &http.Server{
|
|
Addr: fmt.Sprintf(":%s", a.port),
|
|
Handler: a.server.Engine(),
|
|
ReadHeaderTimeout: 10 * time.Second,
|
|
ReadTimeout: 30 * time.Second,
|
|
WriteTimeout: 30 * time.Second,
|
|
IdleTimeout: 60 * time.Second,
|
|
}
|
|
|
|
serverErr := make(chan error, 1)
|
|
go func() {
|
|
err := httpServer.ListenAndServe()
|
|
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
|
serverErr <- err
|
|
}
|
|
close(serverErr)
|
|
}()
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
if a.stopMailRuntime != nil {
|
|
a.stopMailRuntime()
|
|
}
|
|
shutdownCtx, cancel := context.WithTimeout(context.Background(), a.shutdownTimeout)
|
|
defer cancel()
|
|
if err := httpServer.Shutdown(shutdownCtx); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
|
return fmt.Errorf("shutdown api server: %w", err)
|
|
}
|
|
if err, ok := <-serverErr; ok && err != nil {
|
|
return fmt.Errorf("run api server: %w", err)
|
|
}
|
|
return nil
|
|
case err, ok := <-serverErr:
|
|
if a.stopMailRuntime != nil {
|
|
a.stopMailRuntime()
|
|
}
|
|
if !ok || err == nil {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("run api server: %w", err)
|
|
}
|
|
}
|
|
|
|
func validateStoreRuntimeMode(mode string, allowInMemory bool) error {
|
|
if mode == "development" || allowInMemory {
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("DATABASE_URL is required when APP_ENV=%q (set ALLOW_INMEMORY_STORE=true only for temporary non-production testing)", mode)
|
|
}
|
|
|
|
func inMemoryStoreAllowed() bool {
|
|
raw := strings.TrimSpace(strings.ToLower(os.Getenv("ALLOW_INMEMORY_STORE")))
|
|
switch raw {
|
|
case "1", "true", "yes", "on":
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|