rastrillo / idear Public

Clone
git clone https://amadan.net/rastrillo/idear

Plain git — no account needed to clone.

Download

Download this file

1// Command board is a complete, working app on rastrillo + idear: a
2// shared message board whose roster — who is in this instance, at what
3// rank, and who may change that — is idear's.
4//
5// It is the worked reference SKILL.md points at. Read app.go for the
6// wiring, models.go for the app's own identity row and the seed, and
7// app_test.go for the whole flow driven through real HTTP.
8//
9// Run it:
10//
11// BOARD_SEED=1 go run ./example -addr 127.0.0.1:8080 -db /tmp/board.db
12//
13// then sign in at http://127.0.0.1:8080/signin as ada@example.test
14// (Owner), kim@example.test (Admin) or sam@example.test (Member), all
15// with the password "demo-password".
16package main
17
18import (
19 "context"
20 "log/slog"
21 "net/url"
22 "os"
23
24 "github.com/carlosframework/rastrillo"
25 "github.com/carlosframework/rastrillo/db"
26)
27
28func main() {
29 logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
30
31 origin := os.Getenv("BOARD_ORIGIN")
32 if origin == "" {
33 origin = "http://localhost:8080"
34 // Loud on purpose: origin decides the Secure/__Host- cookie
35 // attributes and the CSRF same-origin check, so a silent
36 // default in a real deployment means http-grade cookies on an
37 // https app.
38 logger.Warn("BOARD_ORIGIN not set; defaulting", "origin", origin)
39 }
40
41 // The instance's display name, shown on the PUBLIC invitation
42 // page. Set it, always: idear falls back to the request's Host
43 // header, which the client supplies.
44 site := os.Getenv("BOARD_NAME")
45 if site == "" {
46 site = hostOf(origin)
47 }
48
49 // Resolve, not Run: this app opens its own database handle through
50 // db.Open — a *gorm.DB over the split reader/writer pool — so
51 // Options.DBPath must be blanked before Serve, or Serve opens a
52 // second connection to the same file.
53 opts, err := rastrillo.Resolve(rastrillo.Options{DBPath: "board.db", Logger: logger})
54 if err != nil {
55 logger.Error("resolve activation", "err", err)
56 os.Exit(1)
57 }
58
59 d, err := db.Open(opts.DBPath, logger)
60 if err != nil {
61 logger.Error("open database", "err", err)
62 os.Exit(1)
63 }
64 defer d.Close()
65
66 a, err := newApp(d, origin, site, logger)
67 if err != nil {
68 logger.Error("build app", "err", err)
69 os.Exit(1)
70 }
71
72 if os.Getenv("BOARD_SEED") == "1" {
73 if err := Seed(context.Background(), d.G, a.roster); err != nil {
74 logger.Error("seed", "err", err)
75 os.Exit(1)
76 }
77 }
78
79 opts.Mux = a.mux
80 opts.DBPath = ""
81 if err := rastrillo.Serve(opts); err != nil {
82 logger.Error("serve failed", "err", err)
83 os.Exit(1)
84 }
85}
86
87// hostOf is the host half of an absolute origin, used as the instance
88// name when BOARD_NAME is unset. It is NOT the Host header: it comes
89// from the app's own configured origin, so it is a name the operator
90// chose rather than one a visitor sent.
91func hostOf(origin string) string {
92 u, err := url.Parse(origin)
93 if err != nil || u.Host == "" {
94 return origin
95 }
96 return u.Host
97}
98