The codebase that powers boop.cat boop.cat
11
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 171 lines 5.2 kB view raw
1// Copyright 2025 boop.cat 2// Licensed under the Apache License, Version 2.0 3// See LICENSE file for details. 4 5package main 6 7import ( 8 "fmt" 9 "net/http" 10 "os" 11 "path/filepath" 12 "time" 13 14 "github.com/go-chi/chi/v5" 15 chimiddleware "github.com/go-chi/chi/v5/middleware" 16 "github.com/go-chi/cors" 17 "github.com/joho/godotenv" 18 19 "boop-cat/config" 20 "boop-cat/db" 21 "boop-cat/handlers" 22 "boop-cat/lib" 23 "boop-cat/middleware" 24 "boop-cat/oauth" 25) 26 27func main() { 28 29 _ = godotenv.Load() 30 _ = godotenv.Load("../.env") 31 32 lib.StartDMCAMonitor() 33 34 cfg := config.Load() 35 36 if cfg.SessionSecret == "" { 37 fmt.Fprintln(os.Stderr, "Missing SESSION_SECRET. Generate one: openssl rand -base64 32") 38 os.Exit(1) 39 } 40 41 database, err := db.GetDB(cfg.DBPath) 42 if err != nil { 43 fmt.Fprintf(os.Stderr, "Failed to initialize database: %v\n", err) 44 os.Exit(1) 45 } 46 47 r := chi.NewRouter() 48 49 middleware.InitSessionStore(cfg.SessionSecret, cfg.CookieSecure) 50 51 oauth.InitProviders(cfg.SessionSecret) 52 53 r.Use(chimiddleware.Logger) 54 r.Use(chimiddleware.Recoverer) 55 r.Use(chimiddleware.RealIP) 56 r.Use(middleware.WithUser(database)) 57 r.Use(middleware.RateLimit(100, 60*time.Second)) 58 59 r.Use(cors.Handler(cors.Options{ 60 AllowedOrigins: []string{"*"}, 61 AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}, 62 AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"}, 63 ExposedHeaders: []string{"Link"}, 64 AllowCredentials: true, 65 MaxAge: 300, 66 })) 67 68 r.Get("/api/health", func(w http.ResponseWriter, r *http.Request) { 69 w.Header().Set("Content-Type", "application/json") 70 w.Write([]byte(`{"ok":true,"backend":"go"}`)) 71 }) 72 73 r.Get("/api/config", func(w http.ResponseWriter, r *http.Request) { 74 w.Header().Set("Content-Type", "application/json") 75 fmt.Fprintf(w, `{"deliveryMode":"%s","edgeRootDomain":"%s"}`, 76 cfg.DeliveryMode, cfg.EdgeRootDomain) 77 }) 78 79 deployHandler := handlers.NewDeployHandler(database) 80 81 authHandler := handlers.NewAuthHandler(database, deployHandler.Engine) 82 r.Mount("/api/auth", authHandler.Routes()) 83 r.Route("/auth", func(r chi.Router) { 84 authHandler.MountOAuthRoutes(r) 85 }) 86 r.Get("/api/github/repos", authHandler.GetGitHubRepos) 87 r.Get("/github/installed", authHandler.GitHubInstalled) 88 89 apiKeysHandler := handlers.NewAPIKeysHandler(database) 90 r.Mount("/api/api-keys", apiKeysHandler.Routes()) 91 92 sitesHandler := handlers.NewSitesHandler(database, deployHandler.Engine) 93 94 r.Mount("/api/account", handlers.NewAccountHandler(database).Routes()) 95 96 cdHandler := handlers.NewCustomDomainHandler(database, deployHandler.Engine) 97 98 r.Route("/api/sites", func(r chi.Router) { 99 r.Use(middleware.RequireLogin) 100 101 r.Get("/", sitesHandler.ListSites) 102 r.Post("/", sitesHandler.CreateSite) 103 104 r.Route("/{siteId}", func(r chi.Router) { 105 r.Patch("/", sitesHandler.UpdateSiteEnv) 106 r.Patch("/settings", sitesHandler.UpdateSiteSettings) 107 r.Put("/settings", sitesHandler.UpdateSiteSettings) 108 r.Post("/settings", sitesHandler.UpdateSiteSettings) 109 r.Delete("/", sitesHandler.DeleteSite) 110 111 r.Post("/deploy", deployHandler.TriggerDeploy) 112 r.Get("/deployments", deployHandler.ListDeployments) 113 114 r.Get("/custom-domains", cdHandler.ListCustomDomains) 115 r.Post("/custom-domains", cdHandler.CreateCustomDomain) 116 r.Delete("/custom-domains/{id}", cdHandler.DeleteCustomDomain) 117 r.Post("/custom-domains/{id}/poll", cdHandler.PollCustomDomain) 118 }) 119 120 r.Post("/preview", deployHandler.PreviewSite) 121 }) 122 123 r.Route("/api/deployments/{id}", func(r chi.Router) { 124 r.Use(middleware.RequireLogin) 125 r.Get("/", deployHandler.GetDeployment) 126 r.Get("/logs", deployHandler.GetDeploymentLogs) 127 r.Post("/stop", deployHandler.StopDeployment) 128 }) 129 130 r.Delete("/api/account", func(w http.ResponseWriter, r *http.Request) { 131 middleware.RequireLogin(http.HandlerFunc(authHandler.DeleteAccount)).ServeHTTP(w, r) 132 }) 133 134 ghWebhookHandler := handlers.NewGitHubWebhookHandler(database, deployHandler.Engine) 135 r.Mount("/api/github/webhook", ghWebhookHandler.Routes()) 136 137 apiV1Handler := handlers.NewAPIV1Handler(database, deployHandler.Engine) 138 r.Mount("/api/v1", apiV1Handler.Routes()) 139 140 adminHandler := handlers.NewAdminHandler(database) 141 r.Mount("/api/admin", adminHandler.Routes()) 142 143 atprotoHandler := handlers.NewATProtoHandler(database) 144 r.Get("/client-metadata.json", atprotoHandler.ServeClientMetadata) 145 r.Get("/jwks.json", atprotoHandler.ServeJWKS) 146 r.Get("/auth/atproto", atprotoHandler.BeginAuth) 147 r.Get("/auth/atproto/callback", atprotoHandler.Callback) 148 149 clientDist := filepath.Join("..", "client", "dist") 150 if _, err := os.Stat(clientDist); err == nil { 151 152 fs := http.FileServer(http.Dir(clientDist)) 153 r.Handle("/*", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 154 155 path := filepath.Join(clientDist, r.URL.Path) 156 if _, err := os.Stat(path); os.IsNotExist(err) { 157 158 http.ServeFile(w, r, filepath.Join(clientDist, "index.html")) 159 return 160 } 161 fs.ServeHTTP(w, r) 162 })) 163 } 164 165 addr := fmt.Sprintf(":%d", cfg.Port) 166 fmt.Printf("boop.cat (Go) listening on http://127.0.0.1%s\n", addr) 167 if err := http.ListenAndServe(addr, r); err != nil { 168 fmt.Fprintf(os.Stderr, "Server error: %v\n", err) 169 os.Exit(1) 170 } 171}