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 oauth "tangled.sh/tangled.sh/core/appview/oauth/handler"
11 "tangled.sh/tangled.sh/core/appview/pulls"
12 "tangled.sh/tangled.sh/core/appview/repo"
13 "tangled.sh/tangled.sh/core/appview/settings"
14 "tangled.sh/tangled.sh/core/appview/state/userutil"
15)
16
17func (s *State) Router() http.Handler {
18 router := chi.NewRouter()
19 middleware := middleware.New(
20 s.oauth,
21 s.db,
22 s.enforcer,
23 s.repoResolver,
24 s.idResolver,
25 s.pages,
26 )
27
28 router.HandleFunc("/*", func(w http.ResponseWriter, r *http.Request) {
29 pat := chi.URLParam(r, "*")
30 if strings.HasPrefix(pat, "did:") || strings.HasPrefix(pat, "@") {
31 s.UserRouter(&middleware).ServeHTTP(w, r)
32 } else {
33 // Check if the first path element is a valid handle without '@' or a flattened DID
34 pathParts := strings.SplitN(pat, "/", 2)
35 if len(pathParts) > 0 {
36 if userutil.IsHandleNoAt(pathParts[0]) {
37 // Redirect to the same path but with '@' prefixed to the handle
38 redirectPath := "@" + pat
39 http.Redirect(w, r, "/"+redirectPath, http.StatusFound)
40 return
41 } else if userutil.IsFlattenedDid(pathParts[0]) {
42 // Redirect to the unflattened DID version
43 unflattenedDid := userutil.UnflattenDid(pathParts[0])
44 var redirectPath string
45 if len(pathParts) > 1 {
46 redirectPath = unflattenedDid + "/" + pathParts[1]
47 } else {
48 redirectPath = unflattenedDid
49 }
50 http.Redirect(w, r, "/"+redirectPath, http.StatusFound)
51 return
52 }
53 }
54 s.StandardRouter(&middleware).ServeHTTP(w, r)
55 }
56 })
57
58 return router
59}
60
61func (s *State) UserRouter(mw *middleware.Middleware) http.Handler {
62 r := chi.NewRouter()
63
64 // strip @ from user
65 r.Use(middleware.StripLeadingAt)
66
67 r.With(mw.ResolveIdent()).Route("/{user}", func(r chi.Router) {
68 r.Get("/", s.Profile)
69
70 r.With(mw.ResolveRepo()).Route("/{repo}", func(r chi.Router) {
71 r.Use(mw.GoImport())
72
73 r.Mount("/", s.RepoRouter(mw))
74
75 r.Mount("/pulls", s.PullsRouter(mw))
76
77 // These routes get proxied to the knot
78 r.Get("/info/refs", s.InfoRefs)
79 r.Post("/git-upload-pack", s.UploadPack)
80 r.Post("/git-receive-pack", s.ReceivePack)
81
82 })
83 })
84
85 r.NotFound(func(w http.ResponseWriter, r *http.Request) {
86 s.pages.Error404(w)
87 })
88
89 return r
90}
91
92func (s *State) StandardRouter(mw *middleware.Middleware) http.Handler {
93 r := chi.NewRouter()
94
95 r.Handle("/static/*", s.pages.Static())
96
97 r.Get("/", s.Timeline)
98
99 r.With(middleware.AuthMiddleware(s.oauth)).Post("/logout", s.Logout)
100
101 r.Route("/knots", func(r chi.Router) {
102 r.Use(middleware.AuthMiddleware(s.oauth))
103 r.Get("/", s.Knots)
104 r.Post("/key", s.RegistrationKey)
105
106 r.Route("/{domain}", func(r chi.Router) {
107 r.Post("/init", s.InitKnotServer)
108 r.Get("/", s.KnotServerInfo)
109 r.Route("/member", func(r chi.Router) {
110 r.Use(mw.KnotOwner())
111 r.Get("/", s.ListMembers)
112 r.Put("/", s.AddMember)
113 r.Delete("/", s.RemoveMember)
114 })
115 })
116 })
117
118 r.Route("/repo", func(r chi.Router) {
119 r.Route("/new", func(r chi.Router) {
120 r.Use(middleware.AuthMiddleware(s.oauth))
121 r.Get("/", s.NewRepo)
122 r.Post("/", s.NewRepo)
123 })
124 // r.Post("/import", s.ImportRepo)
125 })
126
127 r.With(middleware.AuthMiddleware(s.oauth)).Route("/follow", func(r chi.Router) {
128 r.Post("/", s.Follow)
129 r.Delete("/", s.Follow)
130 })
131
132 r.With(middleware.AuthMiddleware(s.oauth)).Route("/star", func(r chi.Router) {
133 r.Post("/", s.Star)
134 r.Delete("/", s.Star)
135 })
136
137 r.Route("/profile", func(r chi.Router) {
138 r.Use(middleware.AuthMiddleware(s.oauth))
139 r.Get("/edit-bio", s.EditBioFragment)
140 r.Get("/edit-pins", s.EditPinsFragment)
141 r.Post("/bio", s.UpdateProfileBio)
142 r.Post("/pins", s.UpdateProfilePins)
143 })
144
145 r.Mount("/settings", s.SettingsRouter())
146 r.Mount("/", s.OAuthRouter())
147
148 r.Get("/keys/{user}", s.Keys)
149
150 r.NotFound(func(w http.ResponseWriter, r *http.Request) {
151 s.pages.Error404(w)
152 })
153 return r
154}
155
156func (s *State) OAuthRouter() http.Handler {
157 store := sessions.NewCookieStore([]byte(s.config.Core.CookieSecret))
158 oauth := oauth.New(s.config, s.pages, s.idResolver, s.db, store, s.oauth, s.enforcer, s.posthog)
159 return oauth.Router()
160}
161
162func (s *State) SettingsRouter() http.Handler {
163 settings := &settings.Settings{
164 Db: s.db,
165 OAuth: s.oauth,
166 Pages: s.pages,
167 Config: s.config,
168 }
169
170 return settings.Router()
171}
172
173func (s *State) PullsRouter(mw *middleware.Middleware) http.Handler {
174 pulls := pulls.New(s.oauth, s.repoResolver, s.pages, s.idResolver, s.db, s.config)
175 return pulls.Router(mw)
176}
177
178func (s *State) RepoRouter(mw *middleware.Middleware) http.Handler {
179 repo := repo.New(s.oauth, s.repoResolver, s.pages, s.idResolver, s.db, s.config, s.posthog, s.enforcer)
180 return repo.Router(mw)
181}