mirror of
https://github.com/Dvorinka/Productier.git
synced 2026-06-03 20:13:01 +00:00
first commit
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user