168 lines
4.2 KiB
Go
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
|
|
}
|