Hub: accounts, IMVU linking by tagline, proxy sign-in, presence, console
CI / test (push) Failing after 42s

People sign up on the website and link IMVU accounts by putting a code in the account's tagline, which the
hub reads from IMVU's public API (the most recent proof wins; link caps per person). IMVU's age verification
or Access Pass make an account adult. T5DE's proxy signs in with a device code and keeps a WebSocket open,
reporting the IMVU account in use and the community products it wears; /online shows those who opted in.
Moderators get a console: who's online, people, link caps, bans, roles, and the audit log. Tests run the
website and API against an embedded Postgres and a fake IMVU API.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
dhkatzandClaude Opus 5.5 committed 2026-10-29 03:45:52 -07:00
1 parent 54e569bea3
commit b9dc3f0ec1
36 files changed
+3548 -5

No files matched your search

+106
View File
@@ -0,0 +1,106 @@
// Command t5de-hub runs T5DE's hub: the website, the moderation console, and the API proxies sign in to.
//
// t5de-hub -db postgres://... -public-url https://hub.t5de.dev [-listen :8080] [-data dir]
// t5de-hub -dev runs its own Postgres in <data>/postgres (for development)
package main
import (
"context"
"errors"
"flag"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"path/filepath"
"strings"
"time"
embeddedpostgres "github.com/fergusstrange/embedded-postgres"
"git.t5de.dev/t5de/t5de-hub/internal/imvu"
"git.t5de.dev/t5de/t5de-hub/internal/realtime"
"git.t5de.dev/t5de/t5de-hub/internal/store"
"git.t5de.dev/t5de/t5de-hub/internal/token"
"git.t5de.dev/t5de/t5de-hub/internal/web"
)
func main() {
listen := flag.String("listen", "127.0.0.1:8080", "address to listen on")
dbURL := flag.String("db", os.Getenv("T5DE_HUB_DB"), "Postgres URL (or $T5DE_HUB_DB)")
publicURL := flag.String("public-url", "", "the hub's address as people reach it (default: http://<listen>)")
data := flag.String("data", "t5de-hub-data", "where the hub keeps its signing key (and, with -dev, its database)")
dev := flag.Bool("dev", false, "run a Postgres of its own in <data>/postgres, for development")
flag.Parse()
logger := log.New(os.Stderr, "", log.LstdFlags)
if err := run(logger, *listen, *dbURL, *publicURL, *data, *dev); err != nil {
logger.Fatal(err)
}
}
func run(logger *log.Logger, listen, dbURL, publicURL, data string, dev bool) error {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
if publicURL == "" {
publicURL = "http://" + strings.Replace(listen, "0.0.0.0", "localhost", 1)
}
publicURL = strings.TrimSuffix(publicURL, "/")
if dev {
pg, url, err := startDevPostgres(data)
if err != nil {
return fmt.Errorf("starting Postgres: %w", err)
}
defer pg.Stop()
dbURL = url
}
if dbURL == "" {
return errors.New("no database: give -db (or $T5DE_HUB_DB), or -dev")
}
st, err := store.Open(ctx, dbURL)
if err != nil {
return err
}
defer st.Close()
keys, err := token.LoadOrCreate(filepath.Join(data, "signing.key"), publicURL)
if err != nil {
return err
}
server := &web.Server{Store: st, IMVU: imvu.New(), Keys: keys, PublicURL: publicURL, Log: logger}
server.Realtime = realtime.New(web.Owners{Store: st}, logger)
handler, err := server.Handler()
if err != nil {
return err
}
srv := &http.Server{Addr: listen, Handler: handler, ReadHeaderTimeout: 10 * time.Second}
go func() {
<-ctx.Done()
shutdown, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
srv.Shutdown(shutdown)
}()
logger.Printf("t5de-hub listening on %s (%s)", listen, publicURL)
if err := srv.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
return err
}
return nil
}
// startDevPostgres runs Postgres from data/postgres (downloaded once into data/postgres-cache).
func startDevPostgres(data string) (*embeddedpostgres.EmbeddedPostgres, string, error) {
abs, err := filepath.Abs(data)
if err != nil {
return nil, "", err
}
const port = 54321
pg := embeddedpostgres.NewDatabase(embeddedpostgres.DefaultConfig().
Port(port).Database("t5de_hub").
DataPath(filepath.Join(abs, "postgres", "data")).
RuntimePath(filepath.Join(abs, "postgres", "runtime")).
CachePath(filepath.Join(abs, "postgres-cache")).
StartTimeout(time.Minute))
if err := pg.Start(); err != nil {
return nil, "", err
}
return pg, fmt.Sprintf("postgres://postgres:postgres@localhost:%d/t5de_hub?sslmode=disable", port), nil
}