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