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}