spread

https://git.tonybtw.com/spread.git git://git.tonybtw.com/spread.git
1,600 bytes raw
1
// Package app is the self-hosted, Postgres-backed side of Spread: subscribers,
2
// users, sessions, the admin CMS, and the customer dashboard. It is imported
3
// only by the standalone binary (main.go), never by the Vercel function, so
4
// the public demo stays dependency-light.
5
package app
6
7
import (
8
	"context"
9
	_ "embed"
10
	"fmt"
11
12
	"github.com/jackc/pgx/v5/pgxpool"
13
)
14
15
//go:embed schema.sql
16
var schemaSQL string
17
18
// App holds the database pool and shared configuration for all handlers.
19
type App struct {
20
	pool         *pgxpool.Pool
21
	secureCookie bool
22
	baseURL      string
23
	mailer       Mailer
24
}
25
26
// Config configures the application. Only DSN is required; the rest have sane
27
// defaults so the CLI helpers can open with a bare Config.
28
type Config struct {
29
	DSN          string
30
	SecureCookie bool
31
	BaseURL      string
32
	Mailer       Mailer
33
}
34
35
// Open connects to Postgres, verifies the connection, and applies the schema.
36
func Open(ctx context.Context, cfg Config) (*App, error) {
37
	pool, err := pgxpool.New(ctx, cfg.DSN)
38
	if err != nil {
39
		return nil, fmt.Errorf("connect: %w", err)
40
	}
41
	if err := pool.Ping(ctx); err != nil {
42
		pool.Close()
43
		return nil, fmt.Errorf("ping: %w", err)
44
	}
45
	if _, err := pool.Exec(ctx, schemaSQL); err != nil {
46
		pool.Close()
47
		return nil, fmt.Errorf("apply schema: %w", err)
48
	}
49
	if cfg.Mailer == nil {
50
		cfg.Mailer = LogMailer{}
51
	}
52
	if cfg.BaseURL == "" {
53
		cfg.BaseURL = "http://localhost:8137"
54
	}
55
	return &App{pool: pool, secureCookie: cfg.SecureCookie, baseURL: cfg.BaseURL, mailer: cfg.Mailer}, nil
56
}
57
58
func (a *App) Close() {
59
	if a.pool != nil {
60
		a.pool.Close()
61
	}
62
}