forked from tangled.org/core
this repo has no description
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/middleware" 10 oauthhandler "tangled.sh/tangled.sh/core/appview/oauth/handler" 11 "tangled.sh/tangled.sh/core/appview/pulls" 12 "tangled.sh/tangled.sh/core/appview/settings" 13 "tangled.sh/tangled.sh/core/appview/state/userutil" 14) 15 16func (s *State) Router() http.Handler { 17 router := chi.NewRouter() 18 middleware := middleware.New( 19 s.oauth, 20 s.db, 21 s.enforcer, 22 s.repoResolver, 23 s.resolver, 24 s.pages, 25 ) 26 27 router.HandleFunc("/*", func(w http.ResponseWriter, r *http.Request) { 28 pat := chi.URLParam(r, "*") 29 if strings.HasPrefix(pat, "did:") || strings.HasPrefix(pat, "@") { 30 s.UserRouter(&middleware).ServeHTTP(w, r) 31 } else { 32 // Check if the first path element is a valid handle without '@' or a flattened DID 33 pathParts := strings.SplitN(pat, "/", 2) 34 if len(pathParts) > 0 { 35 if userutil.IsHandleNoAt(pathParts[0]) { 36 // Redirect to the same path but with '@' prefixed to the handle 37 redirectPath := "@" + pat 38 http.Redirect(w, r, "/"+redirectPath, http.StatusFound) 39 return 40 } else if userutil.IsFlattenedDid(pathParts[0]) { 41 // Redirect to the unflattened DID version 42 unflattenedDid := userutil.UnflattenDid(pathParts[0]) 43 var redirectPath string 44 if len(pathParts) > 1 { 45 redirectPath = unflattenedDid + "/" + pathParts[1] 46 } else { 47 redirectPath = unflattenedDid 48 } 49 http.Redirect(w, r, "/"+redirectPath, http.StatusFound) 50 return 51 } 52 } 53 s.StandardRouter(&middleware).ServeHTTP(w, r) 54 } 55 }) 56 57 return router 58} 59 60func (s *State) UserRouter(mw *middleware.Middleware) http.Handler { 61 r := chi.NewRouter() 62 63 // strip @ from user 64 r.Use(middleware.StripLeadingAt) 65 66 r.With(mw.ResolveIdent()).Route("/{user}", func(r chi.Router) { 67 r.Get("/", s.Profile) 68 69 r.With(mw.ResolveRepo()).Route("/{repo}", func(r chi.Router) { 70 r.Use(mw.GoImport()) 71 72 r.Get("/", s.RepoIndex) 73 r.Get("/commits/{ref}", s.RepoLog) 74 r.Route("/tree/{ref}", func(r chi.Router) { 75 r.Get("/", s.RepoIndex) 76 r.Get("/*", s.RepoTree) 77 }) 78 r.Get("/commit/{ref}", s.RepoCommit) 79 r.Get("/branches", s.RepoBranches) 80 r.Route("/tags", func(r chi.Router) { 81 r.Get("/", s.RepoTags) 82 r.Route("/{tag}", func(r chi.Router) { 83 r.Use(middleware.AuthMiddleware(s.oauth)) 84 // require auth to download for now 85 r.Get("/download/{file}", s.DownloadArtifact) 86 87 // require repo:push to upload or delete artifacts 88 // 89 // additionally: only the uploader can truly delete an artifact 90 // (record+blob will live on their pds) 91 r.Group(func(r chi.Router) { 92 r.With(mw.RepoPermissionMiddleware("repo:push")) 93 r.Post("/upload", s.AttachArtifact) 94 r.Delete("/{file}", s.DeleteArtifact) 95 }) 96 }) 97 }) 98 r.Get("/blob/{ref}/*", s.RepoBlob) 99 r.Get("/raw/{ref}/*", s.RepoBlobRaw) 100 101 r.Route("/issues", func(r chi.Router) { 102 r.With(middleware.Paginate).Get("/", s.RepoIssues) 103 r.Get("/{issue}", s.RepoSingleIssue) 104 105 r.Group(func(r chi.Router) { 106 r.Use(middleware.AuthMiddleware(s.oauth)) 107 r.Get("/new", s.NewIssue) 108 r.Post("/new", s.NewIssue) 109 r.Post("/{issue}/comment", s.NewIssueComment) 110 r.Route("/{issue}/comment/{comment_id}/", func(r chi.Router) { 111 r.Get("/", s.IssueComment) 112 r.Delete("/", s.DeleteIssueComment) 113 r.Get("/edit", s.EditIssueComment) 114 r.Post("/edit", s.EditIssueComment) 115 }) 116 r.Post("/{issue}/close", s.CloseIssue) 117 r.Post("/{issue}/reopen", s.ReopenIssue) 118 }) 119 }) 120 121 r.Route("/fork", func(r chi.Router) { 122 r.Use(middleware.AuthMiddleware(s.oauth)) 123 r.Get("/", s.ForkRepo) 124 r.Post("/", s.ForkRepo) 125 r.With(mw.RepoPermissionMiddleware("repo:owner")).Route("/sync", func(r chi.Router) { 126 r.Post("/", s.SyncRepoFork) 127 }) 128 }) 129 130 r.Route("/compare", func(r chi.Router) { 131 r.Get("/", s.RepoCompareNew) // start an new comparison 132 133 // we have to wildcard here since we want to support GitHub's compare syntax 134 // /compare/{ref1}...{ref2} 135 // for example: 136 // /compare/master...some/feature 137 // /compare/master...example.com:another/feature <- this is a fork 138 r.Get("/{base}/{head}", s.RepoCompare) 139 r.Get("/*", s.RepoCompare) 140 }) 141 142 r.Mount("/pulls", s.PullsRouter(mw)) 143 144 // These routes get proxied to the knot 145 r.Get("/info/refs", s.InfoRefs) 146 r.Post("/git-upload-pack", s.UploadPack) 147 r.Post("/git-receive-pack", s.ReceivePack) 148 149 // settings routes, needs auth 150 r.Group(func(r chi.Router) { 151 r.Use(middleware.AuthMiddleware(s.oauth)) 152 // repo description can only be edited by owner 153 r.With(mw.RepoPermissionMiddleware("repo:owner")).Route("/description", func(r chi.Router) { 154 r.Put("/", s.RepoDescription) 155 r.Get("/", s.RepoDescription) 156 r.Get("/edit", s.RepoDescriptionEdit) 157 }) 158 r.With(mw.RepoPermissionMiddleware("repo:settings")).Route("/settings", func(r chi.Router) { 159 r.Get("/", s.RepoSettings) 160 r.With(mw.RepoPermissionMiddleware("repo:invite")).Put("/collaborator", s.AddCollaborator) 161 r.With(mw.RepoPermissionMiddleware("repo:delete")).Delete("/delete", s.DeleteRepo) 162 r.Put("/branches/default", s.SetDefaultBranch) 163 }) 164 }) 165 }) 166 }) 167 168 r.NotFound(func(w http.ResponseWriter, r *http.Request) { 169 s.pages.Error404(w) 170 }) 171 172 return r 173} 174 175func (s *State) StandardRouter(mw *middleware.Middleware) http.Handler { 176 r := chi.NewRouter() 177 178 r.Handle("/static/*", s.pages.Static()) 179 180 r.Get("/", s.Timeline) 181 182 r.With(middleware.AuthMiddleware(s.oauth)).Post("/logout", s.Logout) 183 184 r.Route("/knots", func(r chi.Router) { 185 r.Use(middleware.AuthMiddleware(s.oauth)) 186 r.Get("/", s.Knots) 187 r.Post("/key", s.RegistrationKey) 188 189 r.Route("/{domain}", func(r chi.Router) { 190 r.Post("/init", s.InitKnotServer) 191 r.Get("/", s.KnotServerInfo) 192 r.Route("/member", func(r chi.Router) { 193 r.Use(mw.KnotOwner()) 194 r.Get("/", s.ListMembers) 195 r.Put("/", s.AddMember) 196 r.Delete("/", s.RemoveMember) 197 }) 198 }) 199 }) 200 201 r.Route("/repo", func(r chi.Router) { 202 r.Route("/new", func(r chi.Router) { 203 r.Use(middleware.AuthMiddleware(s.oauth)) 204 r.Get("/", s.NewRepo) 205 r.Post("/", s.NewRepo) 206 }) 207 // r.Post("/import", s.ImportRepo) 208 }) 209 210 r.With(middleware.AuthMiddleware(s.oauth)).Route("/follow", func(r chi.Router) { 211 r.Post("/", s.Follow) 212 r.Delete("/", s.Follow) 213 }) 214 215 r.With(middleware.AuthMiddleware(s.oauth)).Route("/star", func(r chi.Router) { 216 r.Post("/", s.Star) 217 r.Delete("/", s.Star) 218 }) 219 220 r.Route("/profile", func(r chi.Router) { 221 r.Use(middleware.AuthMiddleware(s.oauth)) 222 r.Get("/edit-bio", s.EditBioFragment) 223 r.Get("/edit-pins", s.EditPinsFragment) 224 r.Post("/bio", s.UpdateProfileBio) 225 r.Post("/pins", s.UpdateProfilePins) 226 }) 227 228 r.Mount("/settings", s.SettingsRouter()) 229 r.Mount("/", s.OAuthRouter()) 230 231 r.Get("/keys/{user}", s.Keys) 232 233 r.NotFound(func(w http.ResponseWriter, r *http.Request) { 234 s.pages.Error404(w) 235 }) 236 return r 237} 238 239func (s *State) OAuthRouter() http.Handler { 240 oauth := &oauthhandler.OAuthHandler{ 241 Config: s.config, 242 Pages: s.pages, 243 Resolver: s.resolver, 244 Db: s.db, 245 Store: sessions.NewCookieStore([]byte(s.config.Core.CookieSecret)), 246 OAuth: s.oauth, 247 Enforcer: s.enforcer, 248 Posthog: s.posthog, 249 } 250 251 return oauth.Router() 252} 253 254func (s *State) SettingsRouter() http.Handler { 255 settings := &settings.Settings{ 256 Db: s.db, 257 OAuth: s.oauth, 258 Pages: s.pages, 259 Config: s.config, 260 } 261 262 return settings.Router() 263} 264 265func (s *State) PullsRouter(mw *middleware.Middleware) http.Handler { 266 pulls := pulls.New(s.oauth, s.repoResolver, s.pages, s.resolver, s.db, s.config) 267 return pulls.Router(mw) 268}