<NME> routes.rb
<BEF> # Page titles on Wikipedia may include dots, so this constraint is needed.

Rails.application.routes.draw do
  get 'errors/file_not_found'
  get 'errors/unprocessable'
  get 'errors/login_error'
  get 'errors/internal_server_error'
  get 'errors/incorrect_passcode'
  put 'errors/incorrect_passcode'

  # Sessions
  # This is for when you open a login link in a new tab, which prevents Rails JS from
  # intercepting the click and issuing a post request. Omniauth login is post-only.
  get 'users/auth/mediawiki', to: redirect('/')
  devise_for :users, controllers: { omniauth_callbacks: 'omniauth_callbacks' }
  
  devise_scope :user do
    # OmniAuth may fall back to :new_user_session when the OAuth flow fails.
    # So, we treat it as a login error.
    get 'sign_in', to: 'errors#login_error', as: :new_user_session

    get 'sign_out', to: 'users#signout', as: :destroy_user_session
    get 'sign_out_oauth', to: 'devise/sessions#destroy',
                          as: :true_destroy_user_session
  end

  get '/settings/all_admins' => 'settings#all_admins'
    get 'users/:username' => 'user_profiles#show' , constraints: { username: /.*/ }
    get 'user_stats' => 'user_profiles#stats'
    get 'stats_graphs' => 'user_profiles#stats_graphs'
    get 'update_email_preferences/:username' => 'user_profiles#update_email_preferences'
    post 'users/update/:username' => 'user_profiles#update'
  end

  # Users

  get '/settings/course_creation' => 'settings#course_creation'
  post '/settings/update_course_creation' => 'settings#update_course_creation'

  get '/settings/default_campaign' => 'settings#default_campaign'
  post '/settings/update_default_campaign' => 'settings#update_default_campaign'

  # Griddler allows us to receive incoming emails. By default,
  # the path for incoming emails is /email_processor
  mount_griddler

  #UserProfilesController
  controller :user_profiles do
    get 'users/:username' => 'user_profiles#show' , constraints: { username: /.*/ }
    get 'user_stats' => 'user_profiles#stats'
    get 'stats_graphs' => 'user_profiles#stats_graphs'
    delete 'profile_image' => 'user_profiles#delete_profile_image', as: 'delete_profile_image', constraints: { username: /.*/ }
    get 'update_email_preferences/:username' => 'user_profiles#update_email_preferences', constraints: { username: /.*/ }
    post 'users/update/:username' => 'user_profiles#update' , constraints: { username: /.*/ }
  end

  #PersonalDataController
  controller :personal_data do
    get 'download_personal_data' => 'personal_data#show'
  end

  # Users
  resources :users, only: [:index, :show], param: :username, constraints: { username: /.*/ } do
    collection do
      get 'revisions'
    end
  end

  resources :assignments do
    patch '/status' => 'assignments#update_status'
    resources :assignment_suggestions
  end
  put '/assignments/:assignment_id/claim' => 'assignments#claim'
  post '/assignments/assign_reviewers_randomly' => 'assignments#assign_reviewers_randomly'

  get 'mass_enrollment/:course_id'  => 'mass_enrollment#index',
      constraints: { course_id: /.*/ }
  post 'mass_enrollment/:course_id'  => 'mass_enrollment#add_users',
      constraints: { course_id: /.*/ }

  get '/requested_accounts_campaigns/*campaign_slug/create' => 'requested_accounts_campaigns#create_accounts',
      constraints: { campaign_slug: /.*/ }
  put '/requested_accounts_campaigns/*campaign_slug/enable_account_requests' => 'requested_accounts_campaigns#enable_account_requests',
      constraints: { campaign_slug: /.*/ }
  put '/requested_accounts_campaigns/*campaign_slug/disable_account_requests' => 'requested_accounts_campaigns#disable_account_requests',
      constraints: { campaign_slug: /.*/ }
  get '/requested_accounts_campaigns/*campaign_slug' => 'requested_accounts_campaigns#index',
      constraints: { campaign_slug: /.*/ }

  put 'requested_accounts' => 'requested_accounts#request_account'
  delete 'requested_accounts/*course_slug/*id/delete' => 'requested_accounts#destroy',
      constraints: { course_slug: /.*/ }
  post 'requested_accounts/*course_slug/create' => 'requested_accounts#create_accounts',
      constraints: { course_slug: /.*/ }
  get 'requested_accounts/*course_slug/enable_account_requests' => 'requested_accounts#enable_account_requests',
      constraints: { course_slug: /.*/ }
  get 'requested_accounts/:course_slug' => 'requested_accounts#show',
      constraints: { course_slug: /.*/ }
  get '/requested_accounts' => 'requested_accounts#index'
  post '/requested_accounts' => 'requested_accounts#create_all_accounts'

  # Self-enrollment: joining a course by entering a passcode or visiting a url
  get 'courses/:course_id/enroll/(:passcode)' => 'self_enrollment#enroll_self',
      constraints: { course_id: /.*/ }

  # Courses
  controller :courses do
    get 'courses/new' => 'courses#new',
        constraints: { id: /.*/ } # repeat of resources

    get 'courses/*id/manual_update' => 'courses#manual_update',
        :as => :manual_update, constraints: { id: /.*/ }
    get 'courses/*id/notify_untrained' => 'courses#notify_untrained',
        :as => :notify_untrained, constraints: { id: /.*/ }
    get 'courses/*id/needs_update' =>  'courses#needs_update',
        :as => :needs_update, constraints: { id: /.*/ }
    get 'courses/*id/ores_plot' =>  'ores_plot#course_plot',
        constraints: { id: /.*/ }
    get 'courses/*id/refresh_ores_data' =>  'ores_plot#refresh_ores_data',
        :as => :refresh_ores_data, constraints: { id: /.*/ }
    get 'courses/*id/check' => 'courses#check',
        :as => :check, constraints: { id: /.*/ }
    get 'courses/search.json' => 'courses#search'
    match 'courses/*id/campaign' => 'courses#list',
          constraints: { id: /.*/ }, via: [:post, :delete]
    match 'courses/*id/tag' => 'courses#tag',
          constraints: { id: /.*/ }, via: [:post, :delete]
    match 'courses/*id/user' => 'users/enrollment#index',
          constraints: { id: /.*/ }, via: [:post, :delete]

    # show-type actions: first all the specific json endpoints,
    # then the catchall show endpoint
    get 'courses/:slug/course.json' => 'courses#course',
        constraints: { slug: /.*/ }
    get 'courses/:slug/articles.json' => 'courses#articles',
        constraints: { slug: /.*/ }
    get 'courses/:slug/users.json' => 'courses#users',
        constraints: { slug: /.*/ }
    get 'courses/:slug/assignments.json' => 'courses#assignments',
        constraints: { slug: /.*/ }
    get 'courses/:slug/campaigns.json' => 'courses#campaigns',
        constraints: { slug: /.*/ }
    get 'courses/:slug/categories.json' => 'courses#categories',
        constraints: { slug: /.*/ }
    get 'courses/:slug/tags.json' => 'courses#tags',
        constraints: { slug: /.*/ }
    get 'courses/:slug/timeline.json' => 'courses#timeline',
        constraints: { slug: /.*/ }
    get 'courses/:slug/uploads.json' => 'courses#uploads',
        constraints: { slug: /.*/ }
    get 'courses/:slug/alerts.json' => 'courses#alerts',
        constraints: { slug: /.*/ }
    get 'courses/:slug/suspected_plagiarism.json' => 'courses#suspected_plagiarism',
        constraints: { slug: /.*/ }
    get 'courses/:school/:titleterm(/:_subpage(/:_subsubpage(/:_subsubsubpage)))' => 'courses#show',
        :as => 'show',
        constraints: {
          school: /[^\/]*/,
          titleterm: /[^\/]*/,
          _subsubsubpage: /.*/
        }

    get 'embed/course_stats/:school/:titleterm(/:_subpage(/:_subsubpage))' => 'embed#course_stats',
    constraints: {
        school: /[^\/]*/,
        titleterm: /[^\/]*/
    }

    post 'clone_course/:id' => 'course_clone#clone', as: 'course_clone'
    post 'courses/:id/update_syllabus' => 'courses/syllabuses#update'
    delete 'courses/:id/delete_all_weeks' => 'courses#delete_all_weeks',
      constraints: {
        id: /.*/
      }
    get 'find_course/:course_id' => 'courses#find'
  end

  # Categories
  post 'categories' => 'categories#add_category'
  delete 'categories' => 'categories#remove_category'

  get 'lookups/campaign(.:format)' => 'lookups#campaign'
  get 'lookups/tag(.:format)' => 'lookups#tag'

  # Timeline
  resources :courses, constraints: { id: /.*/ } do
    resources :weeks, only: [:index, :new, :create], constraints: { id: /.*/ }
    # get 'courses' => 'courses#index'
  end
  resources :weeks, only: [:index, :show, :edit, :update, :destroy]
  resources :blocks, only: [:show, :edit, :update, :destroy]
  post 'courses/:course_id/timeline' => 'timeline#update_timeline',
       constraints: { course_id: /.*/ }
  post 'courses/:course_id/enable_timeline' => 'timeline#enable_timeline',
       constraints: { course_id: /.*/ }
  post 'courses/:course_id/disable_timeline' => 'timeline#disable_timeline',
       constraints: { course_id: /.*/ }

  get 'revisions' => 'revisions#index'

  get 'articles/article_data' => 'articles#article_data'
  get 'articles/details' => 'articles#details'
  post 'articles/status' => 'articles#update_tracked_status'

  resources :courses_users, only: [:index]
  resources :alerts, only: [:create] do
    member do
      get 'resolve'
      put 'resolve'
    end
  end

  put 'greeting' => 'greeting#greet_course_students'

  # Article Finder
  get 'article_finder' => 'article_finder#index'

  # Reports and analytics
  get 'analytics(/*any)' => 'analytics#index'
  post 'analytics(/*any)' => 'analytics#results'
  get 'usage' => 'analytics#usage'
  get 'ungreeted' => 'analytics#ungreeted'
  get 'course_csv' => 'analytics#course_csv'
  get 'course_edits_csv' => 'analytics#course_edits_csv'
  get 'course_uploads_csv' => 'analytics#course_uploads_csv'
  get 'course_students_csv' => 'analytics#course_students_csv'
  get 'course_articles_csv' => 'analytics#course_articles_csv'
  get 'course_revisions_csv' => 'analytics#course_revisions_csv'
  get 'course_wikidata_csv' => 'analytics#course_wikidata_csv'
  get 'all_courses_csv' => 'analytics#all_courses_csv'
  get 'all_courses' => 'analytics#all_courses'
  get 'all_campaigns' => 'analytics#all_campaigns'

  # Campaigns
  resources :campaigns, param: :slug, except: :show do
    member do
      get 'overview'
      get 'programs'
      get 'articles'
      get 'users'
      get 'assignments'
      get 'students'
      get 'instructors'
      get 'courses'
      get 'ores_plot'
      get 'articles_csv'
      get 'revisions_csv'
      get 'alerts'
      get 'wikidata'
      put 'add_organizer'
      put 'remove_organizer'
      put 'remove_course'
      get 'active_courses'
    end
  end
  
  get 'campaigns/statistics.json' => 'campaigns#statistics'
  get 'campaigns/:slug.json',
      controller: :campaigns,
      action: :show
  get 'campaigns/:slug', to: redirect('campaigns/%{slug}/programs')
  get 'campaigns/:slug/programs/:courses_query',
      controller: :campaigns,
      action: :programs,
      to: 'campaigns/%{slug}/programs?courses_query=%{courses_query}'
  get 'campaigns/:slug/ores_data.json' =>  'ores_plot#campaign_plot'
  get 'current_term(/:subpage)' => 'campaigns#current_term'

  # Courses by tag
  resources :tagged_courses, param: :tag do
    member do
      get 'programs'
      get 'articles'
      get 'alerts'
    end
  end

  # Recent Activity
  get 'recent-activity/plagiarism/report' => 'recent_activity#plagiarism_report'
  get 'recent-activity(/*any)' => 'recent_activity#index', as: :recent_activity

  # Revision analytics JSON API for React
  get 'revision_analytics/dyk_eligible',
      controller: 'revision_analytics',
      action: 'dyk_eligible'
  get 'revision_analytics/suspected_plagiarism',
      controller: 'revision_analytics',
      action: 'suspected_plagiarism'
  get 'revision_analytics/recent_edits',
      controller: 'revision_analytics',
      action: 'recent_edits'
  get 'revision_analytics/recent_uploads',
      controller: 'revision_analytics',
      action: 'recent_uploads'


  # Revision Feedback
  get '/revision_feedback' => 'revision_feedback#index'

  # Wizard
  get 'wizards' => 'wizard#wizard_index'
  get 'wizards/:wizard_id' => 'wizard#wizard'
  post 'courses/:course_id/wizard/:wizard_id' => 'wizard#submit_wizard',
       constraints: { course_id: /.*/ }

  # Training
  get 'training' => 'training#index'
  get 'training/:library_id' => 'training#show', as: :training_library
  get 'training/:library_id/:module_id' => 'training#training_module', as: :training_module
  get 'training_modules_users' => 'training_modules_users#index'
  post 'training_modules_users' => 'training_modules_users#create_or_update'
  post 'training_modules_users/exercise' => 'training_modules_users#mark_exercise_complete'
  get 'reload_trainings' => 'training#reload'

  get 'training_status' => 'training_status#show'
  get 'user_training_status' => 'training_status#user'

  # for React
  get 'training/:library_id/:module_id(/*any)' => 'training#slide_view'

  # API for slides for a module
  get 'training_modules' => 'training_modules#index'
  get 'training_module' => 'training_modules#show'


  # Misc
  # get 'courses' => 'courses#index'
  get 'explore' => 'explore#index'
  get 'explore/search' => 'explore#search'
  get 'unsubmitted_courses' => 'unsubmitted_courses#index'
  get 'active_courses' => 'active_courses#index'
  get '/courses_by_wiki/:language.:project(.org)' => 'courses_by_wiki#show'

  # frequenty asked questions
  resources :faq
  get '/faq_topics' => 'faq_topics#index'
  get '/faq_topics/new' => 'faq_topics#new'
  post '/faq_topics' => 'faq_topics#create'
  get '/faq_topics/:slug/edit' => 'faq_topics#edit'
  post '/faq_topics/:slug' => 'faq_topics#update'
  delete '/faq_topics/:slug' => 'faq_topics#delete'

  # Authenticated users root to the courses dashboard
  authenticated :user do
    root to: "dashboard#index", as: :courses_dashboard
  end

  get 'dashboard' => 'dashboard#index'
  get 'my_account' => 'dashboard#my_account'

  # Unauthenticated users root to the home page
  root to: 'home#index'

  # Surveys
  mount Rapidfire::Engine => "/surveys/rapidfire", :as => 'rapidfire'
  get '/surveys/results' => 'surveys#results_index', as: 'results'
  resources :survey_assignments, path: 'surveys/assignments'
  post '/survey_assignments/:id/send_test_email' => 'survey_assignments#send_test_email', as: 'send_test_email'
  put '/surveys/question_position' => 'questions#update_position'
  get '/survey/results/:id' => 'surveys#results', as: 'survey_results'
  get '/survey/question/results/:id' => 'questions#results', as: 'question_results'
  get '/surveys/question_group_question/:id' => 'questions#question'
  get '/surveys/:id/question_group' => 'surveys#edit_question_groups', :as => "edit_question_groups"
  post '/surveys/question_group/clone/:id' => 'surveys#clone_question_group'
  post '/surveys/question/clone/:id' => 'surveys#clone_question'
  post '/surveys/update_question_group_position' => 'surveys#update_question_group_position'
  resources :surveys
  get '/surveys/:id/optout' => 'surveys#optout', as: 'optout'
  get '/surveys/select_course/:id' => 'surveys#course_select'
  put '/survey_notification' => 'survey_notifications#update'
  post '/survey_notification/create' => 'survey_assignments#create_notifications', as: 'create_notifications'
  post '/survey_notification/send' => 'survey_assignments#send_notifications', as: 'send_notifications'
  get '/survey/responses' => 'survey_responses#index'
  delete '/survey/responses/:id/delete' => 'survey_responses#delete'

  # Onboarding
  get 'onboarding(/*any)' => 'onboarding#index', as: :onboarding
  put 'onboarding/onboard' => 'onboarding#onboard', as: :onboard
  put 'onboarding/supplementary' => 'onboarding#supplementary', as: :supplementary

  # Update Locale Preference
  post '/update_locale/:locale' => 'users/locale#update_locale', as: :update_locale
  get '/update_locale/:locale' => 'users/locale#update_locale'

  # Route aliases for React frontend
  get '/course_creator(/*any)' => 'dashboard#index', as: :course_creator

  get '/feedback' => 'feedback_form_responses#new', as: :feedback
  get '/feedback_form_responses' => 'feedback_form_responses#index'
  get '/feedback_form_responses/:id' => 'feedback_form_responses#show', as: :feedback_form_response
  post '/feedback_form_responses' => 'feedback_form_responses#create'
  get '/feedback/confirmation' => 'feedback_form_responses#confirmation'

  # Salesforce
  if Features.wiki_ed?
    put '/salesforce/link/:course_id' => 'salesforce#link'
    put '/salesforce/update/:course_id' => 'salesforce#update'
    get '/salesforce/create_media' => 'salesforce#create_media'
  end

  # Wikimedia Event Center
  post '/wikimedia_event_center/confirm_event_sync' => 'wikimedia_event_center#confirm_event_sync'
  post '/wikimedia_event_center/update_event_participants' => 'wikimedia_event_center#update_event_participants'
  post '/wikimedia_event_center/unsync_event' => 'wikimedia_event_center#unsync_event'

  # Experiments
  namespace :experiments do
    get 'fall2017_cmu_experiment/:course_id/:email_code/opt_in' => 'fall2017_cmu_experiment#opt_in'
    get 'fall2017_cmu_experiment/:course_id/:email_code/opt_out' => 'fall2017_cmu_experiment#opt_out'
    get 'fall2017_cmu_experiment/course_list' => 'fall2017_cmu_experiment#course_list'
    get 'spring2018_cmu_experiment/:course_id/:email_code/opt_in' => 'spring2018_cmu_experiment#opt_in'
    get 'spring2018_cmu_experiment/:course_id/:email_code/opt_out' => 'spring2018_cmu_experiment#opt_out'
    get 'spring2018_cmu_experiment/course_list' => 'spring2018_cmu_experiment#course_list'
  end

  resources :admin
  resources :alerts_list

  namespace :mass_email do
    get 'term_recap' => 'term_recap#index'
    post 'term_recap/send' => 'term_recap#send_recap_emails'
  end

  get '/redirect/sandbox/:sandbox' => 'redirects#sandbox'

  resources :settings, only: [:index]

  authenticate :user, lambda { |u| u.admin? } do
    post '/tickets/reply' => 'tickets#reply', format: false
    post '/tickets/notify_owner' => 'tickets#notify_owner', format: false
    get '/tickets/*dashboard' => 'tickets#dashboard', format: false
    mount TicketDispenser::Engine, at: "/td"
  end

  require 'sidekiq_unique_jobs/web'
  require 'sidekiq/cron/web'
  authenticate :user, lambda { |u| u.admin? } do
    mount Sidekiq::Web => '/sidekiq'
  end

  get '/private_information' => 'about_this_site#private_information'
  get '/styleguide' => 'styleguide#index'

  # Errors
  match '/404', to: 'errors#file_not_found', via: :all
  match '/422', to: 'errors#unprocessable', via: :all
  match '/599', to: 'errors#login_error', via: :all
  match '/500', to: 'errors#internal_server_error', via: :all
end

<MSG> Fix username param constraints for user profile updates

The lack of the constraint to allow `.` and other characters caused errors if default-disallowed param characters were included.

<DFF> @@ -28,8 +28,8 @@ Rails.application.routes.draw do
     get 'users/:username' => 'user_profiles#show' , constraints: { username: /.*/ }
     get 'user_stats' => 'user_profiles#stats'
     get 'stats_graphs' => 'user_profiles#stats_graphs'
-    get 'update_email_preferences/:username' => 'user_profiles#update_email_preferences'
-    post 'users/update/:username' => 'user_profiles#update'
+    get 'update_email_preferences/:username' => 'user_profiles#update_email_preferences', constraints: { username: /.*/ }
+    post 'users/update/:username' => 'user_profiles#update' , constraints: { username: /.*/ }
   end
 
   # Users

{'commit': "diff --git a/config/routes.rb b/config/routes.rb\nindex bf73c46db..fcf5850c5 100644\n--- a/config/routes.rb\n+++ b/config/routes.rb\n@@ -28,8 +28,8 @@ Rails.application.routes.draw do\n     get 'users/:username' => 'user_profiles#show' , constraints: { username: /.*/ }\n     get 'user_stats' => 'user_profiles#stats'\n     get 'stats_graphs' => 'user_profiles#stats_graphs'\n-    get 'update_email_preferences/:username' => 'user_profiles#update_email_preferences'\n-    post 'users/update/:username' => 'user_profiles#update'\n+    get 'update_email_preferences/:username' => 'user_profiles#update_email_preferences', constraints: { username: /.*/ }\n+    post 'users/update/:username' => 'user_profiles#update' , constraints: { username: /.*/ }\n   end\n \n   # Users\n", 'commit_subject': 'Fix username param constraints for user profile updates', 'repo_name': 'WikiEducationFoundation/WikiEduDashboard', 'committer': {'name': 'Sage Ross', 'email': '0bc9f67b2b08dbd979104ac630343507d3de0edf@gmail.com', 'time_sec': '1536096312', 'tz_offset': '-420', 'date': {'seconds': '1536096312'}}, 'commit_tree': '6c96ccfd73761c16cc52a1c3b7a97e515bcd8e08', 'commit_message': 'Fix username param constraints for user profile updates\n\nThe lack of the constraint to allow `.` and other characters caused errors if default-disallowed param characters were included.\n', 'language_name': 'Shell', 'license': 'mit', 'hunks': "@@ -28,8 +28,8 @@ Rails.application.routes.draw do\n     get 'users/:username' => 'user_profiles#show' , constraints: { username: /.*/ }\n     get 'user_stats' => 'user_profiles#stats'\n     get 'stats_graphs' => 'user_profiles#stats_graphs'\n-    get 'update_email_preferences/:username' => 'user_profiles#update_email_preferences'\n-    post 'users/update/:username' => 'user_profiles#update'\n+    get 'update_email_preferences/:username' => 'user_profiles#update_email_preferences', constraints: { username: /.*/ }\n+    post 'users/update/:username' => 'user_profiles#update' , constraints: { username: /.*/ }\n   end\n \n   # Users\n", 'addition_count': 2, 'deletion_count': 2, 'src_file': 'a/config/routes.rb', 'tgt_file': 'b/config/routes.rb', 'file_extension': '.rb', 'before_file': '# Page titles on Wikipedia may include dots, so this constraint is needed.\n\nRails.application.routes.draw do\n  get \'errors/file_not_found\'\n  get \'errors/unprocessable\'\n  get \'errors/login_error\'\n  get \'errors/internal_server_error\'\n  get \'errors/incorrect_passcode\'\n  put \'errors/incorrect_passcode\'\n\n  # Sessions\n  # This is for when you open a login link in a new tab, which prevents Rails JS from\n  # intercepting the click and issuing a post request. Omniauth login is post-only.\n  get \'users/auth/mediawiki\', to: redirect(\'/\')\n  devise_for :users, controllers: { omniauth_callbacks: \'omniauth_callbacks\' }\n  \n  devise_scope :user do\n    # OmniAuth may fall back to :new_user_session when the OAuth flow fails.\n    # So, we treat it as a login error.\n    get \'sign_in\', to: \'errors#login_error\', as: :new_user_session\n\n    get \'sign_out\', to: \'users#signout\', as: :destroy_user_session\n    get \'sign_out_oauth\', to: \'devise/sessions#destroy\',\n                          as: :true_destroy_user_session\n  end\n\n  get \'/settings/all_admins\' => \'settings#all_admins\'\n    get \'users/:username\' => \'user_profiles#show\' , constraints: { username: /.*/ }\n    get \'user_stats\' => \'user_profiles#stats\'\n    get \'stats_graphs\' => \'user_profiles#stats_graphs\'\n    get \'update_email_preferences/:username\' => \'user_profiles#update_email_preferences\'\n    post \'users/update/:username\' => \'user_profiles#update\'\n  end\n\n  # Users\n\n  get \'/settings/course_creation\' => \'settings#course_creation\'\n  post \'/settings/update_course_creation\' => \'settings#update_course_creation\'\n\n  get \'/settings/default_campaign\' => \'settings#default_campaign\'\n  post \'/settings/update_default_campaign\' => \'settings#update_default_campaign\'\n\n  # Griddler allows us to receive incoming emails. By default,\n  # the path for incoming emails is /email_processor\n  mount_griddler\n\n  #UserProfilesController\n  controller :user_profiles do\n    get \'users/:username\' => \'user_profiles#show\' , constraints: { username: /.*/ }\n    get \'user_stats\' => \'user_profiles#stats\'\n    get \'stats_graphs\' => \'user_profiles#stats_graphs\'\n    delete \'profile_image\' => \'user_profiles#delete_profile_image\', as: \'delete_profile_image\', constraints: { username: /.*/ }\n    get \'update_email_preferences/:username\' => \'user_profiles#update_email_preferences\', constraints: { username: /.*/ }\n    post \'users/update/:username\' => \'user_profiles#update\' , constraints: { username: /.*/ }\n  end\n\n  #PersonalDataController\n  controller :personal_data do\n    get \'download_personal_data\' => \'personal_data#show\'\n  end\n\n  # Users\n  resources :users, only: [:index, :show], param: :username, constraints: { username: /.*/ } do\n    collection do\n      get \'revisions\'\n    end\n  end\n\n  resources :assignments do\n    patch \'/status\' => \'assignments#update_status\'\n    resources :assignment_suggestions\n  end\n  put \'/assignments/:assignment_id/claim\' => \'assignments#claim\'\n  post \'/assignments/assign_reviewers_randomly\' => \'assignments#assign_reviewers_randomly\'\n\n  get \'mass_enrollment/:course_id\'  => \'mass_enrollment#index\',\n      constraints: { course_id: /.*/ }\n  post \'mass_enrollment/:course_id\'  => \'mass_enrollment#add_users\',\n      constraints: { course_id: /.*/ }\n\n  get \'/requested_accounts_campaigns/*campaign_slug/create\' => \'requested_accounts_campaigns#create_accounts\',\n      constraints: { campaign_slug: /.*/ }\n  put \'/requested_accounts_campaigns/*campaign_slug/enable_account_requests\' => \'requested_accounts_campaigns#enable_account_requests\',\n      constraints: { campaign_slug: /.*/ }\n  put \'/requested_accounts_campaigns/*campaign_slug/disable_account_requests\' => \'requested_accounts_campaigns#disable_account_requests\',\n      constraints: { campaign_slug: /.*/ }\n  get \'/requested_accounts_campaigns/*campaign_slug\' => \'requested_accounts_campaigns#index\',\n      constraints: { campaign_slug: /.*/ }\n\n  put \'requested_accounts\' => \'requested_accounts#request_account\'\n  delete \'requested_accounts/*course_slug/*id/delete\' => \'requested_accounts#destroy\',\n      constraints: { course_slug: /.*/ }\n  post \'requested_accounts/*course_slug/create\' => \'requested_accounts#create_accounts\',\n      constraints: { course_slug: /.*/ }\n  get \'requested_accounts/*course_slug/enable_account_requests\' => \'requested_accounts#enable_account_requests\',\n      constraints: { course_slug: /.*/ }\n  get \'requested_accounts/:course_slug\' => \'requested_accounts#show\',\n      constraints: { course_slug: /.*/ }\n  get \'/requested_accounts\' => \'requested_accounts#index\'\n  post \'/requested_accounts\' => \'requested_accounts#create_all_accounts\'\n\n  # Self-enrollment: joining a course by entering a passcode or visiting a url\n  get \'courses/:course_id/enroll/(:passcode)\' => \'self_enrollment#enroll_self\',\n      constraints: { course_id: /.*/ }\n\n  # Courses\n  controller :courses do\n    get \'courses/new\' => \'courses#new\',\n        constraints: { id: /.*/ } # repeat of resources\n\n    get \'courses/*id/manual_update\' => \'courses#manual_update\',\n        :as => :manual_update, constraints: { id: /.*/ }\n    get \'courses/*id/notify_untrained\' => \'courses#notify_untrained\',\n        :as => :notify_untrained, constraints: { id: /.*/ }\n    get \'courses/*id/needs_update\' =>  \'courses#needs_update\',\n        :as => :needs_update, constraints: { id: /.*/ }\n    get \'courses/*id/ores_plot\' =>  \'ores_plot#course_plot\',\n        constraints: { id: /.*/ }\n    get \'courses/*id/refresh_ores_data\' =>  \'ores_plot#refresh_ores_data\',\n        :as => :refresh_ores_data, constraints: { id: /.*/ }\n    get \'courses/*id/check\' => \'courses#check\',\n        :as => :check, constraints: { id: /.*/ }\n    get \'courses/search.json\' => \'courses#search\'\n    match \'courses/*id/campaign\' => \'courses#list\',\n          constraints: { id: /.*/ }, via: [:post, :delete]\n    match \'courses/*id/tag\' => \'courses#tag\',\n          constraints: { id: /.*/ }, via: [:post, :delete]\n    match \'courses/*id/user\' => \'users/enrollment#index\',\n          constraints: { id: /.*/ }, via: [:post, :delete]\n\n    # show-type actions: first all the specific json endpoints,\n    # then the catchall show endpoint\n    get \'courses/:slug/course.json\' => \'courses#course\',\n        constraints: { slug: /.*/ }\n    get \'courses/:slug/articles.json\' => \'courses#articles\',\n        constraints: { slug: /.*/ }\n    get \'courses/:slug/users.json\' => \'courses#users\',\n        constraints: { slug: /.*/ }\n    get \'courses/:slug/assignments.json\' => \'courses#assignments\',\n        constraints: { slug: /.*/ }\n    get \'courses/:slug/campaigns.json\' => \'courses#campaigns\',\n        constraints: { slug: /.*/ }\n    get \'courses/:slug/categories.json\' => \'courses#categories\',\n        constraints: { slug: /.*/ }\n    get \'courses/:slug/tags.json\' => \'courses#tags\',\n        constraints: { slug: /.*/ }\n    get \'courses/:slug/timeline.json\' => \'courses#timeline\',\n        constraints: { slug: /.*/ }\n    get \'courses/:slug/uploads.json\' => \'courses#uploads\',\n        constraints: { slug: /.*/ }\n    get \'courses/:slug/alerts.json\' => \'courses#alerts\',\n        constraints: { slug: /.*/ }\n    get \'courses/:slug/suspected_plagiarism.json\' => \'courses#suspected_plagiarism\',\n        constraints: { slug: /.*/ }\n    get \'courses/:school/:titleterm(/:_subpage(/:_subsubpage(/:_subsubsubpage)))\' => \'courses#show\',\n        :as => \'show\',\n        constraints: {\n          school: /[^\\/]*/,\n          titleterm: /[^\\/]*/,\n          _subsubsubpage: /.*/\n        }\n\n    get \'embed/course_stats/:school/:titleterm(/:_subpage(/:_subsubpage))\' => \'embed#course_stats\',\n    constraints: {\n        school: /[^\\/]*/,\n        titleterm: /[^\\/]*/\n    }\n\n    post \'clone_course/:id\' => \'course_clone#clone\', as: \'course_clone\'\n    post \'courses/:id/update_syllabus\' => \'courses/syllabuses#update\'\n    delete \'courses/:id/delete_all_weeks\' => \'courses#delete_all_weeks\',\n      constraints: {\n        id: /.*/\n      }\n    get \'find_course/:course_id\' => \'courses#find\'\n  end\n\n  # Categories\n  post \'categories\' => \'categories#add_category\'\n  delete \'categories\' => \'categories#remove_category\'\n\n  get \'lookups/campaign(.:format)\' => \'lookups#campaign\'\n  get \'lookups/tag(.:format)\' => \'lookups#tag\'\n\n  # Timeline\n  resources :courses, constraints: { id: /.*/ } do\n    resources :weeks, only: [:index, :new, :create], constraints: { id: /.*/ }\n    # get \'courses\' => \'courses#index\'\n  end\n  resources :weeks, only: [:index, :show, :edit, :update, :destroy]\n  resources :blocks, only: [:show, :edit, :update, :destroy]\n  post \'courses/:course_id/timeline\' => \'timeline#update_timeline\',\n       constraints: { course_id: /.*/ }\n  post \'courses/:course_id/enable_timeline\' => \'timeline#enable_timeline\',\n       constraints: { course_id: /.*/ }\n  post \'courses/:course_id/disable_timeline\' => \'timeline#disable_timeline\',\n       constraints: { course_id: /.*/ }\n\n  get \'revisions\' => \'revisions#index\'\n\n  get \'articles/article_data\' => \'articles#article_data\'\n  get \'articles/details\' => \'articles#details\'\n  post \'articles/status\' => \'articles#update_tracked_status\'\n\n  resources :courses_users, only: [:index]\n  resources :alerts, only: [:create] do\n    member do\n      get \'resolve\'\n      put \'resolve\'\n    end\n  end\n\n  put \'greeting\' => \'greeting#greet_course_students\'\n\n  # Article Finder\n  get \'article_finder\' => \'article_finder#index\'\n\n  # Reports and analytics\n  get \'analytics(/*any)\' => \'analytics#index\'\n  post \'analytics(/*any)\' => \'analytics#results\'\n  get \'usage\' => \'analytics#usage\'\n  get \'ungreeted\' => \'analytics#ungreeted\'\n  get \'course_csv\' => \'analytics#course_csv\'\n  get \'course_edits_csv\' => \'analytics#course_edits_csv\'\n  get \'course_uploads_csv\' => \'analytics#course_uploads_csv\'\n  get \'course_students_csv\' => \'analytics#course_students_csv\'\n  get \'course_articles_csv\' => \'analytics#course_articles_csv\'\n  get \'course_revisions_csv\' => \'analytics#course_revisions_csv\'\n  get \'course_wikidata_csv\' => \'analytics#course_wikidata_csv\'\n  get \'all_courses_csv\' => \'analytics#all_courses_csv\'\n  get \'all_courses\' => \'analytics#all_courses\'\n  get \'all_campaigns\' => \'analytics#all_campaigns\'\n\n  # Campaigns\n  resources :campaigns, param: :slug, except: :show do\n    member do\n      get \'overview\'\n      get \'programs\'\n      get \'articles\'\n      get \'users\'\n      get \'assignments\'\n      get \'students\'\n      get \'instructors\'\n      get \'courses\'\n      get \'ores_plot\'\n      get \'articles_csv\'\n      get \'revisions_csv\'\n      get \'alerts\'\n      get \'wikidata\'\n      put \'add_organizer\'\n      put \'remove_organizer\'\n      put \'remove_course\'\n      get \'active_courses\'\n    end\n  end\n  \n  get \'campaigns/statistics.json\' => \'campaigns#statistics\'\n  get \'campaigns/:slug.json\',\n      controller: :campaigns,\n      action: :show\n  get \'campaigns/:slug\', to: redirect(\'campaigns/%{slug}/programs\')\n  get \'campaigns/:slug/programs/:courses_query\',\n      controller: :campaigns,\n      action: :programs,\n      to: \'campaigns/%{slug}/programs?courses_query=%{courses_query}\'\n  get \'campaigns/:slug/ores_data.json\' =>  \'ores_plot#campaign_plot\'\n  get \'current_term(/:subpage)\' => \'campaigns#current_term\'\n\n  # Courses by tag\n  resources :tagged_courses, param: :tag do\n    member do\n      get \'programs\'\n      get \'articles\'\n      get \'alerts\'\n    end\n  end\n\n  # Recent Activity\n  get \'recent-activity/plagiarism/report\' => \'recent_activity#plagiarism_report\'\n  get \'recent-activity(/*any)\' => \'recent_activity#index\', as: :recent_activity\n\n  # Revision analytics JSON API for React\n  get \'revision_analytics/dyk_eligible\',\n      controller: \'revision_analytics\',\n      action: \'dyk_eligible\'\n  get \'revision_analytics/suspected_plagiarism\',\n      controller: \'revision_analytics\',\n      action: \'suspected_plagiarism\'\n  get \'revision_analytics/recent_edits\',\n      controller: \'revision_analytics\',\n      action: \'recent_edits\'\n  get \'revision_analytics/recent_uploads\',\n      controller: \'revision_analytics\',\n      action: \'recent_uploads\'\n\n\n  # Revision Feedback\n  get \'/revision_feedback\' => \'revision_feedback#index\'\n\n  # Wizard\n  get \'wizards\' => \'wizard#wizard_index\'\n  get \'wizards/:wizard_id\' => \'wizard#wizard\'\n  post \'courses/:course_id/wizard/:wizard_id\' => \'wizard#submit_wizard\',\n       constraints: { course_id: /.*/ }\n\n  # Training\n  get \'training\' => \'training#index\'\n  get \'training/:library_id\' => \'training#show\', as: :training_library\n  get \'training/:library_id/:module_id\' => \'training#training_module\', as: :training_module\n  get \'training_modules_users\' => \'training_modules_users#index\'\n  post \'training_modules_users\' => \'training_modules_users#create_or_update\'\n  post \'training_modules_users/exercise\' => \'training_modules_users#mark_exercise_complete\'\n  get \'reload_trainings\' => \'training#reload\'\n\n  get \'training_status\' => \'training_status#show\'\n  get \'user_training_status\' => \'training_status#user\'\n\n  # for React\n  get \'training/:library_id/:module_id(/*any)\' => \'training#slide_view\'\n\n  # API for slides for a module\n  get \'training_modules\' => \'training_modules#index\'\n  get \'training_module\' => \'training_modules#show\'\n\n\n  # Misc\n  # get \'courses\' => \'courses#index\'\n  get \'explore\' => \'explore#index\'\n  get \'explore/search\' => \'explore#search\'\n  get \'unsubmitted_courses\' => \'unsubmitted_courses#index\'\n  get \'active_courses\' => \'active_courses#index\'\n  get \'/courses_by_wiki/:language.:project(.org)\' => \'courses_by_wiki#show\'\n\n  # frequenty asked questions\n  resources :faq\n  get \'/faq_topics\' => \'faq_topics#index\'\n  get \'/faq_topics/new\' => \'faq_topics#new\'\n  post \'/faq_topics\' => \'faq_topics#create\'\n  get \'/faq_topics/:slug/edit\' => \'faq_topics#edit\'\n  post \'/faq_topics/:slug\' => \'faq_topics#update\'\n  delete \'/faq_topics/:slug\' => \'faq_topics#delete\'\n\n  # Authenticated users root to the courses dashboard\n  authenticated :user do\n    root to: "dashboard#index", as: :courses_dashboard\n  end\n\n  get \'dashboard\' => \'dashboard#index\'\n  get \'my_account\' => \'dashboard#my_account\'\n\n  # Unauthenticated users root to the home page\n  root to: \'home#index\'\n\n  # Surveys\n  mount Rapidfire::Engine => "/surveys/rapidfire", :as => \'rapidfire\'\n  get \'/surveys/results\' => \'surveys#results_index\', as: \'results\'\n  resources :survey_assignments, path: \'surveys/assignments\'\n  post \'/survey_assignments/:id/send_test_email\' => \'survey_assignments#send_test_email\', as: \'send_test_email\'\n  put \'/surveys/question_position\' => \'questions#update_position\'\n  get \'/survey/results/:id\' => \'surveys#results\', as: \'survey_results\'\n  get \'/survey/question/results/:id\' => \'questions#results\', as: \'question_results\'\n  get \'/surveys/question_group_question/:id\' => \'questions#question\'\n  get \'/surveys/:id/question_group\' => \'surveys#edit_question_groups\', :as => "edit_question_groups"\n  post \'/surveys/question_group/clone/:id\' => \'surveys#clone_question_group\'\n  post \'/surveys/question/clone/:id\' => \'surveys#clone_question\'\n  post \'/surveys/update_question_group_position\' => \'surveys#update_question_group_position\'\n  resources :surveys\n  get \'/surveys/:id/optout\' => \'surveys#optout\', as: \'optout\'\n  get \'/surveys/select_course/:id\' => \'surveys#course_select\'\n  put \'/survey_notification\' => \'survey_notifications#update\'\n  post \'/survey_notification/create\' => \'survey_assignments#create_notifications\', as: \'create_notifications\'\n  post \'/survey_notification/send\' => \'survey_assignments#send_notifications\', as: \'send_notifications\'\n  get \'/survey/responses\' => \'survey_responses#index\'\n  delete \'/survey/responses/:id/delete\' => \'survey_responses#delete\'\n\n  # Onboarding\n  get \'onboarding(/*any)\' => \'onboarding#index\', as: :onboarding\n  put \'onboarding/onboard\' => \'onboarding#onboard\', as: :onboard\n  put \'onboarding/supplementary\' => \'onboarding#supplementary\', as: :supplementary\n\n  # Update Locale Preference\n  post \'/update_locale/:locale\' => \'users/locale#update_locale\', as: :update_locale\n  get \'/update_locale/:locale\' => \'users/locale#update_locale\'\n\n  # Route aliases for React frontend\n  get \'/course_creator(/*any)\' => \'dashboard#index\', as: :course_creator\n\n  get \'/feedback\' => \'feedback_form_responses#new\', as: :feedback\n  get \'/feedback_form_responses\' => \'feedback_form_responses#index\'\n  get \'/feedback_form_responses/:id\' => \'feedback_form_responses#show\', as: :feedback_form_response\n  post \'/feedback_form_responses\' => \'feedback_form_responses#create\'\n  get \'/feedback/confirmation\' => \'feedback_form_responses#confirmation\'\n\n  # Salesforce\n  if Features.wiki_ed?\n    put \'/salesforce/link/:course_id\' => \'salesforce#link\'\n    put \'/salesforce/update/:course_id\' => \'salesforce#update\'\n    get \'/salesforce/create_media\' => \'salesforce#create_media\'\n  end\n\n  # Wikimedia Event Center\n  post \'/wikimedia_event_center/confirm_event_sync\' => \'wikimedia_event_center#confirm_event_sync\'\n  post \'/wikimedia_event_center/update_event_participants\' => \'wikimedia_event_center#update_event_participants\'\n  post \'/wikimedia_event_center/unsync_event\' => \'wikimedia_event_center#unsync_event\'\n\n  # Experiments\n  namespace :experiments do\n    get \'fall2017_cmu_experiment/:course_id/:email_code/opt_in\' => \'fall2017_cmu_experiment#opt_in\'\n    get \'fall2017_cmu_experiment/:course_id/:email_code/opt_out\' => \'fall2017_cmu_experiment#opt_out\'\n    get \'fall2017_cmu_experiment/course_list\' => \'fall2017_cmu_experiment#course_list\'\n    get \'spring2018_cmu_experiment/:course_id/:email_code/opt_in\' => \'spring2018_cmu_experiment#opt_in\'\n    get \'spring2018_cmu_experiment/:course_id/:email_code/opt_out\' => \'spring2018_cmu_experiment#opt_out\'\n    get \'spring2018_cmu_experiment/course_list\' => \'spring2018_cmu_experiment#course_list\'\n  end\n\n  resources :admin\n  resources :alerts_list\n\n  namespace :mass_email do\n    get \'term_recap\' => \'term_recap#index\'\n    post \'term_recap/send\' => \'term_recap#send_recap_emails\'\n  end\n\n  get \'/redirect/sandbox/:sandbox\' => \'redirects#sandbox\'\n\n  resources :settings, only: [:index]\n\n  authenticate :user, lambda { |u| u.admin? } do\n    post \'/tickets/reply\' => \'tickets#reply\', format: false\n    post \'/tickets/notify_owner\' => \'tickets#notify_owner\', format: false\n    get \'/tickets/*dashboard\' => \'tickets#dashboard\', format: false\n    mount TicketDispenser::Engine, at: "/td"\n  end\n\n  require \'sidekiq_unique_jobs/web\'\n  require \'sidekiq/cron/web\'\n  authenticate :user, lambda { |u| u.admin? } do\n    mount Sidekiq::Web => \'/sidekiq\'\n  end\n\n  get \'/private_information\' => \'about_this_site#private_information\'\n  get \'/styleguide\' => \'styleguide#index\'\n\n  # Errors\n  match \'/404\', to: \'errors#file_not_found\', via: :all\n  match \'/422\', to: \'errors#unprocessable\', via: :all\n  match \'/599\', to: \'errors#login_error\', via: :all\n  match \'/500\', to: \'errors#internal_server_error\', via: :all\nend\n', 'message': 'Fix username param constraints for user profile updates\n\nThe lack of the constraint to allow `.` and other characters caused errors if default-disallowed param characters were included.\n', 'author_name': 'Sage Ross', 'lang': 'rb'}