* increases timeout * refactor: reuse constants for redis db * avoid closing connection on pong error * fix: prevent electron from pausing timer executions Our pings could be paused when our app is backgrounded. * refactor: remove unnecessary refs * fix: presence unreliable within same pod It was broadcasting presence changes to other pods, but not notifying clients connected to the same pod
162 lines
5.4 KiB
Go
162 lines
5.4 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
|
|
"cloud.google.com/go/firestore"
|
|
"cloud.google.com/go/storage"
|
|
pbaero "github.com/flowy-live/llink/genproto/aero"
|
|
"github.com/flowy-live/llink/internal"
|
|
"github.com/flowy-live/llink/internal/auth"
|
|
"github.com/flowy-live/llink/internal/db"
|
|
"github.com/flowy-live/llink/internal/depot"
|
|
"github.com/flowy-live/llink/internal/handler"
|
|
"github.com/flowy-live/llink/internal/human"
|
|
"github.com/flowy-live/llink/internal/livekit"
|
|
"github.com/flowy-live/llink/internal/middleware"
|
|
"github.com/flowy-live/llink/internal/network"
|
|
"github.com/flowy-live/llink/internal/particle"
|
|
"github.com/flowy-live/llink/internal/utils"
|
|
"github.com/flowy-live/llink/internal/waitlist"
|
|
"github.com/redis/go-redis/v9"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/credentials/insecure"
|
|
)
|
|
|
|
func redisForAuth() *redis.Client {
|
|
return internal.ConnectAndTestRedis(db.RedisDBAuth)
|
|
}
|
|
|
|
func main() {
|
|
port := utils.MustGetEnv("PORT")
|
|
gcsBucket := utils.MustGetEnv("GCS_BUCKET")
|
|
|
|
// Initialize database
|
|
db.Init()
|
|
defer db.Cleanup()
|
|
|
|
// Initialize Redis for auth
|
|
redisClient := redisForAuth()
|
|
|
|
// Initialize GCS client
|
|
ctx := context.Background()
|
|
storageClient, err := storage.NewClient(ctx)
|
|
if err != nil {
|
|
slog.Error("failed to create GCS client", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
defer storageClient.Close()
|
|
|
|
aeroAddr := utils.MustGetEnv("AERO_ADDR")
|
|
if aeroAddr == "" {
|
|
slog.Error("must provide AERO_ADDR")
|
|
os.Exit(1)
|
|
}
|
|
aeroServer, err := grpc.NewClient(aeroAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
|
if err != nil {
|
|
slog.Error("connection to aero server invalid", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
defer aeroServer.Close()
|
|
aeroSvc := pbaero.NewPrimaryClient(aeroServer)
|
|
|
|
// Initialize services
|
|
authSvc := auth.NewAuthService(redisClient, aeroSvc)
|
|
humanSvc := human.NewService(db.Pool())
|
|
networkSvc := network.NewService(db.Pool())
|
|
particleSvc := particle.NewService(db.Pool(), networkSvc)
|
|
depotSvc := depot.NewService(db.Pool(), storageClient, depot.Config{
|
|
GoogleServiceAccountEmail: utils.MustGetEnv("GOOGLE_SERVICE_ACCOUNT_EMAIL"),
|
|
BucketName: gcsBucket,
|
|
})
|
|
waitlistSvc := waitlist.NewService(db.Pool(), aeroSvc)
|
|
livekitClient := livekit.NewClient()
|
|
|
|
// Initialize Firestore client (for webhook-driven updates)
|
|
gcpProject := utils.MustGetEnv("GCP_PROJECT")
|
|
firestoreClient, err := firestore.NewClient(ctx, gcpProject)
|
|
if err != nil {
|
|
slog.Error("failed to create Firestore client", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
defer firestoreClient.Close()
|
|
|
|
// Initialize handler
|
|
h := handler.NewHandler(authSvc, humanSvc, networkSvc, particleSvc, depotSvc, waitlistSvc, livekitClient, firestoreClient)
|
|
|
|
// Helper to wrap handlers with auth middleware
|
|
withAuth := func(hf http.HandlerFunc) http.Handler {
|
|
return middleware.Auth(authSvc)(http.HandlerFunc(hf))
|
|
}
|
|
|
|
mux := http.NewServeMux()
|
|
|
|
// ==========================================================================
|
|
// Public routes (no auth)
|
|
// ==========================================================================
|
|
mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
})
|
|
mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
})
|
|
mux.HandleFunc("POST /auth/request-code", h.RequestSignInCode)
|
|
mux.HandleFunc("POST /auth/sign-in", h.SignIn)
|
|
mux.HandleFunc("POST /waitlist", h.AddToWaitlist)
|
|
mux.HandleFunc("POST /livekit/webhook", h.HandleLivekitWebhook)
|
|
|
|
// ==========================================================================
|
|
// Protected routes (auth required)
|
|
// ==========================================================================
|
|
|
|
// Auth
|
|
mux.Handle("POST /auth/sign-out", withAuth(h.SignOut))
|
|
mux.Handle("GET /auth/me", withAuth(h.GetCurrentHuman))
|
|
|
|
// Settings
|
|
mux.Handle("PATCH /humans/me/settings", withAuth(h.UpdateSettings))
|
|
|
|
// Networks
|
|
mux.Handle("POST /networks", withAuth(h.CreateNetwork))
|
|
mux.Handle("GET /networks", withAuth(h.ListNetworks))
|
|
mux.Handle("GET /networks/{id}", withAuth(h.GetNetwork))
|
|
mux.Handle("POST /networks/{id}/members", withAuth(h.AddMembersToNetwork))
|
|
// mux.Handle("DELETE /networks/{id}/members/{humanId}", withAuth(h.RemoveMemberFromNetwork))
|
|
|
|
// Network Invitations
|
|
mux.Handle("GET /networks/{id}/invitations", withAuth(h.ListInvitationsForNetwork))
|
|
mux.Handle("DELETE /networks/{id}/invitations", withAuth(h.RevokeInvitation))
|
|
mux.Handle("GET /invitations", withAuth(h.ListMyInvitations))
|
|
mux.Handle("POST /invitations/accept", withAuth(h.AcceptInvitation))
|
|
|
|
// Particles
|
|
mux.Handle("GET /particles/{id}/download", withAuth(h.DownloadParticleMedia))
|
|
|
|
// Depot
|
|
mux.Handle("POST /depot/upload", withAuth(h.PrepareUpload))
|
|
mux.Handle("POST /depot/objects/{id}/confirm", withAuth(h.ConfirmUpload))
|
|
|
|
// LiveKit
|
|
mux.Handle("POST /livekit/token", withAuth(h.GetLivekitToken))
|
|
|
|
// Waitlist (admin-only)
|
|
mux.Handle("GET /waitlist", withAuth(h.GetWaitlist))
|
|
mux.Handle("GET /waitlist/{email}", withAuth(h.GetWaitlistEntry))
|
|
mux.Handle("POST /waitlist/invite", withAuth(h.InviteWaitlistEntrant))
|
|
|
|
// Apply middleware
|
|
// nil allows all origins (required for electron app)
|
|
muxWithCors := middleware.CORS(nil)(mux)
|
|
|
|
addr := fmt.Sprintf("0.0.0.0:%s", port)
|
|
slog.Info("running server", "addr", addr)
|
|
if err := http.ListenAndServe(addr, muxWithCors); err != nil {
|
|
slog.Error("server failed", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|