Files
wxapp-guantong/backend/cmd/smilefirst/main.go
T
2026-09-22 22:51:19 +08:00

168 lines
4.2 KiB
Go

package main
import (
"context"
"database/sql"
"fmt"
"os"
"strings"
"github.com/rogeecn/wxapp-kouqiang-guahao/backend/internal/config"
"github.com/rogeecn/wxapp-kouqiang-guahao/backend/internal/httpapi"
"github.com/rogeecn/wxapp-kouqiang-guahao/backend/internal/service"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
_ "modernc.org/sqlite"
)
func main() {
if err := rootCommand().Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func rootCommand() *cobra.Command {
cfg := config.Load()
return newRootCommand(&cfg, commandHandlers{})
}
type commandRunner func(cmd *cobra.Command, args []string) error
type commandHandlers struct {
serve commandRunner
migrate commandRunner
seed commandRunner
}
func newRootCommand(cfg *config.Config, handlers commandHandlers) *cobra.Command {
if handlers.serve == nil {
handlers.serve = func(cmd *cobra.Command, args []string) error {
if err := cfg.ValidateAdminAuth(); err != nil {
return err
}
log := newLogger(cfg.LogLevel)
database, svc, err := openService(cmd.Context(), *cfg, log)
if err != nil {
return err
}
defer database.Close()
app := httpapi.New(*cfg, svc, log)
log.WithField("addr", cfg.Addr).Info("starting api")
return app.Listen(cfg.Addr)
}
}
if handlers.migrate == nil {
handlers.migrate = func(cmd *cobra.Command, args []string) error {
log := newLogger(cfg.LogLevel)
database, err := openDatabase(*cfg)
if err != nil {
return err
}
defer database.Close()
if err := service.ApplySchema(cmd.Context(), database, cfg.SchemaPath); err != nil {
return err
}
log.Info("schema applied")
return nil
}
}
if handlers.seed == nil {
handlers.seed = func(cmd *cobra.Command, args []string) error {
log := newLogger(cfg.LogLevel)
database, svc, err := openService(cmd.Context(), *cfg, log)
if err != nil {
return err
}
defer database.Close()
if err := svc.Seed(cmd.Context()); err != nil {
return err
}
log.Info("seed complete")
return nil
}
}
var root = &cobra.Command{
Use: "smilefirst",
Short: "Smile First oral-care booking service",
}
root.PersistentFlags().StringVar(&cfg.Addr, "addr", cfg.Addr, "HTTP listen address")
root.PersistentFlags().StringVar(&cfg.DatabasePath, "db", cfg.DatabasePath, "SQLite database path")
root.PersistentFlags().StringVar(&cfg.SchemaPath, "schema", cfg.SchemaPath, "schema SQL path")
root.PersistentFlags().StringVar(&cfg.LogLevel, "log-level", cfg.LogLevel, "log level")
root.AddCommand(&cobra.Command{
Use: "serve",
Short: "Run the HTTP API server",
RunE: handlers.serve,
})
root.AddCommand(&cobra.Command{
Use: "migrate",
Short: "Apply SQLite schema",
RunE: handlers.migrate,
})
root.AddCommand(&cobra.Command{
Use: "seed",
Short: "Insert default categories, projects, and schedule slots",
RunE: handlers.seed,
})
root.RunE = handlers.serve
return root
}
func openService(ctx context.Context, cfg config.Config, log *logrus.Logger) (*sql.DB, *service.Service, error) {
database, err := openDatabase(cfg)
if err != nil {
return nil, nil, err
}
if err := service.ApplySchema(ctx, database, cfg.SchemaPath); err != nil {
database.Close()
return nil, nil, err
}
svc := service.New(database, log)
if err := svc.Seed(ctx); err != nil {
database.Close()
return nil, nil, err
}
return database, svc, nil
}
func openDatabase(cfg config.Config) (*sql.DB, error) {
if err := service.EnsureDatabaseDir(cfg.DatabasePath); err != nil {
return nil, err
}
dsn := cfg.DatabasePath
if dsn != ":memory:" && !strings.HasPrefix(dsn, "file:") {
dsn = "file:" + dsn
}
if dsn != ":memory:" {
sep := "?"
if strings.Contains(dsn, "?") {
sep = "&"
}
dsn += sep + "_pragma=foreign_keys(1)&_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)&_pragma=synchronous(NORMAL)"
}
database, err := sql.Open("sqlite", dsn)
if err != nil {
return nil, err
}
database.SetMaxOpenConns(1)
if err := database.Ping(); err != nil {
database.Close()
return nil, err
}
return database, nil
}
func newLogger(level string) *logrus.Logger {
log := logrus.New()
log.SetFormatter(&logrus.JSONFormatter{})
parsed, err := logrus.ParseLevel(level)
if err != nil {
parsed = logrus.InfoLevel
}
log.SetLevel(parsed)
return log
}