diff --git a/app/assets/javascripts/admin/addon/adapters/permalink.js b/app/assets/javascripts/admin/addon/adapters/permalink.js
new file mode 100644
index 00000000000..44cca43b0e7
--- /dev/null
+++ b/app/assets/javascripts/admin/addon/adapters/permalink.js
@@ -0,0 +1,7 @@
+import RestAdapter from "discourse/adapters/rest";
+
+export default class Permalink extends RestAdapter {
+ basePath() {
+ return "/admin/";
+ }
+}
diff --git a/app/assets/javascripts/admin/addon/components/admin-permalink-form.gjs b/app/assets/javascripts/admin/addon/components/admin-permalink-form.gjs
new file mode 100644
index 00000000000..7225fcb1278
--- /dev/null
+++ b/app/assets/javascripts/admin/addon/components/admin-permalink-form.gjs
@@ -0,0 +1,257 @@
+import Component from "@glimmer/component";
+import { cached } from "@glimmer/tracking";
+import { inject as controller } from "@ember/controller";
+import { action } from "@ember/object";
+import { service } from "@ember/service";
+import { isEmpty } from "@ember/utils";
+import { eq } from "truth-helpers";
+import BackButton from "discourse/components/back-button";
+import Form from "discourse/components/form";
+import { popupAjaxError } from "discourse/lib/ajax-error";
+import i18n from "discourse-common/helpers/i18n";
+import { bind } from "discourse-common/utils/decorators";
+import AdminConfigAreaCard from "admin/components/admin-config-area-card";
+import Permalink from "admin/models/permalink";
+
+const TYPE_TO_FIELD_MAP = {
+ topic: "topicId",
+ post: "postId",
+ category: "categoryId",
+ tag: "tagName",
+ user: "userId",
+ external_url: "externalUrl",
+};
+
+export default class AdminFlagsForm extends Component {
+ @service router;
+ @service store;
+ @controller adminPermalinks;
+
+ get isUpdate() {
+ return this.args.permalink;
+ }
+
+ @cached
+ get formData() {
+ if (this.isUpdate) {
+ let permalinkType;
+ let permalinkValue;
+ if (!isEmpty(this.args.permalink.topic_id)) {
+ permalinkType = "topic";
+ permalinkValue = this.args.permalink.topic_id;
+ } else if (!isEmpty(this.args.permalink.post_id)) {
+ permalinkType = "post";
+ permalinkValue = this.args.permalink.post_id;
+ } else if (!isEmpty(this.args.permalink.category_id)) {
+ permalinkType = "category";
+ permalinkValue = this.args.permalink.category_id;
+ } else if (!isEmpty(this.args.permalink.tag_name)) {
+ permalinkType = "tag";
+ permalinkValue = this.args.permalink.tag_name;
+ } else if (!isEmpty(this.args.permalink.external_url)) {
+ permalinkType = "external_url";
+ permalinkValue = this.args.permalink.external_url;
+ } else if (!isEmpty(this.args.permalink.user_id)) {
+ permalinkType = "user";
+ permalinkValue = this.args.permalink.user_id;
+ }
+
+ return {
+ url: this.args.permalink.url,
+ [TYPE_TO_FIELD_MAP[permalinkType]]: permalinkValue,
+ permalinkType,
+ };
+ } else {
+ return {
+ permalinkType: "topic",
+ };
+ }
+ }
+
+ get header() {
+ return this.isUpdate
+ ? "admin.permalink.form.edit_header"
+ : "admin.permalink.form.add_header";
+ }
+
+ @action
+ save(data) {
+ const createOrUpdate = this.isUpdate ? this.update : this.create;
+ createOrUpdate(data);
+ }
+
+ @bind
+ async create(data) {
+ try {
+ const result = await this.store.createRecord("permalink").save({
+ url: data.url,
+ permalink_type: data.permalinkType,
+ permalink_type_value: this.valueForPermalinkType(data),
+ });
+ this.adminPermalinks.model.unshiftObject(
+ Permalink.create(result.payload)
+ );
+ this.router.transitionTo("adminPermalinks");
+ } catch (error) {
+ popupAjaxError(error);
+ }
+ }
+
+ @bind
+ async update(data) {
+ try {
+ const result = await this.store.update(
+ "permalink",
+ this.args.permalink.id,
+ {
+ url: data.url,
+ permalink_type: data.permalinkType,
+ permalink_type_value: this.valueForPermalinkType(data),
+ }
+ );
+ const index = this.adminPermalinks.model.findIndex(
+ (permalink) => permalink.id === this.args.permalink.id
+ );
+ this.adminPermalinks.model[index] = Permalink.create(result.payload);
+ this.router.transitionTo("adminPermalinks");
+ } catch (error) {
+ popupAjaxError(error);
+ }
+ }
+
+ valueForPermalinkType(data) {
+ return data[TYPE_TO_FIELD_MAP[data.permalinkType]];
+ }
+
+ validatePermalinkTypeValue(data, { removeError }) {
+ Object.keys(TYPE_TO_FIELD_MAP).forEach((type) => {
+ if (data.permalinkType !== type) {
+ removeError(TYPE_TO_FIELD_MAP[type]);
+ }
+ });
+ }
+
+
+
+
+
+
+ <:content>
+
+
+
+
+
+
+ {{i18n
+ "admin.permalink.topic_title"
+ }}
+ {{i18n
+ "admin.permalink.post_title"
+ }}
+ {{i18n
+ "admin.permalink.category_title"
+ }}
+ {{i18n
+ "admin.permalink.tag_title"
+ }}
+ {{i18n
+ "admin.permalink.external_url"
+ }}
+ {{i18n
+ "admin.permalink.user_title"
+ }}
+
+
+ {{#if (eq transientData.permalinkType "topic")}}
+
+
+
+ {{/if}}
+ {{#if (eq transientData.permalinkType "post")}}
+
+
+
+ {{/if}}
+ {{#if (eq transientData.permalinkType "category")}}
+
+
+
+ {{/if}}
+ {{#if (eq transientData.permalinkType "tag")}}
+
+
+
+ {{/if}}
+ {{#if (eq transientData.permalinkType "external_url")}}
+
+
+
+ {{/if}}
+ {{#if (eq transientData.permalinkType "user")}}
+
+
+
+ {{/if}}
+
+
+
+
+
+
+
+
+}
diff --git a/app/assets/javascripts/admin/addon/controllers/admin-permalinks.js b/app/assets/javascripts/admin/addon/controllers/admin-permalinks-index.js
similarity index 63%
rename from app/assets/javascripts/admin/addon/controllers/admin-permalinks.js
rename to app/assets/javascripts/admin/addon/controllers/admin-permalinks-index.js
index 854ccbe45b1..c7d183add03 100644
--- a/app/assets/javascripts/admin/addon/controllers/admin-permalinks.js
+++ b/app/assets/javascripts/admin/addon/controllers/admin-permalinks-index.js
@@ -9,8 +9,10 @@ import discourseDebounce from "discourse-common/lib/debounce";
import I18n from "discourse-i18n";
import Permalink from "admin/models/permalink";
-export default class AdminPermalinksController extends Controller {
+export default class AdminPermalinksIndexController extends Controller {
@service dialog;
+ @service router;
+ @service toasts;
loading = false;
filter = null;
@@ -29,35 +31,35 @@ export default class AdminPermalinksController extends Controller {
discourseDebounce(this, this._debouncedShow, INPUT_DELAY);
}
- @action
- recordAdded(arg) {
- this.model.unshiftObject(arg);
- }
-
@action
copyUrl(pl) {
let linkElement = document.querySelector(`#admin-permalink-${pl.id}`);
clipboardCopy(linkElement.textContent);
- }
-
- @action
- destroyRecord(record) {
- return this.dialog.yesNoConfirm({
- message: I18n.t("admin.permalink.delete_confirm"),
- didConfirm: () => {
- return record.destroy().then(
- (deleted) => {
- if (deleted) {
- this.model.removeObject(record);
- } else {
- this.dialog.alert(I18n.t("generic_error"));
- }
- },
- function () {
- this.dialog.alert(I18n.t("generic_error"));
- }
- );
+ this.toasts.success({
+ duration: 3000,
+ data: {
+ message: I18n.t("admin.permalink.copy_success"),
},
});
}
+
+ @action
+ destroyRecord(permalink) {
+ this.dialog.yesNoConfirm({
+ message: I18n.t("admin.permalink.delete_confirm"),
+ didConfirm: async () => {
+ try {
+ await this.store.destroyRecord("permalink", permalink);
+ this.model.removeObject(permalink);
+ } catch {
+ this.dialog.alert(I18n.t("generic_error"));
+ }
+ },
+ });
+ }
+
+ @action
+ edit(record) {
+ this.router.transitionTo("adminPermalinks.edit", record);
+ }
}
diff --git a/app/assets/javascripts/admin/addon/models/permalink.js b/app/assets/javascripts/admin/addon/models/permalink.js
index 21bafb83de9..af4ef5bdefd 100644
--- a/app/assets/javascripts/admin/addon/models/permalink.js
+++ b/app/assets/javascripts/admin/addon/models/permalink.js
@@ -1,10 +1,10 @@
-import EmberObject from "@ember/object";
import { ajax } from "discourse/lib/ajax";
import DiscourseURL from "discourse/lib/url";
import Category from "discourse/models/category";
+import RestModel from "discourse/models/rest";
import discourseComputed from "discourse-common/utils/decorators";
-export default class Permalink extends EmberObject {
+export default class Permalink extends RestModel {
static findAll(filter) {
return ajax("/admin/permalinks.json", { data: { filter } }).then(function (
permalinks
@@ -13,17 +13,6 @@ export default class Permalink extends EmberObject {
});
}
- save() {
- return ajax("/admin/permalinks.json", {
- type: "POST",
- data: {
- url: this.url,
- permalink_type: this.permalink_type,
- permalink_type_value: this.permalink_type_value,
- },
- });
- }
-
@discourseComputed("category_id")
category(category_id) {
return Category.findById(category_id);
@@ -34,9 +23,8 @@ export default class Permalink extends EmberObject {
return !DiscourseURL.isInternal(external_url);
}
- destroy() {
- return ajax("/admin/permalinks/" + this.id + ".json", {
- type: "DELETE",
- });
+ @discourseComputed("url")
+ key(url) {
+ return url.replace("/", "_");
}
}
diff --git a/app/assets/javascripts/admin/addon/routes/admin-permalinks-edit.js b/app/assets/javascripts/admin/addon/routes/admin-permalinks-edit.js
new file mode 100644
index 00000000000..49d77e1d42c
--- /dev/null
+++ b/app/assets/javascripts/admin/addon/routes/admin-permalinks-edit.js
@@ -0,0 +1,10 @@
+import { service } from "@ember/service";
+import DiscourseRoute from "discourse/routes/discourse";
+
+export default class AdminPermalinksEditRoute extends DiscourseRoute {
+ @service store;
+
+ model(params) {
+ return this.store.find("permalink", params.permalink_id);
+ }
+}
diff --git a/app/assets/javascripts/admin/addon/routes/admin-route-map.js b/app/assets/javascripts/admin/addon/routes/admin-route-map.js
index facec60a4f4..df898346b46 100644
--- a/app/assets/javascripts/admin/addon/routes/admin-route-map.js
+++ b/app/assets/javascripts/admin/addon/routes/admin-route-map.js
@@ -73,10 +73,16 @@ export default function () {
resetNamespace: true,
});
this.route("adminEmojis", { path: "/emojis", resetNamespace: true });
- this.route("adminPermalinks", {
- path: "/permalinks",
- resetNamespace: true,
- });
+
+ this.route(
+ "adminPermalinks",
+ { path: "/permalinks", resetNamespace: true },
+ function () {
+ this.route("new");
+ this.route("edit", { path: "/:permalink_id" });
+ }
+ );
+
this.route("adminEmbedding", {
path: "/embedding",
resetNamespace: true,
diff --git a/app/assets/javascripts/admin/addon/templates/permalinks-edit.hbs b/app/assets/javascripts/admin/addon/templates/permalinks-edit.hbs
new file mode 100644
index 00000000000..f74b91cf898
--- /dev/null
+++ b/app/assets/javascripts/admin/addon/templates/permalinks-edit.hbs
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/assets/javascripts/admin/addon/templates/permalinks-index.hbs b/app/assets/javascripts/admin/addon/templates/permalinks-index.hbs
new file mode 100644
index 00000000000..5d26eb8a7c5
--- /dev/null
+++ b/app/assets/javascripts/admin/addon/templates/permalinks-index.hbs
@@ -0,0 +1,118 @@
+
+ <:actions as |actions|>
+
+
+
+
+
+
+
+
+
+ {{#if this.model.length}}
+
+
+ {{i18n "admin.permalink.url"}} |
+ {{i18n "admin.permalink.destination"}} |
+
+
+ {{#each this.model as |pl|}}
+
+
+
+ {{pl.url}}
+ |
+
+ {{#if pl.topic_id}}
+ {{pl.topic_title}}
+ {{/if}}
+ {{#if pl.post_id}}
+ {{pl.post_topic_title}}
+ #{{pl.post_number}}
+ {{/if}}
+ {{#if pl.category_id}}
+ {{category-link pl.category}}
+ {{/if}}
+ {{#if pl.tag_id}}
+ {{pl.tag_name}}
+ {{/if}}
+ {{#if pl.external_url}}
+ {{#if pl.linkIsExternal}}
+ {{d-icon "up-right-from-square"}}
+ {{/if}}
+ {{pl.external_url}}
+ {{/if}}
+ {{#if pl.user_id}}
+ {{pl.username}}
+ {{/if}}
+ |
+
+
+
+
+
+ <:content>
+
+
+
+
+
+
+
+
+ |
+
+ {{/each}}
+
+
+ {{else}}
+ {{#if this.filter}}
+
{{i18n "search.no_results"}}
+ {{else}}
+
{{i18n
+ "admin.permalink.no_permalinks"
+ }}
+ {{/if}}
+ {{/if}}
+
+
\ No newline at end of file
diff --git a/app/assets/javascripts/admin/addon/templates/permalinks-new.hbs b/app/assets/javascripts/admin/addon/templates/permalinks-new.hbs
new file mode 100644
index 00000000000..824a49fedef
--- /dev/null
+++ b/app/assets/javascripts/admin/addon/templates/permalinks-new.hbs
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/assets/javascripts/admin/addon/templates/permalinks.hbs b/app/assets/javascripts/admin/addon/templates/permalinks.hbs
index 135b614467f..d839ec147ef 100644
--- a/app/assets/javascripts/admin/addon/templates/permalinks.hbs
+++ b/app/assets/javascripts/admin/addon/templates/permalinks.hbs
@@ -1,88 +1,17 @@
-
{{i18n "admin.permalink.title"}}
+
+
+ <:breadcrumbs>
+
+
+
-
- {{i18n "admin.permalink.description"}}
-
-
-
-
-
-
+
+ {{outlet}}
-
-
- {{#if this.model.length}}
-
-
- {{i18n "admin.permalink.url"}} |
- {{i18n
- "admin.permalink.destination"
- }} |
- |
-
-
- {{#each this.model as |pl|}}
-
-
-
- {{pl.url}}
- |
-
- {{#if pl.topic_id}}
- {{pl.topic_title}}
- {{/if}}
- {{#if pl.post_id}}
- {{pl.post_topic_title}}
- #{{pl.post_number}}
- {{/if}}
- {{#if pl.category_id}}
- {{category-link pl.category}}
- {{/if}}
- {{#if pl.tag_id}}
- {{pl.tag_name}}
- {{/if}}
- {{#if pl.external_url}}
- {{#if pl.linkIsExternal}}
- {{d-icon "up-right-from-square"}}
- {{/if}}
- {{pl.external_url}}
- {{/if}}
- {{#if pl.user_id}}
- {{pl.username}}
- {{/if}}
- |
-
-
- |
-
- {{/each}}
-
-
- {{else}}
- {{#if this.filter}}
-
{{i18n "search.no_results"}}
- {{else}}
-
{{i18n
- "admin.permalink.no_permalinks"
- }}
- {{/if}}
- {{/if}}
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/assets/stylesheets/common/admin/customize.scss b/app/assets/stylesheets/common/admin/customize.scss
index 6c4bc21f48e..67e7d04b849 100644
--- a/app/assets/stylesheets/common/admin/customize.scss
+++ b/app/assets/stylesheets/common/admin/customize.scss
@@ -797,49 +797,36 @@
color: var(--primary-medium);
}
-// Permalinks
-.permalinks {
- .url,
- .topic,
- .category,
- .external_url,
- .destination,
- .post {
- @include ellipsis;
- max-width: 100px;
- @include breakpoint(tablet) {
- max-width: 100%;
- }
- }
-
- &.grid tr.admin-list-item {
- grid-template-columns: unset;
- }
-}
-
-.permalink-form {
- padding: 0.5em 1em 0 1em;
- margin-top: 1em;
- background: var(--primary-very-low);
- .select-kit {
- max-width: 260px;
- }
+.admin-permalinks {
@include breakpoint(tablet) {
- label {
+ .admin-page-subheader,
+ .admin-config-area,
+ .admin-config-area__primary-content,
+ .loading-container {
width: 100%;
}
+ .destination {
+ margin-top: 0.5em;
+ }
+ .d-admin-row__controls-options {
+ padding-bottom: 1em;
+ }
+ td {
+ width: auto;
+ }
+ }
+ .permalink-search input {
+ width: 100%;
}
}
-
-.permalink-description {
- color: var(--primary-medium);
-}
-
-.permalink-search {
- margin-top: 2em;
- input {
- min-width: 250px;
- margin-bottom: 0;
+.admin-permalink-item {
+ &__delete.btn,
+ &__delete.btn:hover {
+ border-top: 1px solid var(--primary-low);
+ color: var(--danger);
+ svg {
+ color: var(--danger);
+ }
}
}
diff --git a/app/controllers/admin/permalinks_controller.rb b/app/controllers/admin/permalinks_controller.rb
index 54bea1f5280..d774abe86ba 100644
--- a/app/controllers/admin/permalinks_controller.rb
+++ b/app/controllers/admin/permalinks_controller.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class Admin::PermalinksController < Admin::AdminController
- before_action :fetch_permalink, only: [:destroy]
+ before_action :fetch_permalink, only: %i[show update destroy]
def index
url = params[:filter]
@@ -9,23 +9,38 @@ class Admin::PermalinksController < Admin::AdminController
render_serialized(permalinks, PermalinkSerializer)
end
+ def new
+ end
+
+ def edit
+ end
+
+ def show
+ render_serialized(@permalink, PermalinkSerializer)
+ end
+
def create
- params.require(:url)
- params.require(:permalink_type)
- params.require(:permalink_type_value)
-
- if params[:permalink_type] == "tag_name"
- params[:permalink_type] = "tag_id"
- params[:permalink_type_value] = Tag.find_by_name(params[:permalink_type_value])&.id
- end
-
permalink =
- Permalink.new(:url => params[:url], params[:permalink_type] => params[:permalink_type_value])
- if permalink.save
- render_serialized(permalink, PermalinkSerializer)
- else
- render_json_error(permalink)
- end
+ Permalink.create!(
+ url: permalink_params[:url],
+ permalink_type: permalink_params[:permalink_type],
+ permalink_type_value: permalink_params[:permalink_type_value],
+ )
+ render_serialized(permalink, PermalinkSerializer)
+ rescue ActiveRecord::RecordInvalid => e
+ render_json_error(e.record.errors.full_messages)
+ end
+
+ def update
+ @permalink.update!(
+ url: permalink_params[:url],
+ permalink_type: permalink_params[:permalink_type],
+ permalink_type_value: permalink_params[:permalink_type_value],
+ )
+
+ render_serialized(@permalink, PermalinkSerializer)
+ rescue ActiveRecord::RecordInvalid => e
+ render_json_error(e.record.errors.full_messages)
end
def destroy
@@ -38,4 +53,8 @@ class Admin::PermalinksController < Admin::AdminController
def fetch_permalink
@permalink = Permalink.find(params[:id])
end
+
+ def permalink_params
+ params.require(:permalink).permit(:url, :permalink_type, :permalink_type_value)
+ end
end
diff --git a/app/models/permalink.rb b/app/models/permalink.rb
index ca07c75be2c..0f786943318 100644
--- a/app/models/permalink.rb
+++ b/app/models/permalink.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class Permalink < ActiveRecord::Base
+ attr_accessor :permalink_type, :permalink_type_value
+
belongs_to :topic
belongs_to :post
belongs_to :category
@@ -8,9 +10,22 @@ class Permalink < ActiveRecord::Base
belongs_to :user
before_validation :normalize_url, :encode_url
+ before_validation :set_association_value
+ before_update :clear_associations
validates :url, uniqueness: true
+ validates :topic_id, presence: true, if: Proc.new { |permalink| permalink.topic_type? }
+ validates :post_id, presence: true, if: Proc.new { |permalink| permalink.post_type? }
+ validates :category_id, presence: true, if: Proc.new { |permalink| permalink.category_type? }
+ validates :tag_id, presence: true, if: Proc.new { |permalink| permalink.tag_type? }
+ validates :user_id, presence: true, if: Proc.new { |permalink| permalink.user_type? }
+ validates :external_url, presence: true, if: Proc.new { |permalink| permalink.external_url_type? }
+
+ %i[topic post category tag user external_url].each do |association|
+ define_method("#{association}_type?") { self.permalink_type == association.to_s }
+ end
+
class Normalizer
attr_reader :source
@@ -98,6 +113,24 @@ class Permalink < ActiveRecord::Base
def relative_external_url
external_url.match?(%r{\A/[^/]}) ? "#{Discourse.base_path}#{external_url}" : external_url
end
+
+ def clear_associations
+ self.topic_id = nil if !self.topic_type?
+ self.post_id = nil if !self.post_type?
+ self.category_id = nil if !self.category_type?
+ self.user_id = nil if !self.user_type?
+ self.tag_id = nil if !self.tag_type?
+ self.external_url = nil if !self.external_url_type?
+ end
+
+ def set_association_value
+ self.topic_id = self.permalink_type_value if self.topic_type?
+ self.post_id = self.permalink_type_value if self.post_type?
+ self.user_id = self.permalink_type_value if self.user_type?
+ self.category_id = self.permalink_type_value if self.category_type?
+ self.external_url = self.permalink_type_value if self.external_url_type?
+ self.tag_id = Tag.where(name: self.permalink_type_value).first&.id if self.tag_type?
+ end
end
# == Schema Information
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index ce02d6b9c6a..984d02214e3 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -7252,17 +7252,27 @@ en:
category_id: "Category ID"
category_title: "Category"
tag_name: "Tag name"
+ tag_title: "Tag"
external_url: "External or Relative URL"
user_id: "User ID"
+ user_title: "User"
username: "Username"
destination: "Destination"
copy_to_clipboard: "Copy Permalink to Clipboard"
delete_confirm: Are you sure you want to delete this permalink?
no_permalinks: "You don't have any permalinks yet. Create a new permalink above to begin seeing a list of your permalinks here."
+ add: "Add Permalink"
+ back: "Back to Permalinks"
+ more_options: "More options"
+ copy_success: "Permalink copied to clipboard"
form:
label: "New:"
- add: "Add"
+ add_header: "Add permalink"
+ edit_header: "Edit permalink"
filter: "Search (URL or External URL)"
+ url: "URL"
+ permalink_type: "Permalink type"
+ save: "Save"
reseed:
action:
diff --git a/config/routes.rb b/config/routes.rb
index 6c99110abb9..37c00c3ebba 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -298,13 +298,17 @@ Discourse::Application.routes.draw do
resource :email_style, only: %i[show update]
get "email_style/:field" => "email_styles#show", :constraints => { field: /html|css/ }
+
+ resources :permalinks, only: %i[index new create show destroy]
end
resources :embeddable_hosts, only: %i[create update destroy], constraints: AdminConstraint.new
resources :color_schemes,
only: %i[index create update destroy],
constraints: AdminConstraint.new
- resources :permalinks, only: %i[index create destroy], constraints: AdminConstraint.new
+ resources :permalinks,
+ only: %i[index create show update destroy],
+ constraints: AdminConstraint.new
scope "/customize" do
resources :watched_words, only: %i[index create destroy] do
diff --git a/spec/models/permalink_spec.rb b/spec/models/permalink_spec.rb
index 99f353a3896..7f68cd6078c 100644
--- a/spec/models/permalink_spec.rb
+++ b/spec/models/permalink_spec.rb
@@ -33,6 +33,61 @@ RSpec.describe Permalink do
expect(permalink.errors[:url]).to be_present
end
+ it "validates association" do
+ permalink = described_class.create(url: "/my/old/url", permalink_type: "topic")
+ expect(permalink.errors[:topic_id]).to be_present
+
+ permalink = described_class.create(url: "/my/old/url", permalink_type: "post")
+ expect(permalink.errors[:post_id]).to be_present
+
+ permalink = described_class.create(url: "/my/old/url", permalink_type: "category")
+ expect(permalink.errors[:category_id]).to be_present
+
+ permalink = described_class.create(url: "/my/old/url", permalink_type: "user")
+ expect(permalink.errors[:user_id]).to be_present
+
+ permalink = described_class.create(url: "/my/old/url", permalink_type: "external_url")
+ expect(permalink.errors[:external_url]).to be_present
+
+ permalink = described_class.create(url: "/my/old/url", permalink_type: "tag")
+ expect(permalink.errors[:tag_id]).to be_present
+ end
+
+ it "clears associations when permalink_type changes" do
+ permalink = described_class.create!(url: " my/old/url ")
+
+ permalink.update!(permalink_type_value: 1, permalink_type: "topic")
+ expect(permalink.topic_id).to eq(1)
+
+ permalink.update!(permalink_type_value: 1, permalink_type: "post")
+ expect(permalink.topic_id).to be_nil
+ expect(permalink.post_id).to eq(1)
+
+ permalink.update!(permalink_type_value: 1, permalink_type: "category")
+ expect(permalink.post_id).to be_nil
+ expect(permalink.category_id).to eq(1)
+
+ permalink.update!(permalink_type_value: 1, permalink_type: "user")
+ expect(permalink.category_id).to be_nil
+ expect(permalink.user_id).to eq(1)
+
+ permalink.update!(
+ permalink_type_value: "https://discourse.org",
+ permalink_type: "external_url",
+ )
+ expect(permalink.user_id).to be_nil
+ expect(permalink.external_url).to eq("https://discourse.org")
+
+ tag = Fabricate(:tag, name: "art")
+ permalink.update!(permalink_type_value: "art", permalink_type: "tag")
+ expect(permalink.external_url).to be_nil
+ expect(permalink.tag_id).to eq(tag.id)
+
+ permalink.update!(permalink_type_value: 1, permalink_type: "topic")
+ expect(permalink.tag_id).to be_nil
+ expect(permalink.topic_id).to eq(1)
+ end
+
context "with special characters in URL" do
it "percent encodes any special character" do
permalink = described_class.create!(url: "/2022/10/03/привет-sam")
diff --git a/spec/requests/admin/permalinks_controller_spec.rb b/spec/requests/admin/permalinks_controller_spec.rb
index e08ebac5ec4..d2f9ebaa819 100644
--- a/spec/requests/admin/permalinks_controller_spec.rb
+++ b/spec/requests/admin/permalinks_controller_spec.rb
@@ -80,9 +80,11 @@ RSpec.describe Admin::PermalinksController do
post "/admin/permalinks.json",
params: {
- url: "/topics/771",
- permalink_type: "topic_id",
- permalink_type_value: topic.id,
+ permalink: {
+ url: "/topics/771",
+ permalink_type: "topic",
+ permalink_type_value: topic.id,
+ },
}
expect(response.status).to eq(200)
@@ -102,9 +104,11 @@ RSpec.describe Admin::PermalinksController do
post "/admin/permalinks.json",
params: {
- url: "/topics/771/8291",
- permalink_type: "post_id",
- permalink_type_value: some_post.id,
+ permalink: {
+ url: "/topics/771/8291",
+ permalink_type: "post",
+ permalink_type_value: some_post.id,
+ },
}
expect(response.status).to eq(200)
@@ -124,9 +128,11 @@ RSpec.describe Admin::PermalinksController do
post "/admin/permalinks.json",
params: {
- url: "/forums/11",
- permalink_type: "category_id",
- permalink_type_value: category.id,
+ permalink: {
+ url: "/forums/11",
+ permalink_type: "category",
+ permalink_type_value: category.id,
+ },
}
expect(response.status).to eq(200)
@@ -146,9 +152,11 @@ RSpec.describe Admin::PermalinksController do
post "/admin/permalinks.json",
params: {
- url: "/forums/12",
- permalink_type: "tag_name",
- permalink_type_value: tag.name,
+ permalink: {
+ url: "/forums/12",
+ permalink_type: "tag",
+ permalink_type_value: tag.name,
+ },
}
expect(response.status).to eq(200)
@@ -168,9 +176,11 @@ RSpec.describe Admin::PermalinksController do
post "/admin/permalinks.json",
params: {
- url: "/people/42",
- permalink_type: "user_id",
- permalink_type_value: user.id,
+ permalink: {
+ url: "/people/42",
+ permalink_type: "user",
+ permalink_type_value: user.id,
+ },
}
expect(response.status).to eq(200)
@@ -193,9 +203,11 @@ RSpec.describe Admin::PermalinksController do
expect do
post "/admin/permalinks.json",
params: {
- url: "/topics/771",
- permalink_type: "topic_id",
- permalink_type_value: topic.id,
+ permalink: {
+ url: "/topics/771",
+ permalink_type: "topic",
+ permalink_type_value: topic.id,
+ },
}
end.not_to change { Permalink.count }
diff --git a/spec/requests/permalinks_controller_spec.rb b/spec/requests/permalinks_controller_spec.rb
index 2f421ee6574..6aa6bc22353 100644
--- a/spec/requests/permalinks_controller_spec.rb
+++ b/spec/requests/permalinks_controller_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe PermalinksController do
describe "show" do
it "should redirect to a permalink's target_url with status 301" do
- permalink.update!(topic_id: topic.id)
+ permalink.update!(permalink_type_value: topic.id, permalink_type: "topic")
get "/#{permalink.url}"
@@ -15,7 +15,7 @@ RSpec.describe PermalinksController do
end
it "should work for subfolder installs too" do
- permalink.update!(topic_id: topic.id)
+ permalink.update!(permalink_type_value: topic.id, permalink_type: "topic")
set_subfolder "/forum"
get "/#{permalink.url}"
@@ -25,7 +25,7 @@ RSpec.describe PermalinksController do
end
it "should apply normalizations" do
- permalink.update!(external_url: "/topic/100")
+ permalink.update!(permalink_type_value: "/topic/100", permalink_type: "external_url")
SiteSetting.permalink_normalizations = "/(.*)\\?.*/\\1"
get "/#{permalink.url}", params: { test: "hello" }
@@ -46,7 +46,12 @@ RSpec.describe PermalinksController do
end
context "when permalink's target_url is an external URL" do
- before { permalink.update!(external_url: "https://github.com/discourse/discourse") }
+ before do
+ permalink.update!(
+ permalink_type_value: "https://github.com/discourse/discourse",
+ permalink_type: "external_url",
+ )
+ end
it "redirects to it properly" do
get "/#{permalink.url}"
diff --git a/spec/system/admin_permalinks_page_spec.rb b/spec/system/admin_permalinks_page_spec.rb
new file mode 100644
index 00000000000..7c68c8faf6f
--- /dev/null
+++ b/spec/system/admin_permalinks_page_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+describe "Admin Permalinks Page", type: :system do
+ fab!(:admin)
+ fab!(:post)
+
+ let(:admin_permalinks_page) { PageObjects::Pages::AdminPermalinks.new }
+ let(:admin_permalink_form_page) { PageObjects::Pages::AdminPermalinkForm.new }
+
+ before { sign_in(admin) }
+
+ it "allows admin to created edit and destroy permalink" do
+ admin_permalinks_page.visit
+ admin_permalinks_page.click_add_permalink
+ admin_permalink_form_page
+ .fill_in_url("test")
+ .select_permalink_type("category")
+ .fill_in_category("1")
+ .click_save
+ expect(admin_permalinks_page).to have_permalinks("test")
+
+ admin_permalinks_page.click_edit_permalink("test")
+ admin_permalink_form_page.fill_in_url("test2").click_save
+ expect(admin_permalinks_page).to have_permalinks("test2")
+
+ admin_permalinks_page.click_delete_permalink("test2")
+
+ expect(admin_permalinks_page).to have_no_permalinks
+ end
+end
diff --git a/spec/system/page_objects/pages/admin_permalink_form.rb b/spec/system/page_objects/pages/admin_permalink_form.rb
new file mode 100644
index 00000000000..cbf7bfc7141
--- /dev/null
+++ b/spec/system/page_objects/pages/admin_permalink_form.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+module PageObjects
+ module Pages
+ class AdminPermalinkForm < PageObjects::Pages::Base
+ def fill_in_url(url)
+ form.field("url").fill_in(url)
+ self
+ end
+
+ def fill_in_description(description)
+ form.field("description").fill_in(description)
+ self
+ end
+
+ def select_permalink_type(type)
+ form.field("permalinkType").select(type)
+ self
+ end
+
+ def fill_in_category(category)
+ form.field("categoryId").fill_in(category)
+ self
+ end
+
+ def click_save
+ form.submit
+ expect(page).to have_css(
+ ".admin-permalink-item__url",
+ wait: Capybara.default_max_wait_time * 3,
+ )
+ end
+
+ def form
+ @form ||= PageObjects::Components::FormKit.new(".admin-permalink-form .form-kit")
+ end
+ end
+ end
+end
diff --git a/spec/system/page_objects/pages/admin_permalinks.rb b/spec/system/page_objects/pages/admin_permalinks.rb
new file mode 100644
index 00000000000..ad60a1175bd
--- /dev/null
+++ b/spec/system/page_objects/pages/admin_permalinks.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+module PageObjects
+ module Pages
+ class AdminPermalinks < PageObjects::Pages::Base
+ def visit
+ page.visit("/admin/customize/permalinks")
+ self
+ end
+
+ def toggle(key)
+ PageObjects::Components::DToggleSwitch.new(".admin-flag-item__toggle.#{key}").toggle
+ has_saved_flag?(key)
+ self
+ end
+
+ def click_add_permalink
+ find(".admin-permalinks__header-add-permalink").click
+ self
+ end
+
+ def click_edit_permalink(url)
+ find("tr.#{url} .admin-permalink-item__edit").click
+ self
+ end
+
+ def click_delete_permalink(url)
+ open_permalink_menu(url)
+ find(".admin-permalink-item__delete").click
+ find(".dialog-footer .btn-primary").click
+ expect(page).to have_no_css(".dialog-body")
+ has_closed_permalink_menu?
+ self
+ end
+
+ def has_permalinks?(*permalinks)
+ all(".admin-permalink-item__url").map(&:text) == permalinks
+ end
+
+ def has_no_permalinks?
+ has_no_css?(".admin-permalink-item__url")
+ end
+
+ def open_permalink_menu(url)
+ find("tr.#{url} .permalink-menu-trigger").click
+ self
+ end
+
+ def has_closed_permalink_menu?
+ has_no_css?(".permalink-menu-content")
+ end
+ end
+ end
+end