$ git clone https://github.com/angvp/tango $ cd tango/examples/api-with-admin $ go run .
API with admin
A JSON API and the HTML admin over one set of models. authors is managed only in the admin; posts has the admin and a JSON API, and every post belongs to an author through a foreign key. It's the compact proof of tanGO's app composition: two apps, one registry, generated migrations.
Run it
git clone https://github.com/angvp/tango
cd tango/examples/api-with-admin
go run . -migrate
go run . -tango-admin-create=admin
go run .
-tango-admin-create prompts for a password. Open http://localhost:8000/admin/, log in, and add an author. Then use the API with that author's ID:
go test . runs the same flow against a fresh database.
The operator dashboard
Logging in to /admin/ lands on a dashboard: the database's reachability, dialect and migration state, and the last hour of request and job activity. Both come from sources main.go hands to admin.WithDashboard: a status function built from tango.Status (see dashboard.go), and an observability.Window that tango.WithRecorder feeds on every request. The activity is labelled "this instance only" and starts again from zero when the process restarts; use an external monitoring system for history or several instances. See the admin dashboard guide.
Configuration
TANGO_DB_DSN picks the database and defaults to sqlite://app.db.
The address is TANGO_ADDR, else the PORT hosting platforms set, else :8000.
Ctrl-C or SIGTERM shuts the server down gracefully.
// Command api-with-admin is a JSON API and the admin over one set of// models: authors (admin only) and posts (admin and a JSON API), linked by// a foreign key.packagemainimport("context""database/sql""fmt""os""os/signal""syscall""github.com/angvp/tango""github.com/angvp/tango/admin""github.com/angvp/tango/db""github.com/angvp/tango/observability""api-with-admin/apps/authors""api-with-admin/apps/posts""api-with-admin/migrations"_"modernc.org/sqlite")funcmain(){iferr:=run();err!=nil{fmt.Fprintln(os.Stderr,err)os.Exit(1)}}funcrun()error{dsn,err:=tango.LoadDBConfigFromEnv()iferr!=nil{returnerr}sqlDB,err:=sql.Open(dsn.Driver,dsn.Source)iferr!=nil{returnerr}defersqlDB.Close()store:=db.NewStore(sqlDB,dsn.Dialect)// The dashboard's activity numbers come from this Window, which also// receives every framework metric; Multi would fan out to an exporter too.window:=observability.NewWindow()config:=appConfig(store,sqlDB,dsn.Dialect,window)ifhandled,err:=admin.HandleCLI(context.Background(),store,os.Args[1:],os.Stdin,os.Stdout,os.Stderr);handled||err!=nil{returnerr}ifhandled,err:=tango.DispatchFlags(config,sqlDB,dsn.Dialect,migrations.Migrations);handled||err!=nil{returnerr}// Ctrl-C or SIGTERM cancels ctx, and ServeContext shuts down gracefully.ctx,stop:=signal.NotifyContext(context.Background(),os.Interrupt,syscall.SIGTERM)deferstop()fmt.Println("listening on",config.Addr)returntango.ServeContext(ctx,config,sqlDB,dsn.Dialect,tango.WithRecorder(observability.Multi(window)))}// appConfig is the whole application; the tests build the same one.funcappConfig(store*db.Store,sqlDB*sql.DB,dialectdb.Dialect,window*observability.Window)tango.Config{// The address is TANGO_ADDR, else the PORT hosting platforms set, else :8000.config:=tango.LoadConfigFromEnv(tango.WithPortFromEnv())// The status provider needs the finished config (tango.Status checks// registration), so it reads it through this closure.varfinishedtango.Configdashboard:=admin.DashboardConfig{Status:dashboardStatus(func()tango.Config{returnfinished},sqlDB,dialect),Activity:window,}config.InstalledApps=[]tango.App{posts.New(store),authors.New(),admin.New(store,admin.WithDashboard(dashboard)),}finished=configreturnconfig}