this repo has no description
1package web 2 3import ( 4 "log/slog" 5 "net/http" 6 7 "github.com/go-chi/chi/v5" 8 "tangled.org/core/appview/config" 9 "tangled.org/core/appview/db" 10 "tangled.org/core/appview/indexer" 11 "tangled.org/core/appview/mentions" 12 "tangled.org/core/appview/notify" 13 "tangled.org/core/appview/oauth" 14 "tangled.org/core/appview/pages" 15 isvc "tangled.org/core/appview/service/issue" 16 rsvc "tangled.org/core/appview/service/repo" 17 "tangled.org/core/appview/state" 18 "tangled.org/core/appview/validator" 19 "tangled.org/core/appview/web/handler" 20 "tangled.org/core/appview/web/middleware" 21 "tangled.org/core/idresolver" 22 "tangled.org/core/rbac" 23) 24 25// Rules 26// - Use single function for each endpoints (unless it doesn't make sense.) 27// - Name handler files following the related path (ancestor paths can be 28// trimmed.) 29// - Pass dependencies to each handlers, don't create structs with shared 30// dependencies unless it serves some domain-specific roles like 31// service/issue. Same rule goes to middlewares. 32 33// RouterFromState creates a web router from `state.State`. This exist to 34// bridge between legacy web routers under `State` and new architecture 35func RouterFromState(s *state.State) http.Handler { 36 config, db, enforcer, idResolver, refResolver, indexer, logger, notifier, oauth, pages, validator := s.Expose() 37 38 return Router( 39 logger, 40 config, 41 db, 42 enforcer, 43 idResolver, 44 refResolver, 45 indexer, 46 notifier, 47 oauth, 48 pages, 49 validator, 50 s, 51 ) 52} 53 54func Router( 55 // NOTE: put base dependencies (db, idResolver, oauth etc) 56 logger *slog.Logger, 57 config *config.Config, 58 db *db.DB, 59 enforcer *rbac.Enforcer, 60 idResolver *idresolver.Resolver, 61 mentionsResolver *mentions.Resolver, 62 indexer *indexer.Indexer, 63 notifier notify.Notifier, 64 oauth *oauth.OAuth, 65 pages *pages.Pages, 66 validator *validator.Validator, 67 // to use legacy web handlers. will be removed later 68 s *state.State, 69) http.Handler { 70 repo := rsvc.NewService( 71 logger, 72 config, 73 db, 74 enforcer, 75 ) 76 issue := isvc.NewService( 77 logger, 78 config, 79 db, 80 enforcer, 81 notifier, 82 idResolver, 83 mentionsResolver, 84 indexer.Issues, 85 validator, 86 ) 87 88 i := s.ExposeIssue() 89 90 r := chi.NewRouter() 91 92 mw := s.Middleware() 93 auth := middleware.AuthMiddleware() 94 95 r.Use(middleware.WithLogger(logger)) 96 r.Use(middleware.WithSession(oauth)) 97 98 r.Use(middleware.Normalize()) 99 100 r.Get("/favicon.svg", s.Favicon) 101 r.Get("/favicon.ico", s.Favicon) 102 r.Get("/pwa-manifest.json", s.PWAManifest) 103 r.Get("/robots.txt", s.RobotsTxt) 104 105 r.Handle("/static/*", pages.Static()) 106 107 r.Get("/", s.HomeOrTimeline) 108 r.Get("/timeline", s.Timeline) 109 r.Get("/upgradeBanner", s.UpgradeBanner) 110 111 r.Get("/terms", s.TermsOfService) 112 r.Get("/privacy", s.PrivacyPolicy) 113 r.Get("/brand", s.Brand) 114 // special-case handler for serving tangled.org/core 115 r.Get("/core", s.Core()) 116 117 r.Get("/login", s.Login) 118 r.Post("/login", s.Login) 119 r.Post("/logout", s.Logout) 120 121 r.Get("/goodfirstissues", s.GoodFirstIssues) 122 123 r.With(auth).Get("/repo/new", s.NewRepo) 124 r.With(auth).Post("/repo/new", s.NewRepo) 125 126 r.With(auth).Post("/follow", s.Follow) 127 r.With(auth).Delete("/follow", s.Follow) 128 129 r.With(auth).Post("/star", s.Star) 130 r.With(auth).Delete("/star", s.Star) 131 132 r.With(auth).Post("/react", s.React) 133 r.With(auth).Delete("/react", s.React) 134 135 r.With(auth).Get("/profile/edit-bio", s.EditBioFragment) 136 r.With(auth).Get("/profile/edit-pins", s.EditPinsFragment) 137 r.With(auth).Post("/profile/bio", s.UpdateProfileBio) 138 r.With(auth).Post("/profile/pins", s.UpdateProfilePins) 139 140 r.Mount("/settings", s.SettingsRouter()) 141 r.Mount("/strings", s.StringsRouter(mw)) 142 r.Mount("/settings/knots", s.KnotsRouter()) 143 r.Mount("/settings/spindles", s.SpindlesRouter()) 144 r.Mount("/notifications", s.NotificationsRouter(mw)) 145 146 r.Mount("/signup", s.SignupRouter()) 147 r.Get("/oauth/client-metadata.json", handler.OauthClientMetadata(oauth)) 148 r.Get("/oauth/jwks.json", handler.OauthJwks(oauth)) 149 r.Get("/oauth/callback", oauth.Callback) 150 151 // special-case handler. should replace with xrpc later 152 r.Get("/keys/{user}", s.Keys) 153 154 r.HandleFunc("/@*", func(w http.ResponseWriter, r *http.Request) { 155 http.Redirect(w, r, "/"+chi.URLParam(r, "*"), http.StatusFound) 156 }) 157 158 r.Route("/{user}", func(r chi.Router) { 159 r.Use(middleware.EnsureDidOrHandle(pages)) 160 r.Use(middleware.ResolveIdent(idResolver, pages)) 161 162 r.Get("/", s.Profile) 163 r.Get("/feed.atom", s.AtomFeedPage) 164 165 r.Route("/{repo}", func(r chi.Router) { 166 r.Use(middleware.ResolveRepo(db, pages)) 167 168 r.Mount("/", s.RepoRouter(mw)) 169 170 // /{user}/{repo}/issues/* 171 r.With(middleware.Paginate).Get("/issues", handler.RepoIssues(issue, repo, pages, db)) 172 r.With(auth).Get("/issues/new", handler.NewIssue(repo, pages)) 173 r.With(auth).Post("/issues/new", handler.NewIssuePost(issue, pages)) 174 r.Route("/issues/{issue}", func(r chi.Router) { 175 r.Use(middleware.ResolveIssue(db, pages)) 176 177 r.Get("/", handler.Issue(issue, repo, pages, db)) 178 r.Get("/opengraph", i.IssueOpenGraphSummary) 179 180 r.With(auth).Delete("/", handler.IssueDelete(issue, pages)) 181 182 r.With(auth).Get("/edit", handler.IssueEdit(issue, repo, pages)) 183 r.With(auth).Post("/edit", handler.IssueEditPost(issue, pages)) 184 185 r.With(auth).Post("/close", handler.CloseIssue(issue, pages)) 186 r.With(auth).Post("/reopen", handler.ReopenIssue(issue, pages)) 187 188 r.With(auth).Post("/comment", i.NewIssueComment) 189 r.With(auth).Route("/comment/{commentId}/", func(r chi.Router) { 190 r.Get("/", i.IssueComment) 191 r.Delete("/", i.DeleteIssueComment) 192 r.Get("/edit", i.EditIssueComment) 193 r.Post("/edit", i.EditIssueComment) 194 r.Get("/reply", i.ReplyIssueComment) 195 r.Get("/replyPlaceholder", i.ReplyIssueCommentPlaceholder) 196 }) 197 }) 198 199 r.Mount("/pulls", s.PullsRouter(mw)) 200 r.Mount("/pipelines", s.PipelinesRouter()) 201 r.Mount("/labels", s.LabelsRouter()) 202 203 // These routes get proxied to the knot 204 r.Get("/info/refs", s.InfoRefs) 205 r.Post("/git-upload-pack", s.UploadPack) 206 r.Post("/git-receive-pack", s.ReceivePack) 207 }) 208 }) 209 210 r.NotFound(func(w http.ResponseWriter, r *http.Request) { 211 pages.Error404(w) 212 }) 213 214 return r 215}