forked from tangled.org/core
Monorepo for Tangled — https://tangled.org
1package state 2 3import ( 4 "net/http" 5 "strings" 6 7 "github.com/go-chi/chi/v5" 8 "tangled.org/core/appview/issues" 9 "tangled.org/core/appview/knots" 10 "tangled.org/core/appview/labels" 11 "tangled.org/core/appview/middleware" 12 "tangled.org/core/appview/notifications" 13 "tangled.org/core/appview/pipelines" 14 "tangled.org/core/appview/pulls" 15 "tangled.org/core/appview/repo" 16 "tangled.org/core/appview/settings" 17 "tangled.org/core/appview/signup" 18 "tangled.org/core/appview/spindles" 19 "tangled.org/core/appview/state/userutil" 20 avstrings "tangled.org/core/appview/strings" 21 "tangled.org/core/log" 22) 23 24func (s *State) Router() http.Handler { 25 router := chi.NewRouter() 26 middleware := middleware.New( 27 s.oauth, 28 s.db, 29 s.enforcer, 30 s.repoResolver, 31 s.idResolver, 32 s.pages, 33 ) 34 35 router.Get("/favicon.svg", s.Favicon) 36 router.Get("/favicon.ico", s.Favicon) 37 router.Get("/pwa-manifest.json", s.PWAManifest) 38 router.Get("/robots.txt", s.RobotsTxt) 39 40 userRouter := s.UserRouter(&middleware) 41 standardRouter := s.StandardRouter(&middleware) 42 43 router.HandleFunc("/*", func(w http.ResponseWriter, r *http.Request) { 44 pat := chi.URLParam(r, "*") 45 if strings.HasPrefix(pat, "did:") || strings.HasPrefix(pat, "@") { 46 userRouter.ServeHTTP(w, r) 47 } else { 48 // Check if the first path element is a valid handle without '@' or a flattened DID 49 pathParts := strings.SplitN(pat, "/", 2) 50 if len(pathParts) > 0 { 51 if userutil.IsHandleNoAt(pathParts[0]) { 52 // Redirect to the same path but with '@' prefixed to the handle 53 redirectPath := "@" + pat 54 http.Redirect(w, r, "/"+redirectPath, http.StatusFound) 55 return 56 } else if userutil.IsFlattenedDid(pathParts[0]) { 57 // Redirect to the unflattened DID version 58 unflattenedDid := userutil.UnflattenDid(pathParts[0]) 59 var redirectPath string 60 if len(pathParts) > 1 { 61 redirectPath = unflattenedDid + "/" + pathParts[1] 62 } else { 63 redirectPath = unflattenedDid 64 } 65 http.Redirect(w, r, "/"+redirectPath, http.StatusFound) 66 return 67 } 68 } 69 standardRouter.ServeHTTP(w, r) 70 } 71 }) 72 73 return router 74} 75 76func (s *State) UserRouter(mw *middleware.Middleware) http.Handler { 77 r := chi.NewRouter() 78 79 r.With(mw.ResolveIdent()).Route("/{user}", func(r chi.Router) { 80 r.Get("/", s.Profile) 81 r.Get("/feed.atom", s.AtomFeedPage) 82 83 // redirect /@handle/repo.git -> /@handle/repo 84 r.Get("/{repo}.git", func(w http.ResponseWriter, r *http.Request) { 85 nonDotGitPath := strings.TrimSuffix(r.URL.Path, ".git") 86 http.Redirect(w, r, nonDotGitPath, http.StatusMovedPermanently) 87 }) 88 89 r.With(mw.ResolveRepo()).Route("/{repo}", func(r chi.Router) { 90 r.Use(mw.GoImport()) 91 r.Mount("/", s.RepoRouter(mw)) 92 r.Mount("/issues", s.IssuesRouter(mw)) 93 r.Mount("/pulls", s.PullsRouter(mw)) 94 r.Mount("/pipelines", s.PipelinesRouter(mw)) 95 r.Mount("/labels", s.LabelsRouter(mw)) 96 97 // These routes get proxied to the knot 98 r.Get("/info/refs", s.InfoRefs) 99 r.Post("/git-upload-pack", s.UploadPack) 100 r.Post("/git-receive-pack", s.ReceivePack) 101 102 }) 103 }) 104 105 r.NotFound(func(w http.ResponseWriter, r *http.Request) { 106 s.pages.Error404(w) 107 }) 108 109 return r 110} 111 112func (s *State) StandardRouter(mw *middleware.Middleware) http.Handler { 113 r := chi.NewRouter() 114 115 r.Handle("/static/*", s.pages.Static()) 116 117 r.Get("/", s.HomeOrTimeline) 118 r.Get("/timeline", s.Timeline) 119 r.Get("/upgradeBanner", s.UpgradeBanner) 120 121 // special-case handler for serving tangled.org/core 122 r.Get("/core", s.Core()) 123 124 r.Get("/login", s.Login) 125 r.Post("/login", s.Login) 126 r.Post("/logout", s.Logout) 127 128 r.Route("/repo", func(r chi.Router) { 129 r.Route("/new", func(r chi.Router) { 130 r.Use(middleware.AuthMiddleware(s.oauth)) 131 r.Get("/", s.NewRepo) 132 r.Post("/", s.NewRepo) 133 }) 134 // r.Post("/import", s.ImportRepo) 135 }) 136 137 r.Get("/goodfirstissues", s.GoodFirstIssues) 138 139 r.With(middleware.AuthMiddleware(s.oauth)).Route("/follow", func(r chi.Router) { 140 r.Post("/", s.Follow) 141 r.Delete("/", s.Follow) 142 }) 143 144 r.With(middleware.AuthMiddleware(s.oauth)).Route("/star", func(r chi.Router) { 145 r.Post("/", s.Star) 146 r.Delete("/", s.Star) 147 }) 148 149 r.With(middleware.AuthMiddleware(s.oauth)).Route("/react", func(r chi.Router) { 150 r.Post("/", s.React) 151 r.Delete("/", s.React) 152 }) 153 154 r.Route("/profile", func(r chi.Router) { 155 r.Use(middleware.AuthMiddleware(s.oauth)) 156 r.Get("/edit-bio", s.EditBioFragment) 157 r.Get("/edit-pins", s.EditPinsFragment) 158 r.Post("/bio", s.UpdateProfileBio) 159 r.Post("/pins", s.UpdateProfilePins) 160 }) 161 162 r.Mount("/settings", s.SettingsRouter()) 163 r.Mount("/strings", s.StringsRouter(mw)) 164 r.Mount("/knots", s.KnotsRouter()) 165 r.Mount("/spindles", s.SpindlesRouter()) 166 r.Mount("/notifications", s.NotificationsRouter(mw)) 167 168 r.Mount("/signup", s.SignupRouter()) 169 r.Mount("/", s.oauth.Router()) 170 171 r.Get("/keys/{user}", s.Keys) 172 r.Get("/terms", s.TermsOfService) 173 r.Get("/privacy", s.PrivacyPolicy) 174 r.Get("/brand", s.Brand) 175 176 r.NotFound(func(w http.ResponseWriter, r *http.Request) { 177 s.pages.Error404(w) 178 }) 179 return r 180} 181 182// Core serves tangled.org/core go-import meta tags, and redirects 183// to the core repository if accessed normally. 184func (s *State) Core() http.HandlerFunc { 185 return func(w http.ResponseWriter, r *http.Request) { 186 if r.URL.Query().Get("go-get") == "1" { 187 w.Header().Set("Content-Type", "text/html") 188 w.Write([]byte(`<meta name="go-import" content="tangled.org/core git https://tangled.org/@tangled.org/core">`)) 189 return 190 } 191 192 http.Redirect(w, r, "/@tangled.org/core", http.StatusFound) 193 } 194} 195 196func (s *State) SettingsRouter() http.Handler { 197 settings := &settings.Settings{ 198 Db: s.db, 199 OAuth: s.oauth, 200 Pages: s.pages, 201 Config: s.config, 202 } 203 204 return settings.Router() 205} 206 207func (s *State) SpindlesRouter() http.Handler { 208 logger := log.SubLogger(s.logger, "spindles") 209 210 spindles := &spindles.Spindles{ 211 Db: s.db, 212 OAuth: s.oauth, 213 Pages: s.pages, 214 Config: s.config, 215 Enforcer: s.enforcer, 216 IdResolver: s.idResolver, 217 Logger: logger, 218 } 219 220 return spindles.Router() 221} 222 223func (s *State) KnotsRouter() http.Handler { 224 logger := log.SubLogger(s.logger, "knots") 225 226 knots := &knots.Knots{ 227 Db: s.db, 228 OAuth: s.oauth, 229 Pages: s.pages, 230 Config: s.config, 231 Enforcer: s.enforcer, 232 IdResolver: s.idResolver, 233 Knotstream: s.knotstream, 234 Logger: logger, 235 } 236 237 return knots.Router() 238} 239 240func (s *State) StringsRouter(mw *middleware.Middleware) http.Handler { 241 logger := log.SubLogger(s.logger, "strings") 242 243 strs := &avstrings.Strings{ 244 Db: s.db, 245 OAuth: s.oauth, 246 Pages: s.pages, 247 IdResolver: s.idResolver, 248 Notifier: s.notifier, 249 Logger: logger, 250 } 251 252 return strs.Router(mw) 253} 254 255func (s *State) IssuesRouter(mw *middleware.Middleware) http.Handler { 256 issues := issues.New( 257 s.oauth, 258 s.repoResolver, 259 s.pages, 260 s.idResolver, 261 s.db, 262 s.config, 263 s.notifier, 264 s.validator, 265 log.SubLogger(s.logger, "issues"), 266 ) 267 return issues.Router(mw) 268} 269 270func (s *State) PullsRouter(mw *middleware.Middleware) http.Handler { 271 pulls := pulls.New( 272 s.oauth, 273 s.repoResolver, 274 s.pages, 275 s.idResolver, 276 s.db, 277 s.config, 278 s.notifier, 279 s.enforcer, 280 log.SubLogger(s.logger, "pulls"), 281 ) 282 return pulls.Router(mw) 283} 284 285func (s *State) RepoRouter(mw *middleware.Middleware) http.Handler { 286 repo := repo.New( 287 s.oauth, 288 s.repoResolver, 289 s.pages, 290 s.spindlestream, 291 s.idResolver, 292 s.db, 293 s.config, 294 s.notifier, 295 s.enforcer, 296 log.SubLogger(s.logger, "repo"), 297 s.validator, 298 ) 299 return repo.Router(mw) 300} 301 302func (s *State) PipelinesRouter(mw *middleware.Middleware) http.Handler { 303 pipes := pipelines.New( 304 s.oauth, 305 s.repoResolver, 306 s.pages, 307 s.spindlestream, 308 s.idResolver, 309 s.db, 310 s.config, 311 s.enforcer, 312 log.SubLogger(s.logger, "pipelines"), 313 ) 314 return pipes.Router(mw) 315} 316 317func (s *State) LabelsRouter(mw *middleware.Middleware) http.Handler { 318 ls := labels.New( 319 s.oauth, 320 s.pages, 321 s.db, 322 s.validator, 323 s.enforcer, 324 log.SubLogger(s.logger, "labels"), 325 ) 326 return ls.Router(mw) 327} 328 329func (s *State) NotificationsRouter(mw *middleware.Middleware) http.Handler { 330 notifs := notifications.New(s.db, s.oauth, s.pages, log.SubLogger(s.logger, "notifications")) 331 return notifs.Router(mw) 332} 333 334func (s *State) SignupRouter() http.Handler { 335 sig := signup.New(s.config, s.db, s.posthog, s.idResolver, s.pages, log.SubLogger(s.logger, "signup")) 336 return sig.Router() 337}