(READ ONLY) Margin is an open annotation layer for the internet. Powered by the AT Protocol.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216package main
import ( "context" "net/http" "os" "os/signal" "strings" "syscall" "time"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/cors" "github.com/joho/godotenv"
"margin.at/internal/analytics" "margin.at/internal/api" "margin.at/internal/db" "margin.at/internal/embeddings" "margin.at/internal/firehose" "margin.at/internal/logger" internalMiddleware "margin.at/internal/middleware" "margin.at/internal/oauth" "margin.at/internal/recommendations" "margin.at/internal/sync")
func main() { godotenv.Load("../.env", ".env")
dsn := os.Getenv("DATABASE_URL") if dsn == "" { logger.Fatal("DATABASE_URL environment variable is required") } database, err := db.New(dsn) if err != nil { logger.Fatal("Failed to connect to database: %v", err) } defer database.Close()
if err := database.Migrate(); err != nil { logger.Fatal("Failed to run migrations: %v", err) } database.MigrateUnifiedNotes()
go func() { ticker := time.NewTicker(1 * time.Hour) defer ticker.Stop()
if err := database.DeleteExpiredSessions(); err != nil { logger.Error("Failed to run initial cleanup of expired sessions: %v", err) }
for range ticker.C { if err := database.DeleteExpiredSessions(); err != nil { logger.Error("Failed to delete expired sessions: %v", err) } } }()
embeddingClient := embeddings.NewClient() recService := recommendations.NewService(database, embeddingClient) logger.Info("Recommendation engine initialized (embeddings enabled: %v)", embeddingClient.IsEnabled())
syncSvc := sync.NewService(database)
analyticsCl := analytics.New() defer analyticsCl.Close()
oauthHandler, err := oauth.NewHandler(database, syncSvc, analyticsCl) if err != nil { logger.Fatal("Failed to initialize OAuth: %v", err) }
ingester := firehose.NewIngester(database, syncSvc) firehose.RelayURL = getEnv("BLOCK_RELAY_URL", "wss://jetstream2.us-east.bsky.network/subscribe") logger.Info("Firehose URL: %s", firehose.RelayURL)
backfillCtx, backfillCancel := context.WithCancel(context.Background()) defer backfillCancel()
if recService.IsEnabled() { ingester.SetOnAnnotation(recService.OnAnnotation) ingester.SetOnDocument(recService.OnDocument)
if getEnv("DISABLE_BACKFILL", "") == "" { go func() { time.Sleep(5 * time.Second) select { case <-backfillCtx.Done(): return default: } logger.Info("Starting recommendation backfill...") if err := recService.BackfillDocumentEmbeddings(200); err != nil { logger.Error("Document embedding backfill error: %v", err) } if backfillCtx.Err() != nil { return } annCount, err := recService.BackfillAnnotationEmbeddings(200) if err != nil { logger.Error("Annotation embedding backfill error: %v", err) } if backfillCtx.Err() != nil { return } hlCount, err := recService.BackfillHighlightEmbeddings(200) if err != nil { logger.Error("Highlight embedding backfill error: %v", err) } if backfillCtx.Err() != nil { return } profileCount, err := recService.RebuildAllProfiles() if err != nil { logger.Error("Profile rebuild error: %v", err) } logger.Info("Recommendation backfill complete (annotations: %d, highlights: %d, profiles: %d)", annCount, hlCount, profileCount) }() } else { logger.Info("Recommendation backfill disabled (DISABLE_BACKFILL is set)") } }
go func() { if err := ingester.Start(context.Background()); err != nil { logger.Error("Firehose ingester error: %v", err) } }()
r := chi.NewRouter()
r.Use(internalMiddleware.PrivacyLogger) r.Use(middleware.Recoverer) r.Use(middleware.RequestID) r.Use(middleware.RealIP) r.Use(middleware.Timeout(60 * time.Second)) r.Use(middleware.Throttle(100))
r.Use(cors.Handler(cors.Options{ AllowOriginFunc: func(r *http.Request, origin string) bool { if strings.HasPrefix(origin, "chrome-extension://") || strings.HasPrefix(origin, "moz-extension://") || strings.HasPrefix(origin, "safari-web-extension://") { return true } if baseURL := os.Getenv("BASE_URL"); baseURL != "" { return origin == baseURL } return false }, AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token", "X-Session-Token"}, ExposedHeaders: []string{"Link"}, AllowCredentials: true, MaxAge: 300, }))
tokenRefresher := api.NewTokenRefresher(database, oauthHandler.GetPrivateKey()) noteWriteSvc := api.NewNoteWriteService(database, tokenRefresher)
handler := api.NewHandler(database, noteWriteSvc, tokenRefresher, syncSvc, recService, analyticsCl) handler.RegisterRoutes(r)
r.Route("/auth", func(r chi.Router) { r.Use(middleware.Throttle(10)) r.Get("/login", oauthHandler.HandleLogin) r.Post("/start", oauthHandler.HandleStart) r.Post("/signup", oauthHandler.HandleSignup) r.Get("/callback", oauthHandler.HandleCallback) r.Post("/logout", oauthHandler.HandleLogout) r.Get("/session", oauthHandler.HandleSession) }) r.Get("/oauth-client-metadata.json", oauthHandler.HandleClientMetadata) r.Get("/jwks.json", oauthHandler.HandleJWKS)
port := getEnv("PORT", "8081") server := &http.Server{ Addr: ":" + port, Handler: r, }
go func() { logger.Info("Margin API server running on :%s", port) if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { logger.Fatal("Server error: %v", err) } }()
quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit
logger.Infoln("Shutting down server...") backfillCancel() ingester.Stop()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel()
if err := server.Shutdown(ctx); err != nil { logger.Fatal("Server forced to shutdown: %v", err) }
logger.Infoln("Server exited")}
func getEnv(key, fallback string) string { if value, ok := os.LookupEnv(key); ok { return value } return fallback}