diff --git a/app/controllers/permalinks_controller.rb b/app/controllers/permalinks_controller.rb new file mode 100644 index 000000000..6a01035ac --- /dev/null +++ b/app/controllers/permalinks_controller.rb @@ -0,0 +1,12 @@ +class PermalinksController < ApplicationController + skip_before_filter :check_xhr, :preload_json + + def show + permalink = Permalink.find_by_url(params[:url]) + if permalink && permalink.target_url + redirect_to permalink.target_url, status: :moved_permanently + else + raise Discourse::NotFound + end + end +end diff --git a/app/models/permalink.rb b/app/models/permalink.rb new file mode 100644 index 000000000..d0d06e1f0 --- /dev/null +++ b/app/models/permalink.rb @@ -0,0 +1,21 @@ +class Permalink < ActiveRecord::Base + belongs_to :topic + belongs_to :post + belongs_to :category + + before_validation :normalize_url + + def normalize_url + if self.url + self.url = self.url.strip + self.url = self.url[1..-1] if url[0,1] == '/' + end + end + + def target_url + return post.url if post + return topic.relative_url if topic + return category.url if category + nil + end +end diff --git a/config/application.rb b/config/application.rb index f290bd5c9..357621b23 100644 --- a/config/application.rb +++ b/config/application.rb @@ -148,6 +148,10 @@ module Discourse require 'auth' Discourse.activate_plugins! unless Rails.env.test? and ENV['LOAD_PLUGINS'] != "1" + initializer :add_last_routes, :after => :add_routing_paths do |app| + app.routes_reloader.paths << File.join(Rails.root, 'config', 'routes_last.rb') + end + config.after_initialize do # So open id logs somewhere sane OpenID::Util.logger = Rails.logger diff --git a/config/routes.rb b/config/routes.rb index e375a258b..3cb72a1fa 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -425,4 +425,5 @@ Discourse::Application.routes.draw do # special case for top root to: "list#top", constraints: HomePageConstraint.new("top"), :as => "top_lists" + # See config/routes_last.rb for more end diff --git a/config/routes_last.rb b/config/routes_last.rb new file mode 100644 index 000000000..5382e5f9e --- /dev/null +++ b/config/routes_last.rb @@ -0,0 +1,10 @@ +# These routes must be loaded after all others. +# Routes are loaded in this order: +# +# 1. config/routes.rb +# 2. routes in engines +# 3. config/routes_last.rb + +Discourse::Application.routes.draw do + get "*url", to: 'permalinks#show' +end diff --git a/db/migrate/20140828172407_create_permalinks.rb b/db/migrate/20140828172407_create_permalinks.rb new file mode 100644 index 000000000..5e4d9ce9b --- /dev/null +++ b/db/migrate/20140828172407_create_permalinks.rb @@ -0,0 +1,14 @@ +class CreatePermalinks < ActiveRecord::Migration + def change + create_table :permalinks do |t| + t.string :url, null: false + t.integer :topic_id + t.integer :post_id + t.integer :category_id + + t.timestamps + end + + add_index :permalinks, :url + end +end diff --git a/spec/controllers/permalinks_controller_spec.rb b/spec/controllers/permalinks_controller_spec.rb new file mode 100644 index 000000000..afc50e364 --- /dev/null +++ b/spec/controllers/permalinks_controller_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe PermalinksController do + describe 'show' do + it "should redirect to a permalink's target_url with status 301" do + permalink = Fabricate(:permalink) + Permalink.any_instance.stubs(:target_url).returns('/t/the-topic-slug/42') + get :show, url: permalink.url + response.should redirect_to('/t/the-topic-slug/42') + response.status.should == 301 + end + + it 'return 404 if permalink record does not exist' do + get :show, url: '/not/a/valid/url' + response.status.should == 404 + end + end + +end diff --git a/spec/fabricators/permalink_fabricator.rb b/spec/fabricators/permalink_fabricator.rb new file mode 100644 index 000000000..22a3c59de --- /dev/null +++ b/spec/fabricators/permalink_fabricator.rb @@ -0,0 +1,3 @@ +Fabricator(:permalink) do + url { sequence(:url) {|i| "my/#{i}/url" } } +end \ No newline at end of file diff --git a/spec/models/permalink_spec.rb b/spec/models/permalink_spec.rb new file mode 100644 index 000000000..de5118fa3 --- /dev/null +++ b/spec/models/permalink_spec.rb @@ -0,0 +1,72 @@ +require "spec_helper" + +describe Permalink do + + describe "new record" do + it "strips blanks" do + permalink = described_class.create(url: " my/old/url ") + permalink.url.should == "my/old/url" + end + + it "removes leading slash" do + permalink = described_class.create(url: "/my/old/url") + permalink.url.should == "my/old/url" + end + end + + describe "target_url" do + + let(:permalink) { Fabricate.build(:permalink) } + let(:topic) { Fabricate(:topic) } + let(:post) { Fabricate(:post, topic: topic) } + let(:category) { Fabricate(:category) } + subject(:target_url) { permalink.target_url } + + it "returns a topic url when topic_id is set" do + permalink.topic_id = topic.id + target_url.should == topic.relative_url + end + + it "returns nil when topic_id is set but topic is not found" do + permalink.topic_id = 99999 + target_url.should be_nil + end + + it "returns a post url when post_id is set" do + permalink.post_id = post.id + target_url.should == post.url + end + + it "returns nil when post_id is set but post is not found" do + permalink.post_id = 99999 + target_url.should be_nil + end + + it "returns a post url when post_id and topic_id are both set" do + permalink.post_id = post.id + permalink.topic_id = topic.id + target_url.should == post.url + end + + it "returns a category url when category_id is set" do + permalink.category_id = category.id + target_url.should == category.url + end + + it "returns nil when category_id is set but category is not found" do + permalink.category_id = 99999 + target_url.should be_nil + end + + it "returns a post url when topic_id, post_id, and category_id are all set for some reason" do + permalink.post_id = post.id + permalink.topic_id = topic.id + permalink.category_id = category.id + target_url.should == post.url + end + + it "returns nil when nothing is set" do + target_url.should be_nil + end + end +end