Add routes for i18n (#10193)
* Add routes * Update all_routes spec * Add i18n route specs * Add docs * Docs cleanup * Add note about fragment caching
This commit is contained in:
parent
8099a91611
commit
2a5a933ed1
5 changed files with 655 additions and 462 deletions
931
config/routes.rb
931
config/routes.rb
|
|
@ -5,6 +5,8 @@ Rails.application.routes.draw do
|
|||
controllers tokens: "oauth/tokens"
|
||||
end
|
||||
|
||||
# Devise does not support scoping omniauth callbacks under a dynamic segment
|
||||
# so this lives outside our i18n scope.
|
||||
devise_for :users, controllers: {
|
||||
omniauth_callbacks: "omniauth_callbacks",
|
||||
registrations: "registrations",
|
||||
|
|
@ -18,497 +20,502 @@ Rails.application.routes.draw do
|
|||
delete "/sign_out", to: "devise/sessions#destroy"
|
||||
end
|
||||
|
||||
require "sidekiq/web"
|
||||
require "sidekiq_unique_jobs/web"
|
||||
require "sidekiq/cron/web"
|
||||
# [@forem/delightful] - all routes are nested under this optional scope to
|
||||
# begin supporting i18n.
|
||||
scope "(/locale/:locale)", defaults: { locale: nil } do
|
||||
get "/locale/:locale" => "stories#index"
|
||||
require "sidekiq/web"
|
||||
require "sidekiq_unique_jobs/web"
|
||||
require "sidekiq/cron/web"
|
||||
|
||||
authenticated :user, ->(user) { user.tech_admin? } do
|
||||
Sidekiq::Web.set :session_secret, Rails.application.secrets[:secret_key_base]
|
||||
Sidekiq::Web.set :sessions, Rails.application.config.session_options
|
||||
Sidekiq::Web.class_eval do
|
||||
use Rack::Protection, origin_whitelist: [URL.url] # resolve Rack Protection HttpOrigin
|
||||
end
|
||||
mount Sidekiq::Web => "/sidekiq"
|
||||
mount FieldTest::Engine, at: "abtests"
|
||||
end
|
||||
|
||||
namespace :resource_admin do
|
||||
# Check administrate gem docs
|
||||
DashboardManifest::DASHBOARDS.each do |dashboard_resource|
|
||||
resources dashboard_resource
|
||||
authenticated :user, ->(user) { user.tech_admin? } do
|
||||
Sidekiq::Web.set :session_secret, Rails.application.secrets[:secret_key_base]
|
||||
Sidekiq::Web.set :sessions, Rails.application.config.session_options
|
||||
Sidekiq::Web.class_eval do
|
||||
use Rack::Protection, origin_whitelist: [URL.url] # resolve Rack Protection HttpOrigin
|
||||
end
|
||||
mount Sidekiq::Web => "/sidekiq"
|
||||
mount FieldTest::Engine, at: "abtests"
|
||||
end
|
||||
|
||||
root controller: DashboardManifest::ROOT_DASHBOARD, action: :index
|
||||
end
|
||||
namespace :resource_admin do
|
||||
# Check administrate gem docs
|
||||
DashboardManifest::DASHBOARDS.each do |dashboard_resource|
|
||||
resources dashboard_resource
|
||||
end
|
||||
|
||||
namespace :admin do
|
||||
get "/", to: redirect("/admin/articles")
|
||||
|
||||
authenticate :user, ->(user) { user.has_role?(:tech_admin) } do
|
||||
mount Blazer::Engine, at: "blazer"
|
||||
|
||||
flipper_ui = Flipper::UI.app(Flipper,
|
||||
{ rack_protection: { except: %i[authenticity_token form_token json_csrf
|
||||
remote_token http_origin session_hijacking] } })
|
||||
mount flipper_ui, at: "feature_flags"
|
||||
root controller: DashboardManifest::ROOT_DASHBOARD, action: :index
|
||||
end
|
||||
|
||||
resources :articles, only: %i[index show update]
|
||||
resources :broadcasts
|
||||
resources :buffer_updates, only: %i[create update]
|
||||
resources :listings, only: %i[index edit update destroy]
|
||||
resources :comments, only: [:index]
|
||||
resources :events, only: %i[index create update]
|
||||
resources :feedback_messages, only: %i[index show]
|
||||
resources :invitations, only: %i[index new create]
|
||||
resources :pages, only: %i[index new create edit update destroy]
|
||||
resources :mods, only: %i[index update]
|
||||
resources :moderator_actions, only: %i[index]
|
||||
resources :privileged_reactions, only: %i[index]
|
||||
resources :permissions, only: %i[index]
|
||||
resources :podcasts, only: %i[index edit update destroy] do
|
||||
member do
|
||||
post :fetch
|
||||
post :add_admin
|
||||
delete :remove_admin
|
||||
namespace :admin do
|
||||
get "/", to: redirect("/admin/articles")
|
||||
|
||||
authenticate :user, ->(user) { user.has_role?(:tech_admin) } do
|
||||
mount Blazer::Engine, at: "blazer"
|
||||
|
||||
flipper_ui = Flipper::UI.app(Flipper,
|
||||
{ rack_protection: { except: %i[authenticity_token form_token json_csrf
|
||||
remote_token http_origin session_hijacking] } })
|
||||
mount flipper_ui, at: "feature_flags"
|
||||
end
|
||||
|
||||
resources :articles, only: %i[index show update]
|
||||
resources :broadcasts
|
||||
resources :buffer_updates, only: %i[create update]
|
||||
resources :listings, only: %i[index edit update destroy]
|
||||
resources :comments, only: [:index]
|
||||
resources :events, only: %i[index create update]
|
||||
resources :feedback_messages, only: %i[index show]
|
||||
resources :invitations, only: %i[index new create]
|
||||
resources :pages, only: %i[index new create edit update destroy]
|
||||
resources :mods, only: %i[index update]
|
||||
resources :moderator_actions, only: %i[index]
|
||||
resources :privileged_reactions, only: %i[index]
|
||||
resources :permissions, only: %i[index]
|
||||
resources :podcasts, only: %i[index edit update destroy] do
|
||||
member do
|
||||
post :fetch
|
||||
post :add_admin
|
||||
delete :remove_admin
|
||||
end
|
||||
end
|
||||
|
||||
resources :profile_field_groups, only: %i[update create destroy]
|
||||
resources :profile_fields, only: %i[index update create destroy]
|
||||
resources :reactions, only: [:update]
|
||||
resources :response_templates, only: %i[index new edit create update destroy]
|
||||
resources :chat_channels, only: %i[index create update destroy] do
|
||||
member do
|
||||
delete :remove_user
|
||||
end
|
||||
end
|
||||
resources :reports, only: %i[index show], controller: "feedback_messages" do
|
||||
collection do
|
||||
post "send_email"
|
||||
post "create_note"
|
||||
post "save_status"
|
||||
end
|
||||
end
|
||||
resources :tags, only: %i[index update show]
|
||||
resources :users, only: %i[index show edit update] do
|
||||
member do
|
||||
post "banish"
|
||||
post "full_delete"
|
||||
patch "user_status"
|
||||
post "merge"
|
||||
delete "remove_identity"
|
||||
post "recover_identity"
|
||||
post "send_email"
|
||||
post "verify_email_ownership"
|
||||
patch "unlock_access"
|
||||
end
|
||||
end
|
||||
resources :organization_memberships, only: %i[update destroy create]
|
||||
resources :organizations, only: %i[index show] do
|
||||
member do
|
||||
patch "update_org_credits"
|
||||
end
|
||||
end
|
||||
resources :sponsorships, only: %i[index edit update new create destroy]
|
||||
resources :welcome, only: %i[index create]
|
||||
resources :growth, only: %i[index]
|
||||
resources :tools, only: %i[index create] do
|
||||
collection do
|
||||
post "bust_cache"
|
||||
end
|
||||
end
|
||||
resources :webhook_endpoints, only: :index
|
||||
resource :config
|
||||
resources :badges, only: %i[index edit update new create]
|
||||
resources :display_ads, only: %i[index edit update new create destroy]
|
||||
# These redirects serve as a safegaurd to prevent 404s for any Admins
|
||||
# who have the old badge_achievement URLs bookmarked.
|
||||
get "/badges/badge_achievements", to: redirect("/admin/badge_achievements")
|
||||
get "/badges/badge_achievements/award_badges", to: redirect("/admin/badge_achievements/award_badges")
|
||||
resources :badge_achievements, only: %i[index destroy]
|
||||
get "/badge_achievements/award_badges", to: "badge_achievements#award"
|
||||
post "/badge_achievements/award_badges", to: "badge_achievements#award_badges"
|
||||
resources :secrets, only: %i[index]
|
||||
put "secrets", to: "secrets#update"
|
||||
end
|
||||
|
||||
namespace :stories, defaults: { format: "json" } do
|
||||
resource :feed, only: [:show] do
|
||||
get ":timeframe" => "feeds#show"
|
||||
end
|
||||
end
|
||||
|
||||
resources :profile_field_groups, only: %i[update create destroy]
|
||||
resources :profile_fields, only: %i[index update create destroy]
|
||||
resources :reactions, only: [:update]
|
||||
resources :response_templates, only: %i[index new edit create update destroy]
|
||||
resources :chat_channels, only: %i[index create update destroy] do
|
||||
member do
|
||||
delete :remove_user
|
||||
namespace :api, defaults: { format: "json" } do
|
||||
scope module: :v0,
|
||||
constraints: ApiConstraints.new(version: 0, default: true) do
|
||||
resources :articles, only: %i[index show create update] do
|
||||
collection do
|
||||
get "me(/:status)", to: "articles#me", as: :me, constraints: { status: /published|unpublished|all/ }
|
||||
get "/:username/:slug", to: "articles#show_by_slug", as: :slug
|
||||
end
|
||||
end
|
||||
resources :comments, only: %i[index show]
|
||||
resources :videos, only: [:index]
|
||||
resources :podcast_episodes, only: [:index]
|
||||
resources :users, only: %i[show] do
|
||||
collection do
|
||||
get :me
|
||||
end
|
||||
end
|
||||
resources :tags, only: [:index]
|
||||
resources :follows, only: [:create]
|
||||
namespace :followers do
|
||||
get :users
|
||||
get :organizations
|
||||
end
|
||||
resources :webhooks, only: %i[index create show destroy]
|
||||
|
||||
resources :listings, only: %i[index show create update]
|
||||
get "/listings/category/:category", to: "listings#index", as: :listings_category
|
||||
get "/analytics/totals", to: "analytics#totals"
|
||||
get "/analytics/historical", to: "analytics#historical"
|
||||
get "/analytics/past_day", to: "analytics#past_day"
|
||||
get "/analytics/referrers", to: "analytics#referrers"
|
||||
|
||||
resources :health_checks, only: [] do
|
||||
collection do
|
||||
get :app
|
||||
get :search
|
||||
get :database
|
||||
get :cache
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
resources :reports, only: %i[index show], controller: "feedback_messages" do
|
||||
|
||||
namespace :notifications do
|
||||
resources :counts, only: [:index]
|
||||
resources :reads, only: [:create]
|
||||
end
|
||||
|
||||
namespace :incoming_webhooks do
|
||||
get "/mailchimp/:secret/unsubscribe", to: "mailchimp_unsubscribes#index", as: :mailchimp_unsubscribe_check
|
||||
post "/mailchimp/:secret/unsubscribe", to: "mailchimp_unsubscribes#create", as: :mailchimp_unsubscribe
|
||||
end
|
||||
|
||||
resources :messages, only: [:create]
|
||||
resources :chat_channels, only: %i[index show create update]
|
||||
resources :chat_channel_memberships, only: %i[index create edit update destroy]
|
||||
resources :articles, only: %i[update create destroy] do
|
||||
patch "/admin_unpublish", to: "articles#admin_unpublish"
|
||||
end
|
||||
resources :article_mutes, only: %i[update]
|
||||
resources :comments, only: %i[create update destroy] do
|
||||
patch "/hide", to: "comments#hide"
|
||||
patch "/unhide", to: "comments#unhide"
|
||||
patch "/admin_delete", to: "comments#admin_delete"
|
||||
collection do
|
||||
post "send_email"
|
||||
post "create_note"
|
||||
post "save_status"
|
||||
post "/moderator_create", to: "comments#moderator_create"
|
||||
end
|
||||
end
|
||||
resources :tags, only: %i[index update show]
|
||||
resources :users, only: %i[index show edit update] do
|
||||
member do
|
||||
post "banish"
|
||||
post "full_delete"
|
||||
patch "user_status"
|
||||
post "merge"
|
||||
delete "remove_identity"
|
||||
post "recover_identity"
|
||||
post "send_email"
|
||||
post "verify_email_ownership"
|
||||
patch "unlock_access"
|
||||
end
|
||||
resources :comment_mutes, only: %i[update]
|
||||
resources :users, only: %i[index], defaults: { format: :json } # internal API
|
||||
resources :users, only: %i[update] do
|
||||
resource :twitch_stream_updates, only: %i[show create]
|
||||
end
|
||||
resources :organization_memberships, only: %i[update destroy create]
|
||||
resources :organizations, only: %i[index show] do
|
||||
member do
|
||||
patch "update_org_credits"
|
||||
end
|
||||
end
|
||||
resources :sponsorships, only: %i[index edit update new create destroy]
|
||||
resources :welcome, only: %i[index create]
|
||||
resources :growth, only: %i[index]
|
||||
resources :tools, only: %i[index create] do
|
||||
resources :twitch_live_streams, only: :show, param: :username
|
||||
resources :reactions, only: %i[index create]
|
||||
resources :response_templates, only: %i[index create edit update destroy]
|
||||
resources :feedback_messages, only: %i[index create]
|
||||
resources :organizations, only: %i[update create destroy]
|
||||
resources :followed_articles, only: [:index]
|
||||
resources :follows, only: %i[show create update] do
|
||||
collection do
|
||||
post "bust_cache"
|
||||
get "/bulk_show", to: "follows#bulk_show"
|
||||
end
|
||||
end
|
||||
resources :webhook_endpoints, only: :index
|
||||
resource :config
|
||||
resources :badges, only: %i[index edit update new create]
|
||||
resources :display_ads, only: %i[index edit update new create destroy]
|
||||
# These redirects serve as a safegaurd to prevent 404s for any Admins
|
||||
# who have the old badge_achievement URLs bookmarked.
|
||||
get "/badges/badge_achievements", to: redirect("/admin/badge_achievements")
|
||||
get "/badges/badge_achievements/award_badges", to: redirect("/admin/badge_achievements/award_badges")
|
||||
resources :badge_achievements, only: %i[index destroy]
|
||||
get "/badge_achievements/award_badges", to: "badge_achievements#award"
|
||||
post "/badge_achievements/award_badges", to: "badge_achievements#award_badges"
|
||||
resources :secrets, only: %i[index]
|
||||
put "secrets", to: "secrets#update"
|
||||
end
|
||||
|
||||
namespace :stories, defaults: { format: "json" } do
|
||||
resource :feed, only: [:show] do
|
||||
get ":timeframe" => "feeds#show"
|
||||
end
|
||||
end
|
||||
|
||||
namespace :api, defaults: { format: "json" } do
|
||||
scope module: :v0,
|
||||
constraints: ApiConstraints.new(version: 0, default: true) do
|
||||
resources :articles, only: %i[index show create update] do
|
||||
collection do
|
||||
get "me(/:status)", to: "articles#me", as: :me, constraints: { status: /published|unpublished|all/ }
|
||||
get "/:username/:slug", to: "articles#show_by_slug", as: :slug
|
||||
end
|
||||
end
|
||||
resources :comments, only: %i[index show]
|
||||
resources :videos, only: [:index]
|
||||
resources :podcast_episodes, only: [:index]
|
||||
resources :users, only: %i[show] do
|
||||
collection do
|
||||
get :me
|
||||
end
|
||||
end
|
||||
resources :tags, only: [:index]
|
||||
resources :follows, only: [:create]
|
||||
namespace :followers do
|
||||
get :users
|
||||
get :organizations
|
||||
end
|
||||
resources :webhooks, only: %i[index create show destroy]
|
||||
|
||||
resources :listings, only: %i[index show create update]
|
||||
get "/listings/category/:category", to: "listings#index", as: :listings_category
|
||||
get "/analytics/totals", to: "analytics#totals"
|
||||
get "/analytics/historical", to: "analytics#historical"
|
||||
get "/analytics/past_day", to: "analytics#past_day"
|
||||
get "/analytics/referrers", to: "analytics#referrers"
|
||||
|
||||
resources :health_checks, only: [] do
|
||||
collection do
|
||||
get :app
|
||||
get :search
|
||||
get :database
|
||||
get :cache
|
||||
end
|
||||
resources :image_uploads, only: [:create]
|
||||
resources :notifications, only: [:index]
|
||||
resources :tags, only: [:index] do
|
||||
collection do
|
||||
get "/onboarding", to: "tags#onboarding"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
namespace :notifications do
|
||||
resources :counts, only: [:index]
|
||||
resources :reads, only: [:create]
|
||||
end
|
||||
|
||||
namespace :incoming_webhooks do
|
||||
get "/mailchimp/:secret/unsubscribe", to: "mailchimp_unsubscribes#index", as: :mailchimp_unsubscribe_check
|
||||
post "/mailchimp/:secret/unsubscribe", to: "mailchimp_unsubscribes#create", as: :mailchimp_unsubscribe
|
||||
end
|
||||
|
||||
resources :messages, only: [:create]
|
||||
resources :chat_channels, only: %i[index show create update]
|
||||
resources :chat_channel_memberships, only: %i[index create edit update destroy]
|
||||
resources :articles, only: %i[update create destroy] do
|
||||
patch "/admin_unpublish", to: "articles#admin_unpublish"
|
||||
end
|
||||
resources :article_mutes, only: %i[update]
|
||||
resources :comments, only: %i[create update destroy] do
|
||||
patch "/hide", to: "comments#hide"
|
||||
patch "/unhide", to: "comments#unhide"
|
||||
patch "/admin_delete", to: "comments#admin_delete"
|
||||
collection do
|
||||
post "/moderator_create", to: "comments#moderator_create"
|
||||
resources :stripe_active_cards, only: %i[create update destroy]
|
||||
resources :github_repos, only: %i[index] do
|
||||
collection do
|
||||
post "/update_or_create", to: "github_repos#update_or_create"
|
||||
end
|
||||
end
|
||||
end
|
||||
resources :comment_mutes, only: %i[update]
|
||||
resources :users, only: %i[index], defaults: { format: :json } # internal API
|
||||
resources :users, only: %i[update] do
|
||||
resource :twitch_stream_updates, only: %i[show create]
|
||||
end
|
||||
resources :twitch_live_streams, only: :show, param: :username
|
||||
resources :reactions, only: %i[index create]
|
||||
resources :response_templates, only: %i[index create edit update destroy]
|
||||
resources :feedback_messages, only: %i[index create]
|
||||
resources :organizations, only: %i[update create destroy]
|
||||
resources :followed_articles, only: [:index]
|
||||
resources :follows, only: %i[show create update] do
|
||||
collection do
|
||||
get "/bulk_show", to: "follows#bulk_show"
|
||||
resources :buffered_articles, only: [:index]
|
||||
resources :events, only: %i[index show]
|
||||
resources :videos, only: %i[index create new]
|
||||
resources :video_states, only: [:create]
|
||||
resources :twilio_tokens, only: [:show]
|
||||
resources :html_variants, only: %i[index new create show edit update]
|
||||
resources :html_variant_trials, only: [:create]
|
||||
resources :html_variant_successes, only: [:create]
|
||||
resources :tag_adjustments, only: %i[create destroy]
|
||||
resources :rating_votes, only: [:create]
|
||||
resources :page_views, only: %i[create update]
|
||||
resources :listings, only: %i[index new create edit update destroy dashboard]
|
||||
resources :credits, only: %i[index new create] do
|
||||
get "purchase", on: :collection, to: "credits#new"
|
||||
end
|
||||
end
|
||||
resources :image_uploads, only: [:create]
|
||||
resources :notifications, only: [:index]
|
||||
resources :tags, only: [:index] do
|
||||
collection do
|
||||
get "/onboarding", to: "tags#onboarding"
|
||||
resources :buffer_updates, only: [:create]
|
||||
resources :reading_list_items, only: [:update]
|
||||
resources :poll_votes, only: %i[show create]
|
||||
resources :poll_skips, only: [:create]
|
||||
resources :profile_pins, only: %i[create update]
|
||||
resources :partnerships, only: %i[index create show], param: :option
|
||||
resources :display_ad_events, only: [:create]
|
||||
resources :badges, only: [:index]
|
||||
resources :user_blocks, param: :blocked_id, only: %i[show create destroy]
|
||||
resources :podcasts, only: %i[new create]
|
||||
resources :article_approvals, only: %i[create]
|
||||
resources :video_chats, only: %i[show]
|
||||
resources :user_subscriptions, only: %i[create] do
|
||||
collection do
|
||||
get "/subscribed", action: "subscribed"
|
||||
end
|
||||
end
|
||||
end
|
||||
resources :stripe_active_cards, only: %i[create update destroy]
|
||||
resources :github_repos, only: %i[index] do
|
||||
collection do
|
||||
post "/update_or_create", to: "github_repos#update_or_create"
|
||||
namespace :followings, defaults: { format: :json } do
|
||||
get :users
|
||||
get :tags
|
||||
get :organizations
|
||||
get :podcasts
|
||||
end
|
||||
end
|
||||
resources :buffered_articles, only: [:index]
|
||||
resources :events, only: %i[index show]
|
||||
resources :videos, only: %i[index create new]
|
||||
resources :video_states, only: [:create]
|
||||
resources :twilio_tokens, only: [:show]
|
||||
resources :html_variants, only: %i[index new create show edit update]
|
||||
resources :html_variant_trials, only: [:create]
|
||||
resources :html_variant_successes, only: [:create]
|
||||
resources :tag_adjustments, only: %i[create destroy]
|
||||
resources :rating_votes, only: [:create]
|
||||
resources :page_views, only: %i[create update]
|
||||
resources :listings, only: %i[index new create edit update destroy dashboard]
|
||||
resources :credits, only: %i[index new create] do
|
||||
get "purchase", on: :collection, to: "credits#new"
|
||||
end
|
||||
resources :buffer_updates, only: [:create]
|
||||
resources :reading_list_items, only: [:update]
|
||||
resources :poll_votes, only: %i[show create]
|
||||
resources :poll_skips, only: [:create]
|
||||
resources :profile_pins, only: %i[create update]
|
||||
resources :partnerships, only: %i[index create show], param: :option
|
||||
resources :display_ad_events, only: [:create]
|
||||
resources :badges, only: [:index]
|
||||
resources :user_blocks, param: :blocked_id, only: %i[show create destroy]
|
||||
resources :podcasts, only: %i[new create]
|
||||
resources :article_approvals, only: %i[create]
|
||||
resources :video_chats, only: %i[show]
|
||||
resources :user_subscriptions, only: %i[create] do
|
||||
collection do
|
||||
get "/subscribed", action: "subscribed"
|
||||
|
||||
resource :onboarding, only: :show
|
||||
resources :profile_field_groups, only: %i[index], defaults: { format: :json }
|
||||
|
||||
get "/verify_email_ownership", to: "email_authorizations#verify", as: :verify_email_authorizations
|
||||
get "/search/tags" => "search#tags"
|
||||
get "/search/chat_channels" => "search#chat_channels"
|
||||
get "/search/listings" => "search#listings"
|
||||
get "/search/users" => "search#users"
|
||||
get "/search/feed_content" => "search#feed_content"
|
||||
get "/search/reactions" => "search#reactions"
|
||||
get "/chat_channel_memberships/find_by_chat_channel_id" => "chat_channel_memberships#find_by_chat_channel_id"
|
||||
get "/listings/dashboard" => "listings#dashboard"
|
||||
get "/listings/:category" => "listings#index", :as => :listing_category
|
||||
get "/listings/:category/:slug" => "listings#index", :as => :listing_slug
|
||||
get "/listings/:category/:slug/:view" => "listings#index",
|
||||
:constraints => { view: /moderate/ }
|
||||
get "/listings/:category/:slug/delete_confirm" => "listings#delete_confirm"
|
||||
delete "/listings/:category/:slug" => "listings#destroy"
|
||||
get "/notifications/:filter" => "notifications#index"
|
||||
get "/notifications/:filter/:org_id" => "notifications#index"
|
||||
get "/notification_subscriptions/:notifiable_type/:notifiable_id" => "notification_subscriptions#show"
|
||||
post "/notification_subscriptions/:notifiable_type/:notifiable_id" => "notification_subscriptions#upsert"
|
||||
patch "/onboarding_update" => "users#onboarding_update"
|
||||
patch "/onboarding_checkbox_update" => "users#onboarding_checkbox_update"
|
||||
get "email_subscriptions/unsubscribe"
|
||||
post "/chat_channels/:id/moderate" => "chat_channels#moderate"
|
||||
post "/chat_channels/:id/open" => "chat_channels#open"
|
||||
get "/connect" => "chat_channels#index"
|
||||
get "/connect/:slug" => "chat_channels#index"
|
||||
get "/chat_channels/:id/channel_info", to: "chat_channels#channel_info", as: :chat_channel_info
|
||||
post "/chat_channels/create_chat" => "chat_channels#create_chat"
|
||||
post "/chat_channels/block_chat" => "chat_channels#block_chat"
|
||||
post "/chat_channel_memberships/remove_membership" => "chat_channel_memberships#remove_membership"
|
||||
post "/chat_channel_memberships/add_membership" => "chat_channel_memberships#add_membership"
|
||||
post "/join_chat_channel" => "chat_channel_memberships#join_channel"
|
||||
delete "/messages/:id" => "messages#destroy"
|
||||
patch "/messages/:id" => "messages#update"
|
||||
get "/live/:username" => "twitch_live_streams#show"
|
||||
get "/internal", to: redirect("/admin")
|
||||
get "/internal/:path", to: redirect("/admin/%{path}")
|
||||
|
||||
post "/pusher/auth" => "pusher#auth"
|
||||
|
||||
# Chat channel
|
||||
patch "/chat_channels/update_channel/:id" => "chat_channels#update_channel"
|
||||
|
||||
# Chat Channel Membership json response
|
||||
get "/chat_channel_memberships/chat_channel_info/:id" => "chat_channel_memberships#chat_channel_info"
|
||||
post "/chat_channel_memberships/create_membership_request" => "chat_channel_memberships#create_membership_request"
|
||||
patch "/chat_channel_memberships/leave_membership/:id" => "chat_channel_memberships#leave_membership"
|
||||
patch "/chat_channel_memberships/update_membership/:id" => "chat_channel_memberships#update_membership"
|
||||
get "/channel_request_info/" => "chat_channel_memberships#request_details"
|
||||
patch "/chat_channel_memberships/update_membership_role/:id" => "chat_channel_memberships#update_membership_role"
|
||||
get "/join_channel_invitation/:channel_slug" => "chat_channel_memberships#join_channel_invitation"
|
||||
post "/joining_invitation_response" => "chat_channel_memberships#joining_invitation_response"
|
||||
|
||||
get "/social_previews/article/:id" => "social_previews#article", :as => :article_social_preview
|
||||
get "/social_previews/user/:id" => "social_previews#user", :as => :user_social_preview
|
||||
get "/social_previews/organization/:id" => "social_previews#organization", :as => :organization_social_preview
|
||||
get "/social_previews/tag/:id" => "social_previews#tag", :as => :tag_social_preview
|
||||
get "/social_previews/listing/:id" => "social_previews#listing", :as => :listing_social_preview
|
||||
get "/social_previews/comment/:id" => "social_previews#comment", :as => :comment_social_preview
|
||||
|
||||
get "/async_info/base_data", controller: "async_info#base_data", defaults: { format: :json }
|
||||
get "/async_info/shell_version", controller: "async_info#shell_version", defaults: { format: :json }
|
||||
|
||||
get "/future", to: redirect("devteam/the-future-of-dev-160n")
|
||||
get "/forem", to: redirect("devteam/for-empowering-community-2k6h")
|
||||
|
||||
# Settings
|
||||
post "users/update_language_settings" => "users#update_language_settings"
|
||||
post "users/update_twitch_username" => "users#update_twitch_username"
|
||||
post "users/join_org" => "users#join_org"
|
||||
post "users/leave_org/:organization_id" => "users#leave_org", :as => :users_leave_org
|
||||
post "users/add_org_admin" => "users#add_org_admin"
|
||||
post "users/remove_org_admin" => "users#remove_org_admin"
|
||||
post "users/remove_from_org" => "users#remove_from_org"
|
||||
delete "users/remove_identity", to: "users#remove_identity"
|
||||
post "users/request_destroy", to: "users#request_destroy", as: :user_request_destroy
|
||||
get "users/confirm_destroy/:token", to: "users#confirm_destroy", as: :user_confirm_destroy
|
||||
delete "users/full_delete", to: "users#full_delete", as: :user_full_delete
|
||||
post "organizations/generate_new_secret" => "organizations#generate_new_secret"
|
||||
post "users/api_secrets" => "api_secrets#create", :as => :users_api_secrets
|
||||
delete "users/api_secrets/:id" => "api_secrets#destroy", :as => :users_api_secret
|
||||
|
||||
# The priority is based upon order of creation: first created -> highest priority.
|
||||
# See how all your routes lay out with "rake routes".
|
||||
|
||||
# You can have the root of your site routed with "root
|
||||
get "/robots.:format" => "pages#robots"
|
||||
get "/api", to: redirect("https://docs.forem.com/api")
|
||||
get "/privacy" => "pages#privacy"
|
||||
get "/terms" => "pages#terms"
|
||||
get "/contact" => "pages#contact"
|
||||
get "/code-of-conduct" => "pages#code_of_conduct"
|
||||
get "/report-abuse" => "pages#report_abuse"
|
||||
get "/welcome" => "pages#welcome"
|
||||
get "/challenge" => "pages#challenge"
|
||||
get "/checkin" => "pages#checkin"
|
||||
get "/badge" => "pages#badge"
|
||||
get "/💸", to: redirect("t/hiring")
|
||||
get "/survey", to: redirect("https://dev.to/ben/final-thoughts-on-the-state-of-the-web-survey-44nn")
|
||||
get "/events" => "events#index"
|
||||
get "/workshops", to: redirect("events")
|
||||
get "/sponsors" => "pages#sponsors"
|
||||
get "/search" => "stories#search"
|
||||
post "articles/preview" => "articles#preview"
|
||||
post "comments/preview" => "comments#preview"
|
||||
get "/stories/warm_comments/:username/:slug" => "stories#warm_comments"
|
||||
|
||||
# These routes are required by links in the sites and will most likely to be replaced by a db page
|
||||
get "/about" => "pages#about"
|
||||
get "/about-listings" => "pages#about_listings"
|
||||
get "/security", to: "pages#bounty"
|
||||
get "/community-moderation" => "pages#community_moderation"
|
||||
get "/faq" => "pages#faq"
|
||||
get "/page/post-a-job" => "pages#post_a_job"
|
||||
get "/tag-moderation" => "pages#tag_moderation"
|
||||
|
||||
# NOTE: can't remove the hardcoded URL here as SiteConfig is not available here, we should eventually
|
||||
# setup dynamic redirects, see <https://github.com/thepracticaldev/dev.to/issues/7267>
|
||||
get "/shop", to: redirect("https://shop.dev.to")
|
||||
|
||||
get "/mod" => "moderations#index", :as => :mod
|
||||
get "/mod/:tag" => "moderations#index"
|
||||
get "/page/crayons" => "pages#crayons"
|
||||
|
||||
post "/fallback_activity_recorder" => "ga_events#create"
|
||||
|
||||
get "/page/:slug" => "pages#show"
|
||||
|
||||
scope "p" do
|
||||
pages_actions = %w[welcome editor_guide publishing_from_rss_guide information markdown_basics badges].freeze
|
||||
pages_actions.each do |action|
|
||||
get action, action: action, controller: "pages"
|
||||
end
|
||||
end
|
||||
end
|
||||
namespace :followings, defaults: { format: :json } do
|
||||
get :users
|
||||
get :tags
|
||||
get :organizations
|
||||
get :podcasts
|
||||
end
|
||||
|
||||
resource :onboarding, only: :show
|
||||
resources :profile_field_groups, only: %i[index], defaults: { format: :json }
|
||||
get "/settings/(:tab)" => "users#edit", :as => :user_settings
|
||||
get "/settings/:tab/:org_id" => "users#edit", :constraints => { tab: /organization/ }
|
||||
get "/settings/:tab/:id" => "users#edit", :constraints => { tab: /response-templates/ }
|
||||
get "/signout_confirm" => "users#signout_confirm"
|
||||
get "/dashboard" => "dashboards#show"
|
||||
get "/dashboard/pro", to: "dashboards#pro"
|
||||
get "dashboard/pro/org/:org_id", to: "dashboards#pro", as: :dashboard_pro_org
|
||||
get "dashboard/following" => "dashboards#following_tags"
|
||||
get "dashboard/following_tags" => "dashboards#following_tags"
|
||||
get "dashboard/following_users" => "dashboards#following_users"
|
||||
get "dashboard/following_organizations" => "dashboards#following_organizations"
|
||||
get "dashboard/following_podcasts" => "dashboards#following_podcasts"
|
||||
get "/dashboard/subscriptions" => "dashboards#subscriptions"
|
||||
get "/dashboard/:which" => "dashboards#followers", :constraints => { which: /user_followers/ }
|
||||
get "/dashboard/:which/:org_id" => "dashboards#show",
|
||||
:constraints => {
|
||||
which: /organization/
|
||||
}
|
||||
get "/dashboard/:username" => "dashboards#show"
|
||||
|
||||
get "/verify_email_ownership", to: "email_authorizations#verify", as: :verify_email_authorizations
|
||||
get "/search/tags" => "search#tags"
|
||||
get "/search/chat_channels" => "search#chat_channels"
|
||||
get "/search/listings" => "search#listings"
|
||||
get "/search/users" => "search#users"
|
||||
get "/search/feed_content" => "search#feed_content"
|
||||
get "/search/reactions" => "search#reactions"
|
||||
get "/chat_channel_memberships/find_by_chat_channel_id" => "chat_channel_memberships#find_by_chat_channel_id"
|
||||
get "/listings/dashboard" => "listings#dashboard"
|
||||
get "/listings/:category" => "listings#index", :as => :listing_category
|
||||
get "/listings/:category/:slug" => "listings#index", :as => :listing_slug
|
||||
get "/listings/:category/:slug/:view" => "listings#index",
|
||||
:constraints => { view: /moderate/ }
|
||||
get "/listings/:category/:slug/delete_confirm" => "listings#delete_confirm"
|
||||
delete "/listings/:category/:slug" => "listings#destroy"
|
||||
get "/notifications/:filter" => "notifications#index"
|
||||
get "/notifications/:filter/:org_id" => "notifications#index"
|
||||
get "/notification_subscriptions/:notifiable_type/:notifiable_id" => "notification_subscriptions#show"
|
||||
post "/notification_subscriptions/:notifiable_type/:notifiable_id" => "notification_subscriptions#upsert"
|
||||
patch "/onboarding_update" => "users#onboarding_update"
|
||||
patch "/onboarding_checkbox_update" => "users#onboarding_checkbox_update"
|
||||
get "email_subscriptions/unsubscribe"
|
||||
post "/chat_channels/:id/moderate" => "chat_channels#moderate"
|
||||
post "/chat_channels/:id/open" => "chat_channels#open"
|
||||
get "/connect" => "chat_channels#index"
|
||||
get "/connect/:slug" => "chat_channels#index"
|
||||
get "/chat_channels/:id/channel_info", to: "chat_channels#channel_info", as: :chat_channel_info
|
||||
post "/chat_channels/create_chat" => "chat_channels#create_chat"
|
||||
post "/chat_channels/block_chat" => "chat_channels#block_chat"
|
||||
post "/chat_channel_memberships/remove_membership" => "chat_channel_memberships#remove_membership"
|
||||
post "/chat_channel_memberships/add_membership" => "chat_channel_memberships#add_membership"
|
||||
post "/join_chat_channel" => "chat_channel_memberships#join_channel"
|
||||
delete "/messages/:id" => "messages#destroy"
|
||||
patch "/messages/:id" => "messages#update"
|
||||
get "/live/:username" => "twitch_live_streams#show"
|
||||
get "/internal", to: redirect("/admin")
|
||||
get "/internal/:path", to: redirect("/admin/%{path}")
|
||||
|
||||
post "/pusher/auth" => "pusher#auth"
|
||||
|
||||
# Chat channel
|
||||
patch "/chat_channels/update_channel/:id" => "chat_channels#update_channel"
|
||||
|
||||
# Chat Channel Membership json response
|
||||
get "/chat_channel_memberships/chat_channel_info/:id" => "chat_channel_memberships#chat_channel_info"
|
||||
post "/chat_channel_memberships/create_membership_request" => "chat_channel_memberships#create_membership_request"
|
||||
patch "/chat_channel_memberships/leave_membership/:id" => "chat_channel_memberships#leave_membership"
|
||||
patch "/chat_channel_memberships/update_membership/:id" => "chat_channel_memberships#update_membership"
|
||||
get "/channel_request_info/" => "chat_channel_memberships#request_details"
|
||||
patch "/chat_channel_memberships/update_membership_role/:id" => "chat_channel_memberships#update_membership_role"
|
||||
get "/join_channel_invitation/:channel_slug" => "chat_channel_memberships#join_channel_invitation"
|
||||
post "/joining_invitation_response" => "chat_channel_memberships#joining_invitation_response"
|
||||
|
||||
get "/social_previews/article/:id" => "social_previews#article", :as => :article_social_preview
|
||||
get "/social_previews/user/:id" => "social_previews#user", :as => :user_social_preview
|
||||
get "/social_previews/organization/:id" => "social_previews#organization", :as => :organization_social_preview
|
||||
get "/social_previews/tag/:id" => "social_previews#tag", :as => :tag_social_preview
|
||||
get "/social_previews/listing/:id" => "social_previews#listing", :as => :listing_social_preview
|
||||
get "/social_previews/comment/:id" => "social_previews#comment", :as => :comment_social_preview
|
||||
|
||||
get "/async_info/base_data", controller: "async_info#base_data", defaults: { format: :json }
|
||||
get "/async_info/shell_version", controller: "async_info#shell_version", defaults: { format: :json }
|
||||
|
||||
get "/future", to: redirect("devteam/the-future-of-dev-160n")
|
||||
get "/forem", to: redirect("devteam/for-empowering-community-2k6h")
|
||||
|
||||
# Settings
|
||||
post "users/update_language_settings" => "users#update_language_settings"
|
||||
post "users/update_twitch_username" => "users#update_twitch_username"
|
||||
post "users/join_org" => "users#join_org"
|
||||
post "users/leave_org/:organization_id" => "users#leave_org", :as => :users_leave_org
|
||||
post "users/add_org_admin" => "users#add_org_admin"
|
||||
post "users/remove_org_admin" => "users#remove_org_admin"
|
||||
post "users/remove_from_org" => "users#remove_from_org"
|
||||
delete "users/remove_identity", to: "users#remove_identity"
|
||||
post "users/request_destroy", to: "users#request_destroy", as: :user_request_destroy
|
||||
get "users/confirm_destroy/:token", to: "users#confirm_destroy", as: :user_confirm_destroy
|
||||
delete "users/full_delete", to: "users#full_delete", as: :user_full_delete
|
||||
post "organizations/generate_new_secret" => "organizations#generate_new_secret"
|
||||
post "users/api_secrets" => "api_secrets#create", :as => :users_api_secrets
|
||||
delete "users/api_secrets/:id" => "api_secrets#destroy", :as => :users_api_secret
|
||||
|
||||
# The priority is based upon order of creation: first created -> highest priority.
|
||||
# See how all your routes lay out with "rake routes".
|
||||
|
||||
# You can have the root of your site routed with "root
|
||||
get "/robots.:format" => "pages#robots"
|
||||
get "/api", to: redirect("https://docs.forem.com/api")
|
||||
get "/privacy" => "pages#privacy"
|
||||
get "/terms" => "pages#terms"
|
||||
get "/contact" => "pages#contact"
|
||||
get "/code-of-conduct" => "pages#code_of_conduct"
|
||||
get "/report-abuse" => "pages#report_abuse"
|
||||
get "/welcome" => "pages#welcome"
|
||||
get "/challenge" => "pages#challenge"
|
||||
get "/checkin" => "pages#checkin"
|
||||
get "/badge" => "pages#badge"
|
||||
get "/💸", to: redirect("t/hiring")
|
||||
get "/survey", to: redirect("https://dev.to/ben/final-thoughts-on-the-state-of-the-web-survey-44nn")
|
||||
get "/events" => "events#index"
|
||||
get "/workshops", to: redirect("events")
|
||||
get "/sponsors" => "pages#sponsors"
|
||||
get "/search" => "stories#search"
|
||||
post "articles/preview" => "articles#preview"
|
||||
post "comments/preview" => "comments#preview"
|
||||
get "/stories/warm_comments/:username/:slug" => "stories#warm_comments"
|
||||
|
||||
# These routes are required by links in the sites and will most likely to be replaced by a db page
|
||||
get "/about" => "pages#about"
|
||||
get "/about-listings" => "pages#about_listings"
|
||||
get "/security", to: "pages#bounty"
|
||||
get "/community-moderation" => "pages#community_moderation"
|
||||
get "/faq" => "pages#faq"
|
||||
get "/page/post-a-job" => "pages#post_a_job"
|
||||
get "/tag-moderation" => "pages#tag_moderation"
|
||||
|
||||
# NOTE: can't remove the hardcoded URL here as SiteConfig is not available here, we should eventually
|
||||
# setup dynamic redirects, see <https://github.com/thepracticaldev/dev.to/issues/7267>
|
||||
get "/shop", to: redirect("https://shop.dev.to")
|
||||
|
||||
get "/mod" => "moderations#index", :as => :mod
|
||||
get "/mod/:tag" => "moderations#index"
|
||||
get "/page/crayons" => "pages#crayons"
|
||||
|
||||
post "/fallback_activity_recorder" => "ga_events#create"
|
||||
|
||||
get "/page/:slug" => "pages#show"
|
||||
|
||||
scope "p" do
|
||||
pages_actions = %w[welcome editor_guide publishing_from_rss_guide information markdown_basics badges].freeze
|
||||
pages_actions.each do |action|
|
||||
get action, action: action, controller: "pages"
|
||||
# for testing rails mailers
|
||||
unless Rails.env.production?
|
||||
get "/rails/mailers" => "rails/mailers#index"
|
||||
get "/rails/mailers/*path" => "rails/mailers#preview"
|
||||
end
|
||||
|
||||
get "/embed/:embeddable", to: "liquid_embeds#show", as: "liquid_embed"
|
||||
|
||||
# serviceworkers
|
||||
get "/serviceworker" => "service_worker#index"
|
||||
get "/manifest" => "service_worker#manifest"
|
||||
|
||||
get "/shell_top" => "shell#top"
|
||||
get "/shell_bottom" => "shell#bottom"
|
||||
|
||||
get "/new" => "articles#new"
|
||||
get "/new/:template" => "articles#new"
|
||||
|
||||
get "/pod", to: "podcast_episodes#index"
|
||||
get "/podcasts", to: redirect("pod")
|
||||
get "/readinglist" => "reading_list_items#index"
|
||||
get "/readinglist/:view" => "reading_list_items#index", :constraints => { view: /archive/ }
|
||||
|
||||
get "/feed" => "articles#feed", :as => "feed", :defaults => { format: "rss" }
|
||||
get "/feed/tag/:tag" => "articles#feed", :as => "tag_feed", :defaults => { format: "rss" }
|
||||
get "/feed/:username" => "articles#feed", :as => "user_feed", :defaults => { format: "rss" }
|
||||
get "/rss" => "articles#feed", :defaults => { format: "rss" }
|
||||
|
||||
get "/tag/:tag" => "stories#index"
|
||||
get "/t/:tag", to: "stories#index", as: :tag
|
||||
get "/t/:tag/edit", to: "tags#edit"
|
||||
get "/t/:tag/admin", to: "tags#admin"
|
||||
patch "/tag/:id", to: "tags#update"
|
||||
get "/t/:tag/top/:timeframe" => "stories#index"
|
||||
get "/t/:tag/page/:page" => "stories#index"
|
||||
get "/t/:tag/:timeframe" => "stories#index",
|
||||
:constraints => { timeframe: /latest/ }
|
||||
|
||||
get "/badge/:slug" => "badges#show"
|
||||
|
||||
get "/top/:timeframe" => "stories#index"
|
||||
|
||||
get "/:timeframe" => "stories#index", :constraints => { timeframe: /latest/ }
|
||||
|
||||
get "/:username/series" => "collections#index", :as => "user_series"
|
||||
get "/:username/series/:id" => "collections#show"
|
||||
|
||||
# Legacy comment format (might still be floating around app, and external links)
|
||||
get "/:username/:slug/comments" => "comments#index"
|
||||
get "/:username/:slug/comments/:id_code" => "comments#index"
|
||||
get "/:username/:slug/comments/:id_code/edit" => "comments#edit"
|
||||
get "/:username/:slug/comments/:id_code/delete_confirm" => "comments#delete_confirm"
|
||||
|
||||
# Proper link format
|
||||
get "/:username/comment/:id_code" => "comments#index"
|
||||
get "/:username/comment/:id_code/edit" => "comments#edit"
|
||||
get "/:username/comment/:id_code/delete_confirm" => "comments#delete_confirm"
|
||||
get "/:username/comment/:id_code/mod" => "moderations#comment"
|
||||
get "/:username/comment/:id_code/settings", to: "comments#settings"
|
||||
|
||||
get "/:username/:slug/:view" => "stories#show",
|
||||
:constraints => { view: /moderate/ }
|
||||
get "/:username/:slug/mod" => "moderations#article"
|
||||
get "/:username/:slug/actions_panel" => "moderations#actions_panel"
|
||||
get "/:username/:slug/manage" => "articles#manage"
|
||||
get "/:username/:slug/edit" => "articles#edit"
|
||||
get "/:username/:slug/delete_confirm" => "articles#delete_confirm"
|
||||
get "/:username/:slug/stats" => "articles#stats"
|
||||
get "/:username/:view" => "stories#index",
|
||||
:constraints => { view: /comments|moderate|admin/ }
|
||||
get "/:username/:slug" => "stories#show"
|
||||
get "/:sitemap" => "sitemaps#show",
|
||||
:constraints => { format: /xml/, sitemap: /sitemap-.+/ }
|
||||
get "/:username" => "stories#index", :as => "user_profile"
|
||||
|
||||
root "stories#index"
|
||||
end
|
||||
|
||||
get "/settings/(:tab)" => "users#edit", :as => :user_settings
|
||||
get "/settings/:tab/:org_id" => "users#edit", :constraints => { tab: /organization/ }
|
||||
get "/settings/:tab/:id" => "users#edit", :constraints => { tab: /response-templates/ }
|
||||
get "/signout_confirm" => "users#signout_confirm"
|
||||
get "/dashboard" => "dashboards#show"
|
||||
get "/dashboard/pro", to: "dashboards#pro"
|
||||
get "dashboard/pro/org/:org_id", to: "dashboards#pro", as: :dashboard_pro_org
|
||||
get "dashboard/following" => "dashboards#following_tags"
|
||||
get "dashboard/following_tags" => "dashboards#following_tags"
|
||||
get "dashboard/following_users" => "dashboards#following_users"
|
||||
get "dashboard/following_organizations" => "dashboards#following_organizations"
|
||||
get "dashboard/following_podcasts" => "dashboards#following_podcasts"
|
||||
get "/dashboard/subscriptions" => "dashboards#subscriptions"
|
||||
get "/dashboard/:which" => "dashboards#followers", :constraints => { which: /user_followers/ }
|
||||
get "/dashboard/:which/:org_id" => "dashboards#show",
|
||||
:constraints => {
|
||||
which: /organization/
|
||||
}
|
||||
get "/dashboard/:username" => "dashboards#show"
|
||||
|
||||
# for testing rails mailers
|
||||
unless Rails.env.production?
|
||||
get "/rails/mailers" => "rails/mailers#index"
|
||||
get "/rails/mailers/*path" => "rails/mailers#preview"
|
||||
end
|
||||
|
||||
get "/embed/:embeddable", to: "liquid_embeds#show", as: "liquid_embed"
|
||||
|
||||
# serviceworkers
|
||||
get "/serviceworker" => "service_worker#index"
|
||||
get "/manifest" => "service_worker#manifest"
|
||||
|
||||
get "/shell_top" => "shell#top"
|
||||
get "/shell_bottom" => "shell#bottom"
|
||||
|
||||
get "/new" => "articles#new"
|
||||
get "/new/:template" => "articles#new"
|
||||
|
||||
get "/pod", to: "podcast_episodes#index"
|
||||
get "/podcasts", to: redirect("pod")
|
||||
get "/readinglist" => "reading_list_items#index"
|
||||
get "/readinglist/:view" => "reading_list_items#index", :constraints => { view: /archive/ }
|
||||
|
||||
get "/feed" => "articles#feed", :as => "feed", :defaults => { format: "rss" }
|
||||
get "/feed/tag/:tag" => "articles#feed", :as => "tag_feed", :defaults => { format: "rss" }
|
||||
get "/feed/:username" => "articles#feed", :as => "user_feed", :defaults => { format: "rss" }
|
||||
get "/rss" => "articles#feed", :defaults => { format: "rss" }
|
||||
|
||||
get "/tag/:tag" => "stories#index"
|
||||
get "/t/:tag", to: "stories#index", as: :tag
|
||||
get "/t/:tag/edit", to: "tags#edit"
|
||||
get "/t/:tag/admin", to: "tags#admin"
|
||||
patch "/tag/:id", to: "tags#update"
|
||||
get "/t/:tag/top/:timeframe" => "stories#index"
|
||||
get "/t/:tag/page/:page" => "stories#index"
|
||||
get "/t/:tag/:timeframe" => "stories#index",
|
||||
:constraints => { timeframe: /latest/ }
|
||||
|
||||
get "/badge/:slug" => "badges#show"
|
||||
|
||||
get "/top/:timeframe" => "stories#index"
|
||||
|
||||
get "/:timeframe" => "stories#index", :constraints => { timeframe: /latest/ }
|
||||
|
||||
get "/:username/series" => "collections#index", :as => "user_series"
|
||||
get "/:username/series/:id" => "collections#show"
|
||||
|
||||
# Legacy comment format (might still be floating around app, and external links)
|
||||
get "/:username/:slug/comments" => "comments#index"
|
||||
get "/:username/:slug/comments/:id_code" => "comments#index"
|
||||
get "/:username/:slug/comments/:id_code/edit" => "comments#edit"
|
||||
get "/:username/:slug/comments/:id_code/delete_confirm" => "comments#delete_confirm"
|
||||
|
||||
# Proper link format
|
||||
get "/:username/comment/:id_code" => "comments#index"
|
||||
get "/:username/comment/:id_code/edit" => "comments#edit"
|
||||
get "/:username/comment/:id_code/delete_confirm" => "comments#delete_confirm"
|
||||
get "/:username/comment/:id_code/mod" => "moderations#comment"
|
||||
get "/:username/comment/:id_code/settings", to: "comments#settings"
|
||||
|
||||
get "/:username/:slug/:view" => "stories#show",
|
||||
:constraints => { view: /moderate/ }
|
||||
get "/:username/:slug/mod" => "moderations#article"
|
||||
get "/:username/:slug/actions_panel" => "moderations#actions_panel"
|
||||
get "/:username/:slug/manage" => "articles#manage"
|
||||
get "/:username/:slug/edit" => "articles#edit"
|
||||
get "/:username/:slug/delete_confirm" => "articles#delete_confirm"
|
||||
get "/:username/:slug/stats" => "articles#stats"
|
||||
get "/:username/:view" => "stories#index",
|
||||
:constraints => { view: /comments|moderate|admin/ }
|
||||
get "/:username/:slug" => "stories#show"
|
||||
get "/:sitemap" => "sitemaps#show",
|
||||
:constraints => { format: /xml/, sitemap: /sitemap-.+/ }
|
||||
get "/:username" => "stories#index", :as => "user_profile"
|
||||
|
||||
root "stories#index"
|
||||
end
|
||||
|
||||
# rubocop:enable Metrics/BlockLength
|
||||
|
|
|
|||
148
docs/backend/internationalization.md
Normal file
148
docs/backend/internationalization.md
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
---
|
||||
title: Internationalization (i18n)
|
||||
---
|
||||
|
||||
# Internationalization (i18n)
|
||||
|
||||
## What is internationlization (i18n)?
|
||||
|
||||
To over simplify the concept a bit, internalization (i18n for short) is the
|
||||
process of making the platform more user-friendly in various languages for
|
||||
people around the globe. This includes, but is certainly not limited to, things
|
||||
like making the site available in different languages, changing currency values
|
||||
to match your region, changing date formats, etc.
|
||||
|
||||
## What do we currently support?
|
||||
|
||||
Currently, we allow users to set some language settings (beta) under
|
||||
`/settings/misc`. Based on your selection, the feed algorithm will prioritize
|
||||
content written in your preferred languages.
|
||||
|
||||
We also introduced some routing to lay the groundwork for a more comprehensive
|
||||
i18n implementation.
|
||||
|
||||
## What is the goal?
|
||||
|
||||
We want everyone to feel included, regardless of where they're located or what
|
||||
language(s) they speak. The goal is to make the platform available in various
|
||||
languages.
|
||||
|
||||
## How do you get involved?
|
||||
|
||||
The following is a high level outline of an approach to internationalization.
|
||||
This is by no means set in stone.
|
||||
|
||||
We encourage you to open a pull request (PR) to this documentation or to
|
||||
contribute to internationalization with your ideas - we're
|
||||
[open-source](https://github.com/forem/forem/pulls)!
|
||||
|
||||
## Routing
|
||||
|
||||
We have logic for routes setup. You can visit a page and add `/locale/:locale`
|
||||
to the beginning of the path. For example, if you visit the homepage, you can
|
||||
add `/locale/fr-ca` for French, Canadian where `fr` is the language code and
|
||||
`ca` is the region code.
|
||||
|
||||
Setting up languages under this "sub-folder" approach helps with Search Engine
|
||||
Optimization (SEO), routing, and more.
|
||||
|
||||
_Currently, the various language routes will not do anything - it will stil show
|
||||
the site in English (US)._
|
||||
|
||||
Once i18n is up and running, users will be able to select their preferred
|
||||
language to view the platform in. These routes will be the location of various
|
||||
languages.
|
||||
|
||||
## Translating content
|
||||
|
||||
There are many ways to translate static content on the platform. To start, we
|
||||
can explore tools like [i18n-tasks](https://glebm.github.io/i18n-tasks/) which
|
||||
also has an option to leverage Google Translate programmatically. We'll need to
|
||||
create locale files (likely `.yml`) to house the translations.
|
||||
|
||||
## Search Engine Optimization (SEO)
|
||||
|
||||
It seems search engines, especially Google, don't particularly like content on a
|
||||
page to be in multiple languages. To account for this on pages like articles, we
|
||||
can try an approach using the canonical URL for the language the article was
|
||||
written in.
|
||||
|
||||
For example, if we detect an article is written in Spanish, we can set the
|
||||
canonical URL for that article to be `/locale/es/username/article-slug`. We can
|
||||
then hide comments that are not in the same language as the article/rest of the
|
||||
page (Spanish in this example) only for the views the crawlers would see. That
|
||||
way, when the search engine crawler hits an article written in Spanish, the
|
||||
crawler will see the entire page in Spanish. We will not hide comments for the
|
||||
views that real users see.
|
||||
|
||||
## Caching and service workers
|
||||
|
||||
The platform relies on edge caching, especially with regards to articles. To
|
||||
account for this, we'll need to add logic at the edge that understands what
|
||||
languages the platform currently supports and where to look up the language
|
||||
variant in the cache.
|
||||
|
||||
If the edge doesn't pick up on a user selected preference (possibly sent as an
|
||||
additional header or cookie), the edge will look at the `Accept-Language` header
|
||||
and normalize it. The header can include more specific preferences and look
|
||||
something like: `Accept-Language: fr-ca, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5`.
|
||||
There are 2 things going on here. 1) A user can specify country/region variants
|
||||
for a language - `fr-ca` (French - Canada) and `fr-fr` (French - France). For
|
||||
simplicity's sake, we want to normalize that sort of preference to `fr` to
|
||||
start. 2) A user can specify priority using the q argument. We'll want to
|
||||
interpret the user's priority preferences to match their highest priority
|
||||
language with one we currently support.
|
||||
|
||||
Once the edge is aware of what language it should be looking for, it will set
|
||||
the cache key accordingly.
|
||||
|
||||
We also make use of fragment caching in several places. We need to update the
|
||||
keys for those caches to account for `locale` so we're not mistakenly serving a
|
||||
cached fragment in a different language than intended.
|
||||
|
||||
Service workers load some parts of the UI up front, like the shell. Therefore,
|
||||
the service worker will need to be aware of language preferences so that it
|
||||
loads the shell in the correct language. We also need to make sure the service
|
||||
worker/cache is aware of a change in the user's language preference.
|
||||
|
||||
## Additional considerations
|
||||
|
||||
- _Translating URLs_. For the best SEO result, we should also translate URLs
|
||||
themselves into various languages. Something like the `/about` page could be
|
||||
translated, for example. For now, we aren't going to account for this.
|
||||
- _Translating dynamic/user generated content_. For now, we plan to _not_
|
||||
automatically translate any dynamic/user generated content (articles,
|
||||
comments, listings, etc.). In the future we could explore what that looks
|
||||
like, how a user can opt-in/out of that, etc.
|
||||
|
||||
## Styles, design, and UI
|
||||
|
||||
We'll want to expand some design aspects to support other languages that may be
|
||||
right-to-left, have different spacings, have special characters, etc.
|
||||
|
||||
## Next steps
|
||||
|
||||
A few next steps we can take on the road to internationalization.
|
||||
|
||||
- Update our logic to allow special characters/encodings in URLs. Currently, we
|
||||
generate slugs on dynamic content like articles and tags that may include
|
||||
characters that make the URL
|
||||
invalid.[Here](https://github.com/forem/forem/issues/10116) is a good example.
|
||||
We want to update this logic so these characters work in URLs as expected.
|
||||
- Allow Forem Admins to set a "default language". Currently, if a user doesn't
|
||||
select a language preference, it defaults to English ("en").
|
||||
- Update service workers and caching to interpret language preferences.
|
||||
- Clean up some code. There are some places we're hard-coding strings on the
|
||||
frontend. We'll want to explore moving that sort data to the backend to unify
|
||||
where and how we're translating.
|
||||
- Translate areas of the site into English (US) first to ensure things are still
|
||||
working. In other words, have the platform adhere to the default locale
|
||||
instead of hard-coded strings.
|
||||
- Start translating!
|
||||
|
||||
## Resources
|
||||
|
||||
- [Rails Guides - Rails Internationalization (I18n)](https://guides.rubyonrails.org/i18n.html)
|
||||
- [i18n-tasks](https://glebm.github.io/i18n-tasks/)
|
||||
- [Google: Managing multi-regional and multilingual sites](https://support.google.com/webmasters/answer/182192)
|
||||
- [forem.dev post: What internationalization features should we support?](https://forem.dev/vaidehijoshi/what-internationalization-features-should-we-support-4kl)
|
||||
|
|
@ -11,6 +11,7 @@ items:
|
|||
- data-update-scripts.md
|
||||
- elasticsearch.md
|
||||
- fastly.md
|
||||
- internationalization.md
|
||||
- roles.md
|
||||
- pusher.md
|
||||
- resource-admin.md
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ RSpec.describe "all routes", type: :routing do
|
|||
controller: "stories",
|
||||
action: "index",
|
||||
username: podcast.slug,
|
||||
locale: nil, # default locale
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -17,6 +18,7 @@ RSpec.describe "all routes", type: :routing do
|
|||
controller: "stories",
|
||||
action: "index",
|
||||
username: user.username,
|
||||
locale: nil, # default locale
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -26,6 +28,7 @@ RSpec.describe "all routes", type: :routing do
|
|||
action: "show",
|
||||
slug: "this-is-a-slug",
|
||||
username: "ben",
|
||||
locale: nil, # default locale
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
|||
34
spec/routing/i18n_routes_spec.rb
Normal file
34
spec/routing/i18n_routes_spec.rb
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
require "rails_helper"
|
||||
|
||||
RSpec.describe "i8n routes", type: :routing do
|
||||
let(:locale) { "fr-ca" }
|
||||
let(:i18n_route) { "/locale/#{locale}" }
|
||||
let(:user) { create(:user) }
|
||||
|
||||
it "renders a user index if there is a user with the username successfully" do
|
||||
expect(get: "#{i18n_route}/#{user.username}").to route_to(
|
||||
controller: "stories",
|
||||
action: "index",
|
||||
username: user.username,
|
||||
locale: "fr-ca",
|
||||
)
|
||||
end
|
||||
|
||||
it "renders a user's story successfully" do
|
||||
expect(get: "#{i18n_route}/ben/this-is-a-slug").to route_to(
|
||||
controller: "stories",
|
||||
action: "show",
|
||||
slug: "this-is-a-slug",
|
||||
username: "ben",
|
||||
locale: "fr-ca",
|
||||
)
|
||||
end
|
||||
|
||||
it "renders homepage successfully" do
|
||||
expect(get: i18n_route).to route_to(
|
||||
controller: "stories",
|
||||
action: "index",
|
||||
locale: "fr-ca",
|
||||
)
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Reference in a new issue