1# frozen_string_literal: true
2
3constraints(::Constraints::ProjectUrlConstrainer.new) do
4  # If the route has a wildcard segment, the segment has a regex constraint,
5  # the segment is potentially followed by _another_ wildcard segment, and
6  # the `format` option is not set to false, we need to specify that
7  # regex constraint _outside_ of `constraints: {}`.
8  #
9  # Otherwise, Rails will overwrite the constraint with `/.+?/`,
10  # which breaks some of our wildcard routes like `/blob/*id`
11  # and `/tree/*id` that depend on the negative lookahead inside
12  # `Gitlab::PathRegex.full_namespace_route_regex`, which helps the router
13  # determine whether a certain path segment is part of `*namespace_id`,
14  # `:project_id`, or `*id`.
15  #
16  # See https://github.com/rails/rails/blob/v4.2.8/actionpack/lib/action_dispatch/routing/mapper.rb#L155
17  scope(path: '*namespace_id',
18        as: :namespace,
19        namespace_id: Gitlab::PathRegex.full_namespace_route_regex) do
20    scope(path: ':project_id',
21          constraints: { project_id: Gitlab::PathRegex.project_route_regex },
22          module: :projects,
23          as: :project) do
24      # Begin of the /-/ scope.
25      # Use this scope for all new project routes.
26      scope '-' do
27        get 'archive/*id', constraints: { format: Gitlab::PathRegex.archive_formats_regex, id: /.+?/ }, to: 'repositories#archive', as: 'archive'
28        # Since the page parameter can contain slashes (panel/new), use Rails'
29        # "Route Globbing" syntax (/*page) so that the route helpers do not encode
30        # the slash character.
31        get 'metrics(/:dashboard_path)(/*page)', constraints: { dashboard_path: /.+\.yml/, page: 'panel/new' },
32          to: 'metrics_dashboard#show', as: :metrics_dashboard, format: false
33
34        namespace :metrics, module: :metrics do
35          namespace :dashboards do
36            post :builder, to: 'builder#panel_preview'
37          end
38        end
39
40        namespace :security do
41          resource :configuration, only: [:show], controller: :configuration do
42            resource :sast, only: [:show], controller: :sast_configuration
43          end
44        end
45
46        resources :artifacts, only: [:index, :destroy]
47
48        resources :packages, only: [:index, :show, :destroy], module: :packages
49        resources :package_files, only: [], module: :packages do
50          member do
51            get :download
52          end
53        end
54
55        resources :infrastructure_registry, only: [:index, :show], module: :packages
56
57        resources :jobs, only: [:index, :show], constraints: { id: /\d+/ } do
58          collection do
59            resources :artifacts, only: [] do
60              collection do
61                get :latest_succeeded,
62                  path: '*ref_name_and_path',
63                  format: false
64              end
65            end
66          end
67
68          member do
69            get :status
70            post :cancel
71            post :unschedule
72            post :retry
73            post :play
74            post :erase
75            get :trace, defaults: { format: 'json' }
76            get :raw
77            get :terminal
78            get :proxy
79
80            # These routes are also defined in gitlab-workhorse. Make sure to update accordingly.
81            get '/terminal.ws/authorize', to: 'jobs#terminal_websocket_authorize', format: false
82            get '/proxy.ws/authorize', to: 'jobs#proxy_websocket_authorize', format: false
83          end
84
85          resource :artifacts, only: [] do
86            get :download
87            get :browse, path: 'browse(/*path)', format: false
88            get :file, path: 'file/*path', format: false
89            get :raw, path: 'raw/*path', format: false
90            post :keep
91          end
92        end
93
94        get :learn_gitlab, action: :index, controller: 'learn_gitlab'
95
96        namespace :ci do
97          resource :lint, only: [:show, :create]
98          resource :pipeline_editor, only: [:show], controller: :pipeline_editor, path: 'editor'
99          resources :daily_build_group_report_results, only: [:index], constraints: { format: /(csv|json)/ }
100          namespace :prometheus_metrics do
101            resources :histograms, only: [:create], constraints: { format: 'json' }
102          end
103        end
104
105        resources :runners, only: [:index, :edit, :update, :destroy, :show] do
106          member do
107            post :resume
108            post :pause
109          end
110
111          collection do
112            post :toggle_shared_runners
113            post :toggle_group_runners
114          end
115        end
116
117        namespace :settings do
118          resource :ci_cd, only: [:show, :update], controller: 'ci_cd' do
119            post :reset_cache
120            put :reset_registration_token
121            post :create_deploy_token, path: 'deploy_token/create', to: 'repository#create_deploy_token'
122            get :runner_setup_scripts, format: :json
123          end
124
125          resource :operations, only: [:show, :update] do
126            member do
127              post :reset_alerting_token
128              post :reset_pagerduty_token
129            end
130          end
131
132          resource :integrations, only: [:show]
133
134          resource :repository, only: [:show], controller: :repository do
135            # TODO: Removed this "create_deploy_token" route after change was made in app/helpers/ci_variables_helper.rb:14
136            # See MR comment for more detail: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/27059#note_311585356
137            post :create_deploy_token, path: 'deploy_token/create'
138            post :cleanup
139          end
140
141          resources :access_tokens, only: [:index, :create] do
142            member do
143              put :revoke
144            end
145          end
146
147          resource :packages_and_registries, only: [:show]
148        end
149
150        resources :usage_quotas, only: [:index]
151
152        resources :autocomplete_sources, only: [] do
153          collection do
154            get 'members'
155            get 'issues'
156            get 'merge_requests'
157            get 'labels'
158            get 'milestones'
159            get 'commands'
160            get 'snippets'
161          end
162        end
163
164        resources :project_members, except: [:show, :new, :edit], constraints: { id: %r{[a-zA-Z./0-9_\-#%+:]+} }, concerns: :access_requestable do
165          collection do
166            delete :leave
167
168            # Used for import team
169            # from another project
170            get :import
171            post :apply_import
172          end
173
174          member do
175            post :resend_invite
176          end
177        end
178
179        resources :deploy_keys, constraints: { id: /\d+/ }, only: [:index, :new, :create, :edit, :update] do
180          member do
181            put :enable
182            put :disable
183          end
184        end
185
186        resources :deploy_tokens, constraints: { id: /\d+/ }, only: [] do
187          member do
188            put :revoke
189          end
190        end
191
192        resources :milestones, constraints: { id: /\d+/ } do
193          member do
194            post :promote
195            get :issues
196            get :merge_requests
197            get :participants
198            get :labels
199          end
200        end
201
202        resources :labels, except: [:show], constraints: { id: /\d+/ } do
203          collection do
204            post :generate
205            post :set_priorities
206          end
207
208          member do
209            post :promote
210            post :toggle_subscription
211            delete :remove_priority
212          end
213        end
214
215        resources :services, constraints: { id: %r{[^/]+} }, only: [:edit, :update] do
216          member do
217            put :test
218          end
219
220          resources :hook_logs, only: [:show], controller: :service_hook_logs do
221            member do
222              post :retry
223            end
224          end
225        end
226
227        resources :boards, only: [:index, :show, :create, :update, :destroy], constraints: { id: /\d+/ } do
228          collection do
229            get :recent
230          end
231        end
232
233        resources :releases, only: [:index, :new, :show, :edit], param: :tag, constraints: { tag: %r{[^/]+} } do
234          member do
235            get :downloads, path: 'downloads/*filepath', format: false
236            scope module: :releases do
237              resources :evidences, only: [:show]
238            end
239          end
240        end
241
242        resources :logs, only: [:index] do
243          collection do
244            get :k8s
245            get :elasticsearch
246          end
247        end
248
249        resources :starrers, only: [:index]
250        resources :forks, only: [:index, :new, :create]
251        resources :group_links, only: [:create, :update, :destroy], constraints: { id: /\d+|:id/ }
252
253        resource :import, only: [:new, :create, :show]
254        resource :avatar, only: [:show, :destroy]
255
256        scope :grafana, as: :grafana_api do
257          get 'proxy/:datasource_id/*proxy_path', to: 'grafana_api#proxy'
258          get :metrics_dashboard, to: 'grafana_api#metrics_dashboard'
259        end
260
261        resource :mattermost, only: [:new, :create]
262        resource :variables, only: [:show, :update]
263        resources :triggers, only: [:index, :create, :edit, :update, :destroy]
264
265        resource :mirror, only: [:show, :update] do
266          member do
267            get :ssh_host_keys, constraints: { format: :json }
268            post :update_now
269          end
270        end
271
272        resource :cycle_analytics, only: :show, path: 'value_stream_analytics'
273        scope module: :cycle_analytics, as: 'cycle_analytics', path: 'value_stream_analytics' do
274          scope :events, controller: 'events' do
275            get :issue
276            get :plan
277            get :code
278            get :test
279            get :review
280            get :staging
281            get :production
282          end
283        end
284        get '/cycle_analytics', to: redirect('%{namespace_id}/%{project_id}/-/value_stream_analytics')
285
286        namespace :analytics do
287          resource :cycle_analytics, only: :show, path: 'value_stream_analytics'
288          scope module: :cycle_analytics, as: 'cycle_analytics', path: 'value_stream_analytics' do
289            resources :value_streams, only: [:index] do
290              resources :stages, only: [:index] do
291                member do
292                  get :median
293                  get :average
294                  get :records
295                  get :count
296                end
297              end
298            end
299            resource :summary, controller: :summary, only: :show
300          end
301        end
302
303        resources :cluster_agents, only: [:show], param: :name
304
305        concerns :clusterable
306
307        namespace :serverless do
308          scope :functions do
309            get '/:environment_id/:id', to: 'functions#show'
310            get '/:environment_id/:id/metrics', to: 'functions#metrics', as: :metrics
311          end
312
313          resources :functions, only: [:index]
314        end
315
316        resources :terraform, only: [:index]
317
318        resources :google_cloud, only: [:index]
319
320        namespace :google_cloud do
321          resources :service_accounts, only: [:index, :create]
322        end
323
324        resources :environments, except: [:destroy] do
325          member do
326            post :stop
327            post :cancel_auto_stop
328            get :terminal
329            get :metrics
330            get :additional_metrics
331            get :metrics_dashboard
332
333            # This route is also defined in gitlab-workhorse. Make sure to update accordingly.
334            get '/terminal.ws/authorize', to: 'environments#terminal_websocket_authorize', format: false
335
336            get '/prometheus/api/v1/*proxy_path', to: 'environments/prometheus_api#prometheus_proxy', as: :prometheus_api
337
338            get '/sample_metrics', to: 'environments/sample_metrics#query'
339          end
340
341          collection do
342            get :metrics, action: :metrics_redirect
343            get :folder, path: 'folders/*id', constraints: { format: /(html|json)/ }
344            get :search
345          end
346
347          resources :deployments, only: [:index] do
348            member do
349              get :metrics
350              get :additional_metrics
351            end
352          end
353        end
354
355        namespace :performance_monitoring do
356          resources :dashboards, only: [:create] do
357            collection do
358              put '/:file_name', to: 'dashboards#update', constraints: { file_name: /.+\.yml/ }
359            end
360          end
361        end
362
363        resources :alert_management, only: [:index] do
364          get 'details', on: :member
365        end
366
367        get 'alert_management/:id', to: 'alert_management#details', as: 'alert_management_alert'
368
369        get 'work_items/*work_items_path' => 'work_items#index', as: :work_items
370
371        resource :tracing, only: [:show]
372
373        post 'incidents/integrations/pagerduty', to: 'incident_management/pager_duty_incidents#create'
374
375        resources :incidents, only: [:index]
376
377        get 'issues/incident/:id' => 'incidents#show', as: :issues_incident
378
379        namespace :error_tracking do
380          resources :projects, only: :index
381        end
382
383        resources :product_analytics, only: [:index] do
384          collection do
385            get :setup
386            get :test
387            get :graphs
388          end
389        end
390
391        resources :error_tracking, only: [:index], controller: :error_tracking do
392          collection do
393            get ':issue_id/details',
394              to: 'error_tracking#details',
395              as: 'details'
396            get ':issue_id/stack_trace',
397              to: 'error_tracking/stack_traces#index',
398              as: 'stack_trace'
399            put ':issue_id',
400              to: 'error_tracking#update',
401              as: 'update'
402          end
403        end
404
405        namespace :design_management do
406          namespace :designs, path: 'designs/:design_id(/:sha)', constraints: -> (params) { params[:sha].nil? || Gitlab::Git.commit_id?(params[:sha]) } do
407            resource :raw_image, only: :show
408            resources :resized_image, only: :show, constraints: -> (params) { ::DesignManagement::DESIGN_IMAGE_SIZES.include?(params[:id]) }
409          end
410        end
411
412        get '/snippets/:snippet_id/raw/:ref/*path',
413          to: 'snippets/blobs#raw',
414          format: false,
415          as: :snippet_blob_raw,
416          constraints: { snippet_id: /\d+/ }
417
418        draw :issues
419        draw :merge_requests
420        draw :pipelines
421
422        # The wiki and repository routing contains wildcard characters so
423        # its preferable to keep it below all other project routes
424        draw :repository
425        draw :wiki
426
427        namespace :import do
428          resource :jira, only: [:show], controller: :jira
429        end
430
431        resources :snippets, except: [:create, :update, :destroy], concerns: :awardable, constraints: { id: /\d+/ } do
432          member do
433            get :raw
434            post :mark_as_spam
435          end
436        end
437
438        resources :feature_flags, param: :iid
439        resource :feature_flags_client, only: [] do
440          post :reset_token
441        end
442        resources :feature_flags_user_lists, param: :iid, only: [:index, :new, :edit, :show]
443
444        get '/schema/:branch/*filename',
445          to: 'web_ide_schemas#show',
446          format: false,
447          as: :schema
448
449        resources :hooks, only: [:index, :create, :edit, :update, :destroy], constraints: { id: /\d+/ } do
450          member do
451            post :test
452          end
453
454          resources :hook_logs, only: [:show] do
455            member do
456              post :retry
457            end
458          end
459        end
460
461        namespace :integrations do
462          resource :shimo, only: [:show]
463        end
464      end
465      # End of the /-/ scope.
466
467      # All new routes should go under /-/ scope.
468      # Look for scope '-' at the top of the file.
469
470      #
471      # Service Desk
472      #
473      get '/service_desk' => 'service_desk#show', as: :service_desk # rubocop:todo Cop/PutProjectRoutesUnderScope
474      put '/service_desk' => 'service_desk#update', as: :service_desk_refresh # rubocop:todo Cop/PutProjectRoutesUnderScope
475
476      #
477      # Templates
478      #
479      get '/templates/:template_type' => 'templates#index', # rubocop:todo Cop/PutProjectRoutesUnderScope
480          as: :templates,
481          defaults: { format: 'json' },
482          constraints: { template_type: %r{issue|merge_request}, format: 'json' }
483
484      get '/templates/:template_type/:key' => 'templates#show', # rubocop:todo Cop/PutProjectRoutesUnderScope
485          as: :template,
486          defaults: { format: 'json' },
487          constraints: { key: %r{[^/]+}, template_type: %r{issue|merge_request}, format: 'json' }
488
489      get '/description_templates/names/:template_type', # rubocop:todo Cop/PutProjectRoutesUnderScope
490          to: 'templates#names',
491          as: :template_names,
492          defaults: { format: 'json' },
493          constraints: { template_type: %r{issue|merge_request}, format: 'json' }
494
495      resource :pages, only: [:show, :update, :destroy] do # rubocop: disable Cop/PutProjectRoutesUnderScope
496        resources :domains, except: :index, controller: 'pages_domains', constraints: { id: %r{[^/]+} } do # rubocop: disable Cop/PutProjectRoutesUnderScope
497          member do
498            post :verify # rubocop:todo Cop/PutProjectRoutesUnderScope
499            post :retry_auto_ssl # rubocop:todo Cop/PutProjectRoutesUnderScope
500            delete :clean_certificate # rubocop:todo Cop/PutProjectRoutesUnderScope
501          end
502        end
503      end
504
505      namespace :prometheus do
506        resources :alerts, constraints: { id: /\d+/ }, only: [:index, :create, :show, :update, :destroy] do # rubocop: disable Cop/PutProjectRoutesUnderScope
507          post :notify, on: :collection # rubocop:todo Cop/PutProjectRoutesUnderScope
508          member do
509            get :metrics_dashboard # rubocop:todo Cop/PutProjectRoutesUnderScope
510          end
511        end
512
513        resources :metrics, constraints: { id: %r{[^\/]+} }, only: [:index, :new, :create, :edit, :update, :destroy] do # rubocop: disable Cop/PutProjectRoutesUnderScope
514          get :active_common, on: :collection # rubocop:todo Cop/PutProjectRoutesUnderScope
515          post :validate_query, on: :collection # rubocop:todo Cop/PutProjectRoutesUnderScope
516        end
517      end
518
519      post 'alerts/notify', to: 'alerting/notifications#create' # rubocop:todo Cop/PutProjectRoutesUnderScope
520      post 'alerts/notify/:name/:endpoint_identifier', # rubocop:todo Cop/PutProjectRoutesUnderScope
521            to: 'alerting/notifications#create',
522            as: :alert_http_integration,
523            constraints: { endpoint_identifier: /[A-Za-z0-9]+/ }
524
525      draw :legacy_builds
526
527      resources :container_registry, only: [:index, :destroy, :show], # rubocop: disable Cop/PutProjectRoutesUnderScope
528                                     controller: 'registry/repositories'
529
530      namespace :registry do
531        resources :repository, only: [] do # rubocop: disable Cop/PutProjectRoutesUnderScope
532          # We default to JSON format in the controller to avoid ambiguity.
533          # `latest.json` could either be a request for a tag named `latest`
534          # in JSON format, or a request for tag named `latest.json`.
535          scope format: false do
536            resources :tags, only: [:index, :destroy], # rubocop: disable Cop/PutProjectRoutesUnderScope
537                             constraints: { id: Gitlab::Regex.container_registry_tag_regex } do
538              collection do
539                delete :bulk_destroy # rubocop:todo Cop/PutProjectRoutesUnderScope
540              end
541            end
542          end
543        end
544      end
545
546      resources :notes, only: [:create, :destroy, :update], concerns: :awardable, constraints: { id: /\d+/ } do # rubocop: disable Cop/PutProjectRoutesUnderScope
547        member do
548          delete :delete_attachment # rubocop:todo Cop/PutProjectRoutesUnderScope
549          post :resolve # rubocop:todo Cop/PutProjectRoutesUnderScope
550          delete :resolve, action: :unresolve # rubocop:todo Cop/PutProjectRoutesUnderScope
551          get :outdated_line_change # rubocop:todo Cop/PutProjectRoutesUnderScope
552        end
553      end
554
555      get 'noteable/:target_type/:target_id/notes' => 'notes#index', as: 'noteable_notes' # rubocop:todo Cop/PutProjectRoutesUnderScope
556
557      resources :todos, only: [:create] # rubocop: disable Cop/PutProjectRoutesUnderScope
558
559      resources :uploads, only: [:create] do # rubocop: disable Cop/PutProjectRoutesUnderScope
560        collection do
561          get ":secret/:filename", action: :show, as: :show, constraints: { filename: %r{[^/]+} }, format: false, defaults: { format: nil } # rubocop:todo Cop/PutProjectRoutesUnderScope
562          post :authorize # rubocop:todo Cop/PutProjectRoutesUnderScope
563        end
564      end
565
566      resources :runner_projects, only: [:create, :destroy] # rubocop: disable Cop/PutProjectRoutesUnderScope
567      resources :badges, only: [:index] do # rubocop: disable Cop/PutProjectRoutesUnderScope
568        collection do
569          scope '*ref', constraints: { ref: Gitlab::PathRegex.git_reference_regex } do
570            constraints format: /svg/ do
571              get :pipeline # rubocop:todo Cop/PutProjectRoutesUnderScope
572              get :coverage # rubocop:todo Cop/PutProjectRoutesUnderScope
573            end
574          end
575        end
576      end
577
578      scope :service_ping, controller: :service_ping do
579        post :web_ide_clientside_preview # rubocop:todo Cop/PutProjectRoutesUnderScope
580        post :web_ide_pipelines_count # rubocop:todo Cop/PutProjectRoutesUnderScope
581      end
582
583      resources :web_ide_terminals, path: :ide_terminals, only: [:create, :show], constraints: { id: /\d+/, format: :json } do # rubocop: disable Cop/PutProjectRoutesUnderScope
584        member do
585          post :cancel # rubocop:todo Cop/PutProjectRoutesUnderScope
586          post :retry # rubocop:todo Cop/PutProjectRoutesUnderScope
587        end
588
589        collection do
590          post :check_config # rubocop:todo Cop/PutProjectRoutesUnderScope
591        end
592      end
593
594      # Deprecated unscoped routing.
595      scope as: 'deprecated' do
596        # Issue https://gitlab.com/gitlab-org/gitlab/issues/118849
597        draw :repository_deprecated
598
599        # Issue https://gitlab.com/gitlab-org/gitlab/-/issues/223719
600        # rubocop: disable Cop/PutProjectRoutesUnderScope
601        get '/snippets/:id/raw',
602          to: 'snippets#raw',
603          format: false,
604          constraints: { id: /\d+/ }
605        # rubocop: enable Cop/PutProjectRoutesUnderScope
606      end
607
608      # All new routes should go under /-/ scope.
609      # Look for scope '-' at the top of the file.
610
611      # Legacy routes.
612      # Introduced in 12.0.
613      # Should be removed with https://gitlab.com/gitlab-org/gitlab/issues/28848.
614      Gitlab::Routing.redirect_legacy_paths(self, :mirror, :tags, :hooks,
615                                            :commits, :commit, :find_file, :files, :compare,
616                                            :cycle_analytics, :mattermost, :variables, :triggers,
617                                            :environments, :protected_environments, :error_tracking, :alert_management,
618                                            :tracing,
619                                            :serverless, :clusters, :audit_events, :wikis, :merge_requests,
620                                            :vulnerability_feedback, :security, :dependencies, :issues,
621                                            :pipelines, :pipeline_schedules, :runners, :snippets)
622    end
623    # rubocop: disable Cop/PutProjectRoutesUnderScope
624    resources(:projects,
625              path: '/',
626              constraints: { id: Gitlab::PathRegex.project_route_regex },
627              only: [:edit, :show, :update, :destroy]) do
628      member do
629        put :transfer
630        delete :remove_fork
631        post :archive
632        post :unarchive
633        post :housekeeping
634        post :toggle_star
635        post :preview_markdown
636        post :export
637        post :remove_export
638        post :generate_new_export
639        get :download_export
640        get :activity
641        get :refs
642        put :new_issuable_address
643        get :unfoldered_environment_names
644      end
645    end
646    # rubocop: enable Cop/PutProjectRoutesUnderScope
647  end
648end
649
650# It's under /-/jira scope but cop is only checking /-/
651# rubocop: disable Cop/PutProjectRoutesUnderScope
652scope path: '(/-/jira)', constraints: ::Constraints::JiraEncodedUrlConstrainer.new, as: :jira do
653  scope path: '*namespace_id/:project_id',
654        namespace_id: Gitlab::Jira::Dvcs::ENCODED_ROUTE_REGEX,
655        project_id: Gitlab::Jira::Dvcs::ENCODED_ROUTE_REGEX do
656    get '/', to: redirect { |params, req|
657      ::Gitlab::Jira::Dvcs.restore_full_path(
658        namespace: params[:namespace_id],
659        project: params[:project_id]
660      )
661    }
662
663    get 'commit/:id', constraints: { id: /\h{7,40}/ }, to: redirect { |params, req|
664      project_full_path = ::Gitlab::Jira::Dvcs.restore_full_path(
665        namespace: params[:namespace_id],
666        project: params[:project_id]
667      )
668
669      "/#{project_full_path}/commit/#{params[:id]}"
670    }
671
672    get 'tree/*id', as: nil, to: redirect { |params, req|
673      project_full_path = ::Gitlab::Jira::Dvcs.restore_full_path(
674        namespace: params[:namespace_id],
675        project: params[:project_id]
676      )
677
678      "/#{project_full_path}/-/tree/#{params[:id]}"
679    }
680  end
681end
682# rubocop: enable Cop/PutProjectRoutesUnderScope
683