package main import ( "context" "embed" "fmt" "io/fs" "log/slog" "net/http" "os" "time" "atlas9.dev/c/core/dbi" "atlas9.dev/c/demo/api" "atlas9.dev/c/demo/boot" "atlas9.dev/c/demo/boot/bootdb" "atlas9.dev/c/demo/lib/access" "atlas9.dev/c/demo/tasks" "atlas9.dev/c/mail/mail_ses" ) //go:embed frontend/* var frontendFiles embed.FS func main() { err := run() if err != nil { slog.Error(err.Error()) os.Exit(1) } } func run() error { // Load config config, err := boot.LoadConfig("config.toml") if err != nil { return err } slog.SetDefault(slog.New(boot.LogHandler(os.Stdout))) // DB connection and migration db, err := bootdb.Database(config.Database.Path) if err != nil { return err } defer db.Close() mailer, err := mail_ses.NewSender(context.Background(), config.Mail) if err != nil { return err } srv := boot.NewServer(config.Server.BaseURL, db, mailer, config) // TODO frontend files are going through the session and access middleware. // they must not, but they also shouldn't be blindly exposed either. // perhaps a signed session token would help. // Serve frontend files, from disk in dev mode so UI changes don't // require a server rebuild. var frontend fs.FS if config.Frontend.Dir != "" { slog.Info("serving frontend from disk", "dir", config.Frontend.Dir) frontend = os.DirFS(config.Frontend.Dir) } else { frontend, _ = fs.Sub(frontendFiles, "frontend") } boot.FrontendRoutes(srv.Mux, frontend) ctx := context.Background() newWorker := func(route, table string) *tasks.Worker { return &tasks.Worker{ DB: db, Route: route, Consumer: func(tx dbi.DBI) tasks.Consumer { return tasks.NewSqliteConsumer(tx, table) }, Handler: srv.Mux, // TODO revisit worker access Access: access.Admin(), Interval: 5 * time.Second, LeaseDuration: 30 * time.Second, MaxAttempts: 5, BatchSize: 1, } } go newWorker(api.Path_Account_Task_SendEmailVerification, "email_verification_tasks").Run(ctx) go newWorker(api.Path_Account_Task_SendPasswordReset, "password_reset_tasks").Run(ctx) go newWorker(api.Path_TenantInvitations_Task_SendTenantInvitation, "tenant_invitation_tasks").Run(ctx) go newWorker(api.Path_Domain_Verify, "domain_verification_tasks").Run(ctx) // Periodic cleanup of stale throttle buckets go func() { for { time.Sleep(30 * time.Minute) srv.ThrottleBucket.Cleanup(time.Hour) } }() // Serve addr := fmt.Sprintf(":%d", config.Server.Port) slog.Info("Server starting", "addr", addr) return http.ListenAndServe(addr, srv) }