main
rb 222 lines 11.5 KB
Raw
1 Rails.application.routes.draw do
2 root "pages#home"
3
4 # Static pages — declared before the "/:username" matcher so these literal
5 # slugs aren't read as profiles.
6 get "/code", to: "pages#code", as: :code
7 get "/about", to: "pages#about", as: :about
8 get "/contact", to: "pages#contact", as: :contact
9 get "/privacy", to: "pages#privacy", as: :privacy
10 get "/terms", to: "pages#terms", as: :terms
11
12 # Public changelog: an index and one page per release. The version constraint
13 # (v<major>.<minor>.<patch>) keeps "/changelog/anything-else" from matching,
14 # and both are declared before the "/:username" matcher so the literal
15 # "changelog" slug isn't read as a profile.
16 get "/changelog", to: "changelog#index", as: :changelog
17 get "/changelog/:version", to: "changelog#show", as: :changelog_version,
18 constraints: { version: /v\d+\.\d+\.\d+/ }
19
20 # XML sitemap for search engines (referenced from public/robots.txt).
21 get "/sitemap.xml", to: "sitemaps#index", defaults: { format: "xml" }, as: :sitemap
22
23 # Open Graph share-card images (1200×630 PNG). Rendered server-side and cached
24 # so social/crawler unfurls get a real preview card. Declared before the
25 # "/:username" matcher; the dotted-name constraint mirrors the repo routes so
26 # names like "Qwen2.5-GGUF" survive. `format: false` keeps the literal ".png".
27 get "/og.png", to: "og_images#default", as: :og_default_image, format: false
28 get "/og/:username/:repository.png", to: "og_images#show", as: :og_image,
29 constraints: { repository: /[^\/.][^\/]*/ }, format: false
30
31 # Dev panel — only mounted in development
32 if Rails.env.development?
33 namespace :dev do
34 get "environment", to: "panel#show"
35 put "environment", to: "panel#update"
36 end
37 end
38
39 # Auth — email/password form
40 get "/auth", to: "sessions#new", as: :signin
41 post "/auth", to: "sessions#create", as: :auth_create
42 delete "/auth/signout", to: "sessions#destroy", as: :signout
43
44 # Auth — "Continue with GitHub" / "Continue with Google" (brokered by smbCloud Auth)
45 get "/auth/github", to: "oauth/github#start", as: :github_auth
46 get "/auth/github/callback", to: "oauth/github#callback", as: :github_auth_callback
47 get "/auth/google", to: "oauth/google#start", as: :google_auth
48 get "/auth/google/callback", to: "oauth/google#callback", as: :google_auth_callback
49
50 # Signup
51 get "/auth/signup", to: "registrations#new", as: :signup
52 post "/auth/signup", to: "registrations#create", as: :signup_create
53
54 # Email confirmation landing — smbCloud confirms the token server-side, then
55 # redirects here with ?status=success or ?status=failed&message=...
56 get "/auth/confirmed", to: "confirmations#confirmed", as: :auth_confirmed
57
58 # Resend confirmation email
59 get "/auth/confirmation/resend", to: "confirmations#new", as: :resend_confirmation
60 post "/auth/confirmation/resend", to: "confirmations#create"
61
62 # Reset password — request reset instructions by email
63 get "/auth/password/reset", to: "passwords#new", as: :reset_password
64 post "/auth/password/reset", to: "passwords#create"
65
66 # Reset password — set a new password from the email link
67 # (smbCloud redirects here with ?reset_password_token=...)
68 get "/auth/password/edit", to: "passwords#edit", as: :edit_password
69 post "/auth/password/edit", to: "passwords#update"
70
71 # Discovery surfaces — declared before the "/:username" matcher so they
72 # aren't read as profiles.
73 get "/models", to: "models#index", as: :models # open-weights model library
74 get "/repos", to: "repositories#explore", as: :repos # public code repositories
75
76 # Repository creation
77 get "/new", to: "repositories#new", as: :new_repository
78 post "/new", to: "repositories#create"
79
80 # Import from GitHub (mirror + migrate). Declared before the "/:username"
81 # matcher so "/import" isn't read as a profile.
82 #
83 # The GitHub OAuth connect routes are declared before the resources block so
84 # "/import/github/*" isn't swallowed by the "/import/:id" show route. This is
85 # our own GitHub OAuth app (separate from smbCloud sign-in); the token it mints
86 # can read/clone the user's GitHub repos.
87 get "/import/github/connect", to: "github_connections#connect", as: :github_import_connect
88 get "/import/github/callback", to: "github_connections#callback", as: :github_import_callback
89 delete "/import/github/disconnect", to: "github_connections#disconnect", as: :github_import_disconnect
90
91 resources :imports, path: "import", only: %i[index new create show],
92 constraints: { id: /\d+/ } do
93 post :retry, on: :member
94 end
95
96 # GitHub push webhook → immediate mirror sync (HMAC-authenticated).
97 # Distinct from the GitHub App PR-review webhook at POST /github/webhooks.
98 post "/webhooks/github", to: "mirror_webhooks#create"
99
100 # Settings
101 get "/settings", to: "users#settings", as: :settings
102 patch "/settings", to: "users#update_settings"
103
104 # Admin console — internal ops/business dashboard. Gated by `require_admin!`.
105 # Declared before the "/:username" matcher so "/admin" isn't read as a profile.
106 namespace :admin do
107 root "dashboard#show"
108 get "dashboard", to: "dashboard#show"
109
110 resources :users, only: %i[index show] do
111 member do
112 patch :grant_admin
113 patch :revoke_admin
114 end
115 end
116
117 resources :repositories, only: %i[index show]
118 resources :subscriptions, only: %i[index]
119 end
120
121 # Billing — siGit Code plans (web). Checkout/portal happen on the web.
122 get "/billing", to: "billing#show", as: :billing
123 post "/billing/checkout", to: "billing#checkout", as: :billing_checkout
124 post "/billing/portal", to: "billing#portal", as: :billing_portal
125
126 # JSON API — token-based auth for the siGit Code & Deploy desktop app.
127 # Declared before the catch-all "/:username" route so "/api/..." isn't
128 # swallowed by the username matcher.
129 namespace :api do
130 namespace :v1 do
131 post "auth/sign_in", to: "sessions#create"
132 delete "auth/sign_out", to: "sessions#destroy"
133 post "auth/sign_up", to: "registrations#create"
134 post "auth/confirmation/resend", to: "confirmations#create"
135 post "auth/password/reset", to: "passwords#create"
136 # "Continue with GitHub" / "Continue with Google" for browser SPA clients
137 # (code.sigit.si). A server-side redirect that brokers the smbCloud flow
138 # so the app secret never reaches the client; smbCloud bounces back to the
139 # SPA's redirect_uri with an access_token (the same bearer the API expects).
140 get "auth/github", to: "oauth/github#start"
141 get "auth/google", to: "oauth/google#start"
142 get "me", to: "me#show"
143 delete "me", to: "me#destroy"
144 get "repos", to: "repos#index"
145 post "repos", to: "repos#create"
146 post "git_credentials", to: "git_credentials#create"
147 post "chat/completions", to: "chat_completions#create"
148 get "billing", to: "billing#show"
149 post "billing/checkout", to: "billing#checkout"
150 post "billing/portal", to: "billing#portal"
151
152 # Model Context Protocol endpoint (Streamable HTTP, stateless JSON-RPC).
153 post "mcp", to: "mcp#handle" # JSON-RPC messages from the client
154 get "mcp", to: "mcp#stream" # optional server->client SSE (returns 405 here)
155
156 # siGit Code Cloud Sessions — persisted, resumable conversations.
157 resources :cloud_sessions, path: "sessions",
158 only: %i[index create show update destroy] do
159 post "messages", on: :member, to: "cloud_sessions#create_message"
160 end
161 end
162 end
163
164 # Stripe webhooks for siGit Code billing (authenticated by Stripe signature).
165 post "/stripe/webhooks", to: "stripe_webhooks#create"
166
167 # GitHub App webhooks for siGit Code PR reviews (authenticated by HMAC signature).
168 post "/github/webhooks", to: "github_webhooks#create"
169
170 # Git Smart HTTP — clone/fetch over token-authenticated HTTPS. Declared before
171 # the catch-all "/:username" routes; the `*.git` constraint keeps them from
172 # matching normal repo-browsing URLs.
173 get "/:user/:repo/info/refs", to: "git_http#info_refs",
174 constraints: { repo: /[^\/]+\.git/ }
175 post "/:user/:repo/git-upload-pack", to: "git_http#upload_pack",
176 constraints: { repo: /[^\/]+\.git/ }
177 post "/:user/:repo/git-receive-pack", to: "git_http#receive_pack",
178 constraints: { repo: /[^\/]+\.git/ }
179
180 # User profile (must come before repository routes)
181 get "/:username", to: "users#show", as: :user_profile,
182 constraints: { username: /[a-z0-9][a-z0-9\-]{0,38}/ }
183
184 # Repository routes (in order of specificity)
185 get "/:username/:repository/commits/:branch", to: "commits#index", as: :repository_commits,
186 constraints: { branch: /[^\/]+/, repository: /[^\/.][^\/]*/ }
187 get "/:username/:repository/ci-cd", to: "repositories#cicd", as: :repository_cicd
188 get "/:username/:repository/commit/:sha", to: "commits#show", as: :repository_commit
189
190 # Pull requests — creation-only backend already existed (PullRequestService,
191 # exposed over MCP); these add the web surface on top of it.
192 get "/:username/:repository/pulls", to: "pull_requests#index", as: :repository_pull_requests
193 get "/:username/:repository/pulls/new", to: "pull_requests#new", as: :new_repository_pull_request
194 post "/:username/:repository/pulls", to: "pull_requests#create"
195 get "/:username/:repository/pull/:number", to: "pull_requests#show", as: :repository_pull_request,
196 constraints: { number: /\d+/ }
197 post "/:username/:repository/pull/:number/comments", to: "pull_requests#add_comment",
198 as: :repository_pull_request_comments, constraints: { number: /\d+/ }
199 post "/:username/:repository/pull/:number/close", to: "pull_requests#close", as: :close_repository_pull_request,
200 constraints: { number: /\d+/ }
201 post "/:username/:repository/pull/:number/reopen", to: "pull_requests#reopen", as: :reopen_repository_pull_request,
202 constraints: { number: /\d+/ }
203 post "/:username/:repository/pull/:number/merge", to: "pull_requests#merge", as: :merge_repository_pull_request,
204 constraints: { number: /\d+/ }
205 get "/:username/:repository/blob/:branch/*path", to: "blobs#show", as: :repository_blob, format: false
206 get "/:username/:repository/raw/:branch/*path", to: "blobs#raw", as: :repository_blob_raw, format: false
207 get "/:username/:repository/tree/:branch/*path", to: "repositories#tree", as: :repository_tree
208 get "/:username/:repository/tree/:branch", to: "repositories#tree", as: :repository_branch
209 post "/:username/:repository/star", to: "stars#create", as: :repository_star,
210 constraints: { repository: /[^\/.][^\/]*/ }, format: false
211 post "/:username/:repository/mirror/detach", to: "mirrors#detach", as: :repository_mirror_detach,
212 constraints: { repository: /[^\/.][^\/]*/ }, format: false
213 post "/:username/:repository/mirror/sync", to: "mirrors#sync", as: :repository_mirror_sync,
214 constraints: { repository: /[^\/.][^\/]*/ }, format: false
215 # `format: false` keeps dotted names (e.g. "Qwen2.5-3B-Instruct-GGUF") intact
216 # rather than treating the dot as a response-format separator.
217 get "/:username/:repository", to: "repositories#show", as: :repository,
218 constraints: { repository: /[^\/.][^\/]*/ }, format: false
219
220 # Health check
221 get "up" => "rails/health#show", as: :rails_health_check
222 end