Codex first iteration

This commit is contained in:
2026-07-12 02:56:51 +08:00
parent 16c626fbd7
commit 7cb409f5a6
20 changed files with 1771 additions and 0 deletions
+63
View File
@@ -0,0 +1,63 @@
package app
import (
"context"
"errors"
"log/slog"
"net/http"
"os"
"os/signal"
"syscall"
"git.michelsen.id/phill/preface-tools/internal/auth"
"git.michelsen.id/phill/preface-tools/internal/httpserver"
"git.michelsen.id/phill/preface-tools/internal/tools"
"git.michelsen.id/phill/preface-tools/internal/tools/comicanimator"
)
func Run() error {
cfg, err := LoadConfigFromEnv()
if err != nil {
return err
}
comicCfg, err := comicanimator.LoadConfigFromEnv()
if err != nil {
return err
}
var handler slog.Handler = slog.NewTextHandler(os.Stdout, nil)
if cfg.LogFormat == "json" {
handler = slog.NewJSONHandler(os.Stdout, nil)
}
log := slog.New(handler)
a := auth.New(cfg.StudentPIN, cfg.InstructorPIN, cfg.SessionSecret, cfg.SessionDuration, cfg.Environment == "production")
comic, err := comicanimator.New(comicCfg, log, a)
if err != nil {
return err
}
registry := tools.NewRegistry()
if err = registry.Register(comic); err != nil {
return err
}
srv := &http.Server{Addr: cfg.Address, Handler: httpserver.New(a, registry, log).Handler(), ReadHeaderTimeout: cfg.ReadHeaderTimeout, ReadTimeout: cfg.ReadTimeout, WriteTimeout: cfg.WriteTimeout, IdleTimeout: cfg.IdleTimeout}
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
workerDone := make(chan error, 1)
go func() { workerDone <- comic.Run(ctx) }()
serverDone := make(chan error, 1)
go func() { log.Info("server starting", "address", cfg.Address); serverDone <- srv.ListenAndServe() }()
select {
case err = <-serverDone:
if !errors.Is(err, http.ErrServerClosed) {
stop()
return err
}
case <-ctx.Done():
}
shutdownCtx, cancel := context.WithTimeout(context.Background(), cfg.ShutdownTimeout)
defer cancel()
if err := srv.Shutdown(shutdownCtx); err != nil {
return err
}
stop()
return <-workerDone
}
+42
View File
@@ -0,0 +1,42 @@
package app
import (
"errors"
"os"
"time"
)
type Config struct {
Environment, Address, PublicBaseURL, StudentPIN, InstructorPIN, SessionSecret, LogLevel, LogFormat string
SessionDuration, ReadHeaderTimeout, ReadTimeout, WriteTimeout, IdleTimeout, ShutdownTimeout time.Duration
}
func LoadConfigFromEnv() (Config, error) {
c := Config{Environment: get("APP_ENV", "development"), Address: get("HTTP_ADDRESS", ":8080"), PublicBaseURL: os.Getenv("PUBLIC_BASE_URL"), StudentPIN: os.Getenv("STUDENT_PIN"), InstructorPIN: os.Getenv("INSTRUCTOR_PIN"), SessionSecret: os.Getenv("SESSION_SIGNING_SECRET"), LogLevel: get("LOG_LEVEL", "info"), LogFormat: get("LOG_FORMAT", "text")}
var err error
c.SessionDuration, err = duration("SESSION_DURATION", "4h")
if err != nil {
return c, err
}
values := []struct {
target *time.Duration
key, fallback string
}{{&c.ReadHeaderTimeout, "HTTP_READ_HEADER_TIMEOUT", "5s"}, {&c.ReadTimeout, "HTTP_READ_TIMEOUT", "30s"}, {&c.WriteTimeout, "HTTP_WRITE_TIMEOUT", "30s"}, {&c.IdleTimeout, "HTTP_IDLE_TIMEOUT", "60s"}, {&c.ShutdownTimeout, "HTTP_SHUTDOWN_TIMEOUT", "10s"}}
for _, value := range values {
*value.target, err = duration(value.key, value.fallback)
if err != nil {
return c, err
}
}
if c.StudentPIN == "" || c.InstructorPIN == "" || len(c.SessionSecret) < 32 || c.PublicBaseURL == "" {
return c, errors.New("STUDENT_PIN, INSTRUCTOR_PIN, PUBLIC_BASE_URL, and a 32+ character SESSION_SIGNING_SECRET are required")
}
return c, nil
}
func get(k, d string) string {
if v := os.Getenv(k); v != "" {
return v
}
return d
}
func duration(k, d string) (time.Duration, error) { return time.ParseDuration(get(k, d)) }