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 "github.com/gorilla/sessions" 9 "tangled.org/core/appview/issues" 10 "tangled.org/core/appview/knots" 11 "tangled.org/core/appview/labels" 12 "tangled.org/core/appview/middleware" 13 "tangled.org/core/appview/notifications" 14 oauthhandler "tangled.org/core/appview/oauth/handler" 15 "tangled.org/core/appview/pipelines" 16 "tangled.org/core/appview/pulls" 17 "tangled.org/core/appview/repo" 18 "tangled.org/core/appview/settings" 19 "tangled.org/core/appview/signup" 20 "tangled.org/core/appview/spindles" 21 "tangled.org/core/appview/state/userutil" 22 avstrings "tangled.org/core/appview/strings" 23 "tangled.org/core/log" 24) 25 26func (s *State) Router() http.Handler { 27 router := chi.NewRouter() 28 middleware := middleware.New( 29 s.oauth, 30 s.db, 31 s.enforcer, 32 s.repoResolver, 33 s.idResolver, 34 s.pages, 35 ) 36 37 router.Use(middleware.TryRefreshSession()) 38 router.Get("/favicon.svg", s.Favicon) 39 router.Get("/favicon.ico", s.Favicon) 40 41 userRouter := s.UserRouter(&middleware) 42 standardRouter := s.StandardRouter(&middleware) 43 44 router.HandleFunc("/*", func(w http.ResponseWriter, r *http.Request) { 45 pat := chi.URLParam(r, "*") 46 if strings.HasPrefix(pat, "did:") || strings.HasPrefix(pat, "@") { 47 userRouter.ServeHTTP(w, r) 48 } else { 49 // Check if the first path element is a valid handle without '@' or a flattened DID 50 pathParts := strings.SplitN(pat, "/", 2) 51 if len(pathParts) > 0 { 52 if userutil.IsHandleNoAt(pathParts[0]) { 53 // Redirect to the same path but with '@' prefixed to the handle 54 redirectPath := "@" + pat 55 http.Redirect(w, r, "/"+redirectPath, http.StatusFound) 56 return 57 } else if userutil.IsFlattenedDid(pathParts[0]) { 58 // Redirect to the unflattened DID version 59 unflattenedDid := userutil.UnflattenDid(pathParts[0]) 60 var redirectPath string 61 if len(pathParts) > 1 { 62 redirectPath = unflattenedDid + "/" + pathParts[1] 63 } else { 64 redirectPath = unflattenedDid 65 } 66 http.Redirect(w, r, "/"+redirectPath, http.StatusFound) 67 return 68 } 69 } 70 standardRouter.ServeHTTP(w, r) 71 } 72 }) 73 74 return router 75} 76 77func (s *State) UserRouter(mw *middleware.Middleware) http.Handler { 78 r := chi.NewRouter() 79 80 r.With(mw.ResolveIdent()).Route("/{user}", func(r chi.Router) { 81 r.Get("/", s.Profile) 82 r.Get("/feed.atom", s.AtomFeedPage) 83 84 // redirect /@handle/repo.git -> /@handle/repo 85 r.Get("/{repo}.git", func(w http.ResponseWriter, r *http.Request) { 86 nonDotGitPath := strings.TrimSuffix(r.URL.Path, ".git") 87 http.Redirect(w, r, nonDotGitPath, http.StatusMovedPermanently) 88 }) 89 90 r.With(mw.ResolveRepo()).Route("/{repo}", func(r chi.Router) { 91 r.Use(mw.GoImport()) 92 r.Mount("/", s.RepoRouter(mw)) 93 r.Mount("/issues", s.IssuesRouter(mw)) 94 r.Mount("/pulls", s.PullsRouter(mw)) 95 r.Mount("/pipelines", s.PipelinesRouter(mw)) 96 r.Mount("/labels", s.LabelsRouter(mw)) 97 98 // These routes get proxied to the knot 99 r.Get("/info/refs", s.InfoRefs) 100 r.Post("/git-upload-pack", s.UploadPack) 101 r.Post("/git-receive-pack", s.ReceivePack) 102 103 }) 104 }) 105 106 r.NotFound(func(w http.ResponseWriter, r *http.Request) { 107 s.pages.Error404(w) 108 }) 109 110 return r 111} 112 113func (s *State) StandardRouter(mw *middleware.Middleware) http.Handler { 114 r := chi.NewRouter() 115 116 r.Handle("/static/*", s.pages.Static()) 117 118 r.Get("/", s.HomeOrTimeline) 119 r.Get("/timeline", s.Timeline) 120 r.Get("/upgradeBanner", s.UpgradeBanner) 121 122 // special-case handler for serving tangled.org/core 123 r.Get("/core", s.Core()) 124 125 r.Route("/repo", func(r chi.Router) { 126 r.Route("/new", func(r chi.Router) { 127 r.Use(middleware.AuthMiddleware(s.oauth)) 128 r.Get("/", s.NewRepo) 129 r.Post("/", s.NewRepo) 130 }) 131 // r.Post("/import", s.ImportRepo) 132 }) 133 134 r.With(middleware.AuthMiddleware(s.oauth)).Route("/follow", func(r chi.Router) { 135 r.Post("/", s.Follow) 136 r.Delete("/", s.Follow) 137 }) 138 139 r.With(middleware.AuthMiddleware(s.oauth)).Route("/star", func(r chi.Router) { 140 r.Post("/", s.Star) 141 r.Delete("/", s.Star) 142 }) 143 144 r.With(middleware.AuthMiddleware(s.oauth)).Route("/react", func(r chi.Router) { 145 r.Post("/", s.React) 146 r.Delete("/", s.React) 147 }) 148 149 r.Route("/profile", func(r chi.Router) { 150 r.Use(middleware.AuthMiddleware(s.oauth)) 151 r.Get("/edit-bio", s.EditBioFragment) 152 r.Get("/edit-pins", s.EditPinsFragment) 153 r.Post("/bio", s.UpdateProfileBio) 154 r.Post("/pins", s.UpdateProfilePins) 155 }) 156 157 r.Mount("/settings", s.SettingsRouter()) 158 r.Mount("/strings", s.StringsRouter(mw)) 159 r.Mount("/knots", s.KnotsRouter()) 160 r.Mount("/spindles", s.SpindlesRouter()) 161 r.Mount("/notifications", s.NotificationsRouter(mw)) 162 163 r.Mount("/signup", s.SignupRouter()) 164 r.Mount("/", s.OAuthRouter()) 165 166 r.Get("/keys/{user}", s.Keys) 167 r.Get("/terms", s.TermsOfService) 168 r.Get("/privacy", s.PrivacyPolicy) 169 170 r.NotFound(func(w http.ResponseWriter, r *http.Request) { 171 s.pages.Error404(w) 172 }) 173 return r 174} 175 176// Core serves tangled.org/core go-import meta tags, and redirects 177// to the core repository if accessed normally. 178func (s *State) Core() http.HandlerFunc { 179 return func(w http.ResponseWriter, r *http.Request) { 180 if r.URL.Query().Get("go-get") == "1" { 181 w.Header().Set("Content-Type", "text/html") 182 w.Write([]byte(`<meta name="go-import" content="tangled.org/core git https://tangled.org/@tangled.org/core">`)) 183 return 184 } 185 186 http.Redirect(w, r, "/@tangled.org/core", http.StatusFound) 187 } 188} 189 190func (s *State) OAuthRouter() http.Handler { 191 store := sessions.NewCookieStore([]byte(s.config.Core.CookieSecret)) 192 oauth := oauthhandler.New(s.config, s.pages, s.idResolver, s.db, s.sess, store, s.oauth, s.enforcer, s.posthog) 193 return oauth.Router() 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.New("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.New("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.New("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(s.oauth, s.repoResolver, s.pages, s.idResolver, s.db, s.config, s.notifier, s.validator) 257 return issues.Router(mw) 258} 259 260func (s *State) PullsRouter(mw *middleware.Middleware) http.Handler { 261 pulls := pulls.New(s.oauth, s.repoResolver, s.pages, s.idResolver, s.db, s.config, s.notifier) 262 return pulls.Router(mw) 263} 264 265func (s *State) RepoRouter(mw *middleware.Middleware) http.Handler { 266 logger := log.New("repo") 267 repo := repo.New(s.oauth, s.repoResolver, s.pages, s.spindlestream, s.idResolver, s.db, s.config, s.notifier, s.enforcer, logger, s.validator) 268 return repo.Router(mw) 269} 270 271func (s *State) PipelinesRouter(mw *middleware.Middleware) http.Handler { 272 pipes := pipelines.New(s.oauth, s.repoResolver, s.pages, s.spindlestream, s.idResolver, s.db, s.config, s.enforcer) 273 return pipes.Router(mw) 274} 275 276func (s *State) LabelsRouter(mw *middleware.Middleware) http.Handler { 277 ls := labels.New(s.oauth, s.pages, s.db, s.validator, s.enforcer) 278 return ls.Router(mw) 279} 280 281func (s *State) NotificationsRouter(mw *middleware.Middleware) http.Handler { 282 notifs := notifications.New(s.db, s.oauth, s.pages) 283 return notifs.Router(mw) 284} 285 286func (s *State) SignupRouter() http.Handler { 287 logger := log.New("signup") 288 289 sig := signup.New(s.config, s.db, s.posthog, s.idResolver, s.pages, logger) 290 return sig.Router() 291}