Initial commit
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/rogeecn/wxapp-kouqiang-guahao/backend/internal/config"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func TestRootCommandDefaultsToServe(t *testing.T) {
|
||||
cfg := config.Config{
|
||||
Addr: ":9800",
|
||||
DatabasePath: ":memory:",
|
||||
SchemaPath: "migrations/schema.sql",
|
||||
LogLevel: "info",
|
||||
}
|
||||
var calls []string
|
||||
handlers := commandHandlers{
|
||||
serve: func(cmd *cobra.Command, args []string) error {
|
||||
calls = append(calls, "serve")
|
||||
return nil
|
||||
},
|
||||
migrate: func(cmd *cobra.Command, args []string) error {
|
||||
calls = append(calls, "migrate")
|
||||
return nil
|
||||
},
|
||||
seed: func(cmd *cobra.Command, args []string) error {
|
||||
calls = append(calls, "seed")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd := newRootCommand(&cfg, handlers)
|
||||
cmd.SetArgs([]string{"--addr", "127.0.0.1:18080"})
|
||||
|
||||
if err := cmd.Execute(); err != nil {
|
||||
t.Fatalf("execute root command: %v", err)
|
||||
}
|
||||
if len(calls) != 1 || calls[0] != "serve" {
|
||||
t.Fatalf("calls = %v, want [serve]", calls)
|
||||
}
|
||||
if cfg.Addr != "127.0.0.1:18080" {
|
||||
t.Fatalf("cfg.Addr = %q, want flag value", cfg.Addr)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user