* refactor: update api and client to reference humanIds * fix: prevent deletion of network member This may cause various side effects if there is data in other services which reference this member
140 lines
4.5 KiB
Go
140 lines
4.5 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
|
|
"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/middleware"
|
|
"github.com/flowy-live/llink/internal/network"
|
|
"github.com/flowy-live/llink/internal/particle"
|
|
"github.com/flowy-live/llink/internal/utils"
|
|
"github.com/redis/go-redis/v9"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/credentials/insecure"
|
|
)
|
|
|
|
const (
|
|
// FIX: Use separate redis instance. We start with higher number because use this same instance in helios.
|
|
REDIS_DATABASE_FOR_AUTH int = 4
|
|
)
|
|
|
|
func redisForAuth() *redis.Client {
|
|
return internal.ConnectAndTestRedis(REDIS_DATABASE_FOR_AUTH)
|
|
}
|
|
|
|
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,
|
|
})
|
|
|
|
// Initialize handler
|
|
h := handler.NewHandler(authSvc, humanSvc, networkSvc, particleSvc, depotSvc)
|
|
|
|
// 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)
|
|
|
|
// ==========================================================================
|
|
// Protected routes (auth required)
|
|
// ==========================================================================
|
|
|
|
// Auth
|
|
mux.Handle("POST /auth/sign-out", withAuth(h.SignOut))
|
|
mux.Handle("GET /auth/me", withAuth(h.GetCurrentHuman))
|
|
|
|
// 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))
|
|
|
|
// 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)
|
|
}
|
|
}
|