package rest import ( "context" "database/sql" "errors" "log/slog" "net/http" "time" "goAuth/internal/auth" "goAuth/internal/invite" "goAuth/internal/jitsi" "goAuth/internal/middleware" "goAuth/internal/migration" "goAuth/internal/user" _ "github.com/mattn/go-sqlite3" ) type Handler struct { cfg Config db *sql.DB users *user.Service invites *invite.Service sessions *auth.Service jitsi *jitsi.Service } func New(cfg Config) (*Handler, error) { if cfg.Cookies.SessionName == "" { cfg.Cookies.SessionName = DefaultSessionCookie } if cfg.Cookies.CsrfName == "" { cfg.Cookies.CsrfName = DefaultCsrfCookie } if cfg.Sessions.TokenSize <= 0 { cfg.Sessions.TokenSize = DefaultTokenSize } db, err := openDatabase(cfg.DatabasePath) if err != nil { return nil, err } if err := migrate(context.Background(), db); err != nil { db.Close() return nil, err } handler, err := wire(cfg, db) if err != nil { db.Close() return nil, err } if err := seed(context.Background(), handler.users); err != nil { handler.Close() return nil, err } return handler, nil } func openDatabase(path string) (*sql.DB, error) { db, err := sql.Open("sqlite3", path+"?_foreign_keys=on") if err != nil { slog.Error("open database", slog.String("op", "rest.openDatabase"), slog.Any("error", err)) return nil, err } db.SetMaxOpenConns(1) return db, nil } func wire(cfg Config, db *sql.DB) (*Handler, error) { userRepo, err := user.NewRepository(db) if err != nil { return nil, err } inviteRepo, err := invite.NewRepository(db) if err != nil { return nil, err } sessionRepo, err := auth.NewRepository(db) if err != nil { return nil, err } jitsiRepo, err := jitsi.NewRepository(db) if err != nil { return nil, err } userService, err := user.NewService(userRepo) if err != nil { return nil, err } inviteService, err := invite.NewService(inviteRepo, 0, 0) if err != nil { return nil, err } sessionService, err := auth.NewService( sessionRepo, userService, inviteService, cfg.Sessions.ShortDuration, cfg.Sessions.LongDuration, cfg.Sessions.TokenSize, ) if err != nil { return nil, err } jitsiService, err := jitsi.NewService( jitsiRepo, cfg.Jitsi.Secret, cfg.Jitsi.AppId, cfg.Jitsi.AppName, cfg.Jitsi.Domain, ) if err != nil { return nil, err } return &Handler{ cfg: cfg, db: db, users: userService, invites: inviteService, sessions: sessionService, jitsi: jitsiService, }, nil } func migrate(ctx context.Context, db *sql.DB) error { migrator := migration.NewMigrator(db) migrator.Register("user", user.Migrations()...) migrator.Register("auth", auth.Migrations()...) migrator.Register("invite", invite.Migrations()...) migrator.Register("jitsi", jitsi.Migrations()...) return migrator.Run(ctx) } func (h *Handler) Close() error { if h.db == nil { return nil } return h.db.Close() } func (h *Handler) Router() http.Handler { mux := http.NewServeMux() h.registerRoutes(mux) return mux } func (h *Handler) Serve(ctx context.Context) error { server := &http.Server{ Addr: h.cfg.Addr, Handler: h.Router(), ReadHeaderTimeout: 10 * time.Second, ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 60 * time.Second, } serveErr := make(chan error, 1) go func() { slog.Info("rest server listening", slog.String("addr", h.cfg.Addr)) if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { serveErr <- err return } serveErr <- nil }() select { case err := <-serveErr: return err case <-ctx.Done(): shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() return server.Shutdown(shutdownCtx) } } func (h *Handler) sessionMiddleware() middleware.Middleware { return h.sessions.NewMiddleware(h.cfg.Cookies.SessionName) }