Monorepo for Tangled tangled.org
2

Configure Feed

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

at icy/lqyotq 12 kB View raw
1package state 2 3import ( 4 "context" 5 "database/sql" 6 "errors" 7 "net/http" 8 "strings" 9 10 "github.com/go-chi/chi/v5" 11 "tangled.org/core/appview/db" 12 "tangled.org/core/appview/focus" 13 "tangled.org/core/appview/issues" 14 "tangled.org/core/appview/knotacl" 15 "tangled.org/core/appview/knots" 16 "tangled.org/core/appview/labels" 17 "tangled.org/core/appview/metrics" 18 "tangled.org/core/appview/middleware" 19 "tangled.org/core/appview/migration" 20 "tangled.org/core/appview/notifications" 21 "tangled.org/core/appview/pipelines" 22 "tangled.org/core/appview/pulls" 23 "tangled.org/core/appview/repo" 24 "tangled.org/core/appview/settings" 25 "tangled.org/core/appview/signup" 26 "tangled.org/core/appview/spindles" 27 "tangled.org/core/appview/state/userutil" 28 avstrings "tangled.org/core/appview/strings" 29 avtimeline "tangled.org/core/appview/timeline" 30 "tangled.org/core/blog" 31 "tangled.org/core/log" 32) 33 34func (s *State) Router() http.Handler { 35 router := chi.NewRouter() 36 middleware := middleware.New( 37 s.oauth, 38 s.db, 39 s.enforcer, 40 s.aclService, 41 s.repoResolver, 42 s.idResolver, 43 s.pages, 44 s.rdb, 45 s.logger, 46 ) 47 48 router.Use(metrics.Middleware) 49 router.Use(knotacl.MemoMiddleware) 50 51 if err := db.ReapStaleRunningMigrations(context.Background(), s.db); err != nil { 52 s.logger.Warn("failed to reap stale running migrations", "err", err) 53 } 54 m := migration.NewMigration(s.db, s.oauth, s.idResolver.Directory(), s.logger) 55 router.Use(m.BackgroundMigrationMiddleware) 56 57 router.Get("/pwa-manifest.json", s.WebAppManifest) 58 router.Get("/robots.txt", s.RobotsTxt) 59 router.Get("/.well-known/security.txt", s.SecurityTxt) 60 61 userRouter := s.UserRouter(&middleware) 62 standardRouter := s.StandardRouter(&middleware) 63 64 router.HandleFunc("/*", func(w http.ResponseWriter, r *http.Request) { 65 pat := chi.URLParam(r, "*") 66 pathParts := strings.SplitN(pat, "/", 2) 67 68 if len(pathParts) > 0 { 69 firstPart := pathParts[0] 70 71 if userutil.IsDid(firstPart) { 72 repo, err := db.GetRepoByDid(s.db, firstPart) 73 switch { 74 case err == nil: 75 remaining := "" 76 if len(pathParts) > 1 { 77 remaining = "/" + pathParts[1] 78 } 79 rewritten := "/" + repo.Did + "/" + repo.Rkey + remaining 80 r2 := r.Clone(r.Context()) 81 r2.URL.Path = rewritten 82 r2.URL.RawPath = rewritten 83 userRouter.ServeHTTP(w, r2) 84 case errors.Is(err, sql.ErrNoRows): 85 userRouter.ServeHTTP(w, r) 86 default: 87 s.logger.Error("db error looking up repo DID", "repoDid", firstPart, "err", err) 88 http.Error(w, "internal server error", http.StatusInternalServerError) 89 } 90 return 91 } 92 93 if userutil.IsHandle(firstPart) { 94 userRouter.ServeHTTP(w, r) 95 return 96 } 97 98 // if using a flattened DID (like you would in go modules), unflatten 99 if userutil.IsFlattenedDid(firstPart) { 100 unflattenedDid := userutil.UnflattenDid(firstPart) 101 redirectPath := strings.Join(append([]string{unflattenedDid}, pathParts[1:]...), "/") 102 103 redirectURL := *r.URL 104 redirectURL.Path = "/" + redirectPath 105 106 http.Redirect(w, r, redirectURL.String(), http.StatusFound) 107 return 108 } 109 110 // if using a handle with @, rewrite to work without @ 111 if normalized := strings.TrimPrefix(firstPart, "@"); userutil.IsHandle(normalized) { 112 redirectPath := strings.Join(append([]string{normalized}, pathParts[1:]...), "/") 113 114 redirectURL := *r.URL 115 redirectURL.Path = "/" + redirectPath 116 117 http.Redirect(w, r, redirectURL.String(), http.StatusFound) 118 return 119 } 120 121 } 122 123 standardRouter.ServeHTTP(w, r) 124 }) 125 126 return router 127} 128 129func (s *State) UserRouter(mw *middleware.Middleware) http.Handler { 130 r := chi.NewRouter() 131 r.Use(mw.InjectBaseParams) 132 133 r.With(mw.ResolveIdent()).Route("/{user}", func(r chi.Router) { 134 r.Get("/", s.Profile) 135 r.Get("/feed.atom", s.AtomFeedPage) 136 137 r.With(mw.ResolveRepo()).Route("/{repo}", func(r chi.Router) { 138 r.Use(mw.GoImport()) 139 140 // These routes get proxied to the knot 141 r.Get("/info/refs", s.InfoRefs) 142 r.Post("/git-upload-archive", s.UploadArchive) 143 r.Post("/git-upload-pack", s.UploadPack) 144 r.Post("/git-receive-pack", s.ReceivePack) 145 146 r.Group(func(r chi.Router) { 147 r.Use(mw.CanonicalizeRepoURL()) 148 r.Mount("/issues", s.IssuesRouter(mw)) 149 r.Mount("/pulls", s.PullsRouter(mw)) 150 r.Mount("/pipelines", s.PipelinesRouter(mw)) 151 r.Mount("/labels", s.LabelsRouter()) 152 r.Mount("/", s.RepoRouter(mw)) 153 }) 154 }) 155 }) 156 157 r.NotFound(func(w http.ResponseWriter, r *http.Request) { 158 w.WriteHeader(http.StatusNotFound) 159 s.pages.Error404(w) 160 }) 161 162 return r 163} 164 165func (s *State) StandardRouter(mw *middleware.Middleware) http.Handler { 166 r := chi.NewRouter() 167 r.Use(mw.InjectBaseParams) 168 169 r.Handle("/static/*", s.pages.Static()) 170 171 tl := avtimeline.New(s.oauth, s.db, s.config, s.pages, s.logger, blog.PostsFS) 172 r.Get("/", tl.HomeOrTimeline) 173 r.Get("/home", tl.Home) 174 r.Get("/timeline", tl.Timeline) 175 r.Get("/upgradeBanner", s.UpgradeBanner) 176 r.Post("/newsletter/signup", s.NewsletterSignup) 177 r.Post("/newsletter/dismiss", s.NewsletterDismiss) 178 179 // special-case handler for serving tangled.org/core 180 r.Get("/core", s.Core()) 181 182 r.Get("/login", s.Login) 183 r.Post("/login", s.Login) 184 r.Post("/logout", s.Logout) 185 186 r.With(middleware.Paginate).Get("/search", s.Search) 187 r.With(middleware.AuthMiddleware(s.oauth)).Get("/search/quick", s.SearchQuick) 188 r.With(middleware.AuthMiddleware(s.oauth)).Get("/search/quick/mobile", s.SearchQuickMobile) 189 190 r.Post("/account/switch", s.SwitchAccount) 191 r.With(middleware.AuthMiddleware(s.oauth)).Delete("/account/{did}", s.RemoveAccount) 192 193 r.Route("/repo", func(r chi.Router) { 194 r.Route("/new", func(r chi.Router) { 195 r.Use(middleware.AuthMiddleware(s.oauth)) 196 r.Get("/", s.NewRepo) 197 r.Post("/", s.NewRepo) 198 }) 199 // r.Post("/import", s.ImportRepo) 200 }) 201 202 r.With(middleware.Paginate).Get("/goodfirstissues", s.GoodFirstIssues) 203 204 r.With(middleware.AuthMiddleware(s.oauth)).Route("/follow", func(r chi.Router) { 205 r.Post("/", s.Follow) 206 r.Delete("/", s.Follow) 207 }) 208 209 r.With(middleware.AuthMiddleware(s.oauth)).Route("/vouch", func(r chi.Router) { 210 r.Post("/", s.Vouch) 211 r.Post("/skip", s.SkipVouchSuggestion) 212 }) 213 214 r.With(middleware.AuthMiddleware(s.oauth)).Route("/star", func(r chi.Router) { 215 r.Post("/", s.Star) 216 r.Delete("/", s.Star) 217 }) 218 219 r.With(middleware.AuthMiddleware(s.oauth)).Route("/react", func(r chi.Router) { 220 r.Post("/", s.React) 221 r.Delete("/", s.React) 222 }) 223 224 r.With(middleware.AuthMiddleware(s.oauth)).Route("/comment", func(r chi.Router) { 225 r.Get("/", s.CommentBodyFragment) 226 r.Get("/edit", s.EditCommentFragment) 227 r.Get("/reply", s.NewReplyCommentFragment) 228 r.Get("/reply/placeholder", s.ReplyPlaceholderFragment) 229 r.Post("/", s.NewComment) 230 r.Patch("/", s.EditComment) 231 r.Delete("/", s.DeleteComment) 232 }) 233 234 r.Get("/profile/popover", s.ProfilePopover) 235 236 r.Route("/profile", func(r chi.Router) { 237 r.Use(middleware.AuthMiddleware(s.oauth)) 238 r.Get("/edit-bio", s.EditBioFragment) 239 r.Get("/edit-pins", s.EditPinsFragment) 240 r.Post("/bio", s.UpdateProfileBio) 241 r.Post("/pins", s.UpdateProfilePins) 242 r.Post("/avatar", s.UploadProfileAvatar) 243 r.Delete("/avatar", s.RemoveProfileAvatar) 244 r.Post("/punchcard", s.UpdateProfilePunchcardSetting) 245 }) 246 247 r.Mount("/settings", s.SettingsRouter()) 248 r.Mount("/strings", s.StringsRouter(mw)) 249 250 r.Mount("/settings/knots", s.KnotsRouter()) 251 r.Mount("/settings/spindles", s.SpindlesRouter()) 252 253 r.Mount("/notifications", s.NotificationsRouter(mw)) 254 r.Mount("/focus", s.FocusRouter(mw)) 255 256 r.Mount("/signup", s.SignupRouter()) 257 r.Mount("/", s.oauth.Router()) 258 259 r.Get("/keys/{user}", s.Keys) 260 r.Get("/terms", s.TermsOfService) 261 r.Get("/privacy", s.PrivacyPolicy) 262 r.Get("/brand", s.Brand) 263 264 r.NotFound(func(w http.ResponseWriter, r *http.Request) { 265 w.WriteHeader(http.StatusNotFound) 266 s.pages.Error404(w) 267 }) 268 return r 269} 270 271// Core serves tangled.org/core go-import meta tags, and redirects 272// to the core repository if accessed normally. 273func (s *State) Core() http.HandlerFunc { 274 return func(w http.ResponseWriter, r *http.Request) { 275 if r.URL.Query().Get("go-get") == "1" { 276 w.Header().Set("Content-Type", "text/html") 277 w.Write([]byte(`<meta name="go-import" content="tangled.org/core git https://tangled.org/@tangled.org/core">`)) 278 return 279 } 280 281 http.Redirect(w, r, "/@tangled.org/core", http.StatusFound) 282 } 283} 284 285func (s *State) SettingsRouter() http.Handler { 286 settings := &settings.Settings{ 287 Db: s.db, 288 OAuth: s.oauth, 289 Pages: s.pages, 290 Config: s.config, 291 CfClient: s.cfClient, 292 Logger: log.SubLogger(s.logger, "settings"), 293 IdResolver: s.idResolver, 294 } 295 296 return settings.Router() 297} 298 299func (s *State) SpindlesRouter() http.Handler { 300 logger := log.SubLogger(s.logger, "spindles") 301 302 spindles := &spindles.Spindles{ 303 Db: s.db, 304 OAuth: s.oauth, 305 Pages: s.pages, 306 Config: s.config, 307 Enforcer: s.enforcer, 308 IdResolver: s.idResolver, 309 Logger: logger, 310 } 311 312 return spindles.Router() 313} 314 315func (s *State) KnotsRouter() http.Handler { 316 logger := log.SubLogger(s.logger, "knots") 317 318 knots := &knots.Knots{ 319 Db: s.db, 320 OAuth: s.oauth, 321 Pages: s.pages, 322 Config: s.config, 323 Enforcer: s.enforcer, 324 Acl: s.aclService, 325 IdResolver: s.idResolver, 326 Knotstream: s.knotstream, 327 Logger: logger, 328 } 329 330 return knots.Router() 331} 332 333func (s *State) StringsRouter(mw *middleware.Middleware) http.Handler { 334 logger := log.SubLogger(s.logger, "strings") 335 336 strs := &avstrings.Strings{ 337 Db: s.db, 338 OAuth: s.oauth, 339 Pages: s.pages, 340 IdResolver: s.idResolver, 341 Notifier: s.notifier, 342 Logger: logger, 343 } 344 345 return strs.Router(mw) 346} 347 348func (s *State) IssuesRouter(mw *middleware.Middleware) http.Handler { 349 issues := issues.New( 350 s.oauth, 351 s.repoResolver, 352 s.aclService, 353 s.pages, 354 s.idResolver, 355 s.mentionsResolver, 356 s.db, 357 s.config, 358 s.notifier, 359 s.validator, 360 s.indexer.Issues, 361 log.SubLogger(s.logger, "issues"), 362 ) 363 return issues.Router(mw) 364} 365 366func (s *State) PullsRouter(mw *middleware.Middleware) http.Handler { 367 pulls := pulls.New( 368 s.oauth, 369 s.repoResolver, 370 s.pages, 371 s.idResolver, 372 s.mentionsResolver, 373 s.db, 374 s.config, 375 s.notifier, 376 s.aclService, 377 s.validator, 378 s.indexer.Pulls, 379 log.SubLogger(s.logger, "pulls"), 380 ) 381 return pulls.Router(mw) 382} 383 384func (s *State) RepoRouter(mw *middleware.Middleware) http.Handler { 385 repo := repo.New( 386 s.oauth, 387 s.repoResolver, 388 s.pages, 389 s.spindlestream, 390 s.idResolver, 391 s.db, 392 s.config, 393 s.notifier, 394 s.enforcer, 395 s.aclService, 396 log.SubLogger(s.logger, "repo"), 397 s.validator, 398 s.cfClient, 399 ) 400 return repo.Router(mw) 401} 402 403func (s *State) PipelinesRouter(mw *middleware.Middleware) http.Handler { 404 pipes := pipelines.New( 405 s.oauth, 406 s.repoResolver, 407 s.pages, 408 s.spindlestream, 409 s.pipelineNotifier, 410 s.idResolver, 411 s.db, 412 s.config, 413 s.enforcer, 414 log.SubLogger(s.logger, "pipelines"), 415 ) 416 return pipes.Router(mw) 417} 418 419func (s *State) LabelsRouter() http.Handler { 420 ls := labels.New( 421 s.oauth, 422 s.pages, 423 s.db, 424 s.validator, 425 s.enforcer, 426 s.notifier, 427 log.SubLogger(s.logger, "labels"), 428 ) 429 return ls.Router() 430} 431 432func (s *State) NotificationsRouter(mw *middleware.Middleware) http.Handler { 433 notifs := notifications.New(s.db, s.oauth, s.pages, log.SubLogger(s.logger, "notifications")) 434 return notifs.Router(mw) 435} 436 437func (s *State) FocusRouter(mw *middleware.Middleware) http.Handler { 438 f := focus.New(s.db, s.oauth, s.idResolver, s.pages, log.SubLogger(s.logger, "focus")) 439 return f.Router(mw) 440} 441 442func (s *State) SignupRouter() http.Handler { 443 sig := signup.New(s.config, s.db, s.posthog, s.idResolver, s.pages, log.SubLogger(s.logger, "signup")) 444 return sig.Router() 445}