feat: bootstrap foundation application
This commit is contained in:
98
apps/server/internal/app/app.go
Normal file
98
apps/server/internal/app/app.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/tim/md-hub-secure/apps/server/internal/config"
|
||||
"github.com/tim/md-hub-secure/apps/server/internal/database"
|
||||
"github.com/tim/md-hub-secure/apps/server/internal/docs"
|
||||
"github.com/tim/md-hub-secure/apps/server/internal/httpserver"
|
||||
"github.com/tim/md-hub-secure/apps/server/internal/markdown"
|
||||
"github.com/tim/md-hub-secure/apps/server/internal/store"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
cfg config.Config
|
||||
logger *slog.Logger
|
||||
db database.DB
|
||||
server *http.Server
|
||||
}
|
||||
|
||||
func New(ctx context.Context, cfg config.Config, logger *slog.Logger) (*App, error) {
|
||||
if err := os.MkdirAll(cfg.Content.SourceDir, 0o755); err != nil {
|
||||
return nil, fmt.Errorf("create content source dir: %w", err)
|
||||
}
|
||||
|
||||
contentStore, err := store.New(cfg.Content.StoreDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create content store: %w", err)
|
||||
}
|
||||
|
||||
db, err := database.Open(ctx, cfg.Database)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open database: %w", err)
|
||||
}
|
||||
|
||||
if err := database.ApplyMigrations(ctx, db.SQL()); err != nil {
|
||||
return nil, fmt.Errorf("apply migrations: %w", err)
|
||||
}
|
||||
|
||||
renderer := markdown.NewRenderer()
|
||||
repo := docs.NewRepository(db.SQL())
|
||||
service := docs.NewService(cfg.Content.SourceDir, contentStore, renderer, repo, logger)
|
||||
if err := service.SyncSourceDir(ctx); err != nil {
|
||||
logger.Warn("initial content sync failed", "error", err)
|
||||
}
|
||||
|
||||
handler, err := httpserver.New(httpserver.Dependencies{
|
||||
Config: cfg,
|
||||
Logger: logger,
|
||||
Documents: service,
|
||||
Repository: repo,
|
||||
ContentStore: contentStore,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("build http handler: %w", err)
|
||||
}
|
||||
|
||||
server := &http.Server{
|
||||
Addr: cfg.Server.Addr,
|
||||
Handler: handler,
|
||||
ReadHeaderTimeout: 5 * time.Second,
|
||||
ReadTimeout: 15 * time.Second,
|
||||
WriteTimeout: 30 * time.Second,
|
||||
IdleTimeout: 60 * time.Second,
|
||||
}
|
||||
|
||||
return &App{
|
||||
cfg: cfg,
|
||||
logger: logger,
|
||||
db: db,
|
||||
server: server,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *App) Run(ctx context.Context) error {
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := a.server.Shutdown(shutdownCtx); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
a.logger.Error("shutdown server", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
a.logger.Info("starting server", "addr", a.cfg.Server.Addr)
|
||||
return a.server.ListenAndServe()
|
||||
}
|
||||
|
||||
func (a *App) Close() error {
|
||||
return a.db.Close()
|
||||
}
|
||||
Reference in New Issue
Block a user