Hub: accounts, IMVU linking by tagline, proxy sign-in, presence, console
CI / test (push) Failing after 42s
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:
1 parent
54e569bea3
commit
b9dc3f0ec1
36 files changed
+3548
-5
No files matched your search
@@ -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
|
||||
}
|
||||
Reference in new issue
Block a user