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