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.sh/tangled.sh/core/appview/issues" 10 "tangled.sh/tangled.sh/core/appview/middleware" 11 oauthhandler "tangled.sh/tangled.sh/core/appview/oauth/handler" 12 "tangled.sh/tangled.sh/core/appview/pulls" 13 "tangled.sh/tangled.sh/core/appview/repo" 14 "tangled.sh/tangled.sh/core/appview/settings" 15 "tangled.sh/tangled.sh/core/appview/spindles" 16 "tangled.sh/tangled.sh/core/appview/state/userutil" 17 "tangled.sh/tangled.sh/core/log" 18) 19 20func (s *State) Router() http.Handler { 21 router := chi.NewRouter() 22 middleware := middleware.New( 23 s.oauth, 24 s.db, 25 s.enforcer, 26 s.repoResolver, 27 s.idResolver, 28 s.pages, 29 ) 30 31 router.HandleFunc("/*", func(w http.ResponseWriter, r *http.Request) { 32 pat := chi.URLParam(r, "*") 33 if strings.HasPrefix(pat, "did:") || strings.HasPrefix(pat, "@") { 34 s.UserRouter(&middleware).ServeHTTP(w, r) 35 } else { 36 // Check if the first path element is a valid handle without '@' or a flattened DID 37 pathParts := strings.SplitN(pat, "/", 2) 38 if len(pathParts) > 0 { 39 if userutil.IsHandleNoAt(pathParts[0]) { 40 // Redirect to the same path but with '@' prefixed to the handle 41 redirectPath := "@" + pat 42 http.Redirect(w, r, "/"+redirectPath, http.StatusFound) 43 return 44 } else if userutil.IsFlattenedDid(pathParts[0]) { 45 // Redirect to the unflattened DID version 46 unflattenedDid := userutil.UnflattenDid(pathParts[0]) 47 var redirectPath string 48 if len(pathParts) > 1 { 49 redirectPath = unflattenedDid + "/" + pathParts[1] 50 } else { 51 redirectPath = unflattenedDid 52 } 53 http.Redirect(w, r, "/"+redirectPath, http.StatusFound) 54 return 55 } 56 } 57 s.StandardRouter(&middleware).ServeHTTP(w, r) 58 } 59 }) 60 61 return router 62} 63 64func (s *State) UserRouter(mw *middleware.Middleware) http.Handler { 65 r := chi.NewRouter() 66 67 // strip @ from user 68 r.Use(middleware.StripLeadingAt) 69 70 r.With(mw.ResolveIdent()).Route("/{user}", func(r chi.Router) { 71 r.Get("/", s.Profile) 72 73 r.With(mw.ResolveRepo()).Route("/{repo}", func(r chi.Router) { 74 r.Use(mw.GoImport()) 75 76 r.Mount("/", s.RepoRouter(mw)) 77 r.Mount("/issues", s.IssuesRouter(mw)) 78 r.Mount("/pulls", s.PullsRouter(mw)) 79 80 // These routes get proxied to the knot 81 r.Get("/info/refs", s.InfoRefs) 82 r.Post("/git-upload-pack", s.UploadPack) 83 r.Post("/git-receive-pack", s.ReceivePack) 84 85 }) 86 }) 87 88 r.NotFound(func(w http.ResponseWriter, r *http.Request) { 89 s.pages.Error404(w) 90 }) 91 92 return r 93} 94 95func (s *State) StandardRouter(mw *middleware.Middleware) http.Handler { 96 r := chi.NewRouter() 97 98 r.Handle("/static/*", s.pages.Static()) 99 100 r.Get("/", s.Timeline) 101 102 r.Route("/knots", func(r chi.Router) { 103 r.Use(middleware.AuthMiddleware(s.oauth)) 104 r.Get("/", s.Knots) 105 r.Post("/key", s.RegistrationKey) 106 107 r.Route("/{domain}", func(r chi.Router) { 108 r.Post("/init", s.InitKnotServer) 109 r.Get("/", s.KnotServerInfo) 110 r.Route("/member", func(r chi.Router) { 111 r.Use(mw.KnotOwner()) 112 r.Get("/", s.ListMembers) 113 r.Put("/", s.AddMember) 114 r.Delete("/", s.RemoveMember) 115 }) 116 }) 117 }) 118 119 r.Route("/repo", func(r chi.Router) { 120 r.Route("/new", func(r chi.Router) { 121 r.Use(middleware.AuthMiddleware(s.oauth)) 122 r.Get("/", s.NewRepo) 123 r.Post("/", s.NewRepo) 124 }) 125 // r.Post("/import", s.ImportRepo) 126 }) 127 128 r.With(middleware.AuthMiddleware(s.oauth)).Route("/follow", func(r chi.Router) { 129 r.Post("/", s.Follow) 130 r.Delete("/", s.Follow) 131 }) 132 133 r.With(middleware.AuthMiddleware(s.oauth)).Route("/star", func(r chi.Router) { 134 r.Post("/", s.Star) 135 r.Delete("/", s.Star) 136 }) 137 138 r.Route("/profile", func(r chi.Router) { 139 r.Use(middleware.AuthMiddleware(s.oauth)) 140 r.Get("/edit-bio", s.EditBioFragment) 141 r.Get("/edit-pins", s.EditPinsFragment) 142 r.Post("/bio", s.UpdateProfileBio) 143 r.Post("/pins", s.UpdateProfilePins) 144 }) 145 146 r.Mount("/settings", s.SettingsRouter()) 147 r.Mount("/spindles", s.SpindlesRouter()) 148 r.Mount("/", s.OAuthRouter()) 149 150 r.Get("/keys/{user}", s.Keys) 151 152 r.NotFound(func(w http.ResponseWriter, r *http.Request) { 153 s.pages.Error404(w) 154 }) 155 return r 156} 157 158func (s *State) OAuthRouter() http.Handler { 159 store := sessions.NewCookieStore([]byte(s.config.Core.CookieSecret)) 160 oauth := oauthhandler.New(s.config, s.pages, s.idResolver, s.db, s.sess, store, s.oauth, s.enforcer, s.posthog) 161 return oauth.Router() 162} 163 164func (s *State) SettingsRouter() http.Handler { 165 settings := &settings.Settings{ 166 Db: s.db, 167 OAuth: s.oauth, 168 Pages: s.pages, 169 Config: s.config, 170 } 171 172 return settings.Router() 173} 174 175func (s *State) SpindlesRouter() http.Handler { 176 logger := log.New("spindles") 177 178 spindles := &spindles.Spindles{ 179 Db: s.db, 180 OAuth: s.oauth, 181 Pages: s.pages, 182 Config: s.config, 183 Enforcer: s.enforcer, 184 Logger: logger, 185 } 186 187 return spindles.Router() 188} 189 190func (s *State) IssuesRouter(mw *middleware.Middleware) http.Handler { 191 issues := issues.New(s.oauth, s.repoResolver, s.pages, s.idResolver, s.db, s.config, s.posthog) 192 return issues.Router(mw) 193 194} 195 196func (s *State) PullsRouter(mw *middleware.Middleware) http.Handler { 197 pulls := pulls.New(s.oauth, s.repoResolver, s.pages, s.idResolver, s.db, s.config, s.posthog) 198 return pulls.Router(mw) 199} 200 201func (s *State) RepoRouter(mw *middleware.Middleware) http.Handler { 202 repo := repo.New(s.oauth, s.repoResolver, s.pages, s.idResolver, s.db, s.config, s.posthog, s.enforcer) 203 return repo.Router(mw) 204}