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