From 49cc0eb3e7d1521079e33a60216df46679082547 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 5 Oct 2017 23:42:05 +0200 Subject: [PATCH] Improve admin UI for custom emojis, add copy/disable/enable (#5231) --- .../admin/custom_emojis_controller.rb | 42 +++- .../api/v1/custom_emojis_controller.rb | 2 +- app/models/account_filter.rb | 2 + app/models/custom_emoji.rb | 11 +- app/models/custom_emoji_filter.rb | 34 ++++ .../custom_emojis/_custom_emoji.html.haml | 13 ++ app/views/admin/custom_emojis/index.html.haml | 20 ++ config/brakeman.ignore | 182 +++++++++++++++++- config/locales/de.yml | 6 +- config/locales/en.yml | 7 + config/routes.rb | 8 +- ...005171936_add_disabled_to_custom_emojis.rb | 15 ++ db/schema.rb | 3 +- 13 files changed, 330 insertions(+), 15 deletions(-) create mode 100644 app/models/custom_emoji_filter.rb create mode 100644 db/migrate/20171005171936_add_disabled_to_custom_emojis.rb diff --git a/app/controllers/admin/custom_emojis_controller.rb b/app/controllers/admin/custom_emojis_controller.rb index d70514d9a97..dba9f101236 100644 --- a/app/controllers/admin/custom_emojis_controller.rb +++ b/app/controllers/admin/custom_emojis_controller.rb @@ -2,8 +2,10 @@ module Admin class CustomEmojisController < BaseController + before_action :set_custom_emoji, except: [:index, :new, :create] + def index - @custom_emojis = CustomEmoji.local + @custom_emojis = filtered_custom_emojis.page(params[:page]) end def new @@ -21,14 +23,50 @@ module Admin end def destroy - CustomEmoji.find(params[:id]).destroy + @custom_emoji.destroy redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.destroyed_msg') end + def copy + emoji = @custom_emoji.dup + emoji.domain = nil + + if emoji.save + redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.copied_msg') + else + redirect_to admin_custom_emojis_path, alert: I18n.t('admin.custom_emojis.copy_failed_msg') + end + end + + def enable + @custom_emoji.update!(disabled: false) + redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.enabled_msg') + end + + def disable + @custom_emoji.update!(disabled: true) + redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.disabled_msg') + end + private + def set_custom_emoji + @custom_emoji = CustomEmoji.find(params[:id]) + end + def resource_params params.require(:custom_emoji).permit(:shortcode, :image) end + + def filtered_custom_emojis + CustomEmojiFilter.new(filter_params).results + end + + def filter_params + params.permit( + :local, + :remote + ) + end end end diff --git a/app/controllers/api/v1/custom_emojis_controller.rb b/app/controllers/api/v1/custom_emojis_controller.rb index 4dd77fb5503..f8cd64455a0 100644 --- a/app/controllers/api/v1/custom_emojis_controller.rb +++ b/app/controllers/api/v1/custom_emojis_controller.rb @@ -4,6 +4,6 @@ class Api::V1::CustomEmojisController < Api::BaseController respond_to :json def index - render json: CustomEmoji.local, each_serializer: REST::CustomEmojiSerializer + render json: CustomEmoji.local.where(disabled: false), each_serializer: REST::CustomEmojiSerializer end end diff --git a/app/models/account_filter.rb b/app/models/account_filter.rb index 1a8cc5192ac..18987236820 100644 --- a/app/models/account_filter.rb +++ b/app/models/account_filter.rb @@ -9,9 +9,11 @@ class AccountFilter def results scope = Account.alphabetic + params.each do |key, value| scope.merge!(scope_for(key, value)) if value.present? end + scope end diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb index 9e9be5e12c2..258b50c82f9 100644 --- a/app/models/custom_emoji.rb +++ b/app/models/custom_emoji.rb @@ -12,6 +12,7 @@ # image_updated_at :datetime # created_at :datetime not null # updated_at :datetime not null +# disabled :boolean default(FALSE), not null # class CustomEmoji < ApplicationRecord @@ -26,10 +27,16 @@ class CustomEmoji < ApplicationRecord validates_attachment :image, content_type: { content_type: 'image/png' }, presence: true, size: { in: 0..50.kilobytes } validates :shortcode, uniqueness: { scope: :domain }, format: { with: /\A#{SHORTCODE_RE_FRAGMENT}\z/ }, length: { minimum: 2 } - scope :local, -> { where(domain: nil) } + scope :local, -> { where(domain: nil) } + scope :remote, -> { where.not(domain: nil) } + scope :alphabetic, -> { order(domain: :asc, shortcode: :asc) } include Remotable + def local? + domain.nil? + end + class << self def from_text(text, domain) return [] if text.blank? @@ -38,7 +45,7 @@ class CustomEmoji < ApplicationRecord return [] if shortcodes.empty? - where(shortcode: shortcodes, domain: domain) + where(shortcode: shortcodes, domain: domain, disabled: false) end end end diff --git a/app/models/custom_emoji_filter.rb b/app/models/custom_emoji_filter.rb new file mode 100644 index 00000000000..2d1394a597b --- /dev/null +++ b/app/models/custom_emoji_filter.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +class CustomEmojiFilter + attr_reader :params + + def initialize(params) + @params = params + end + + def results + scope = CustomEmoji.alphabetic + + params.each do |key, value| + scope.merge!(scope_for(key, value)) if value.present? + end + + scope + end + + private + + def scope_for(key, value) + case key.to_s + when 'local' + CustomEmoji.local + when 'remote' + CustomEmoji.remote + when 'by_domain' + CustomEmoji.where(domain: value) + else + raise "Unknown filter: #{key}" + end + end +end diff --git a/app/views/admin/custom_emojis/_custom_emoji.html.haml b/app/views/admin/custom_emojis/_custom_emoji.html.haml index ff1aa9925b4..53263c43f23 100644 --- a/app/views/admin/custom_emojis/_custom_emoji.html.haml +++ b/app/views/admin/custom_emojis/_custom_emoji.html.haml @@ -3,5 +3,18 @@ = image_tag custom_emoji.image.url, class: 'emojione', alt: ":#{custom_emoji.shortcode}:" %td %samp= ":#{custom_emoji.shortcode}:" + %td + - if custom_emoji.local? + = t('admin.accounts.location.local') + - else + = custom_emoji.domain + %td + - unless custom_emoji.local? + = table_link_to 'copy', t('admin.custom_emojis.copy'), copy_admin_custom_emoji_path(custom_emoji), method: :post + %td + - if custom_emoji.disabled? + = table_link_to 'power-off', t('admin.custom_emojis.enable'), enable_admin_custom_emoji_path(custom_emoji), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } + - else + = table_link_to 'power-off', t('admin.custom_emojis.disable'), disable_admin_custom_emoji_path(custom_emoji), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } %td = table_link_to 'times', t('admin.custom_emojis.delete'), admin_custom_emoji_path(custom_emoji), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') } diff --git a/app/views/admin/custom_emojis/index.html.haml b/app/views/admin/custom_emojis/index.html.haml index d5f32e84b4a..20ffb852943 100644 --- a/app/views/admin/custom_emojis/index.html.haml +++ b/app/views/admin/custom_emojis/index.html.haml @@ -1,14 +1,34 @@ - content_for :page_title do = t('admin.custom_emojis.title') +.filters + .filter-subset + %strong= t('admin.accounts.location.title') + %ul + %li= filter_link_to t('admin.accounts.location.all'), local: nil, remote: nil + %li + - if selected? local: '1', remote: nil + = filter_link_to t('admin.accounts.location.local'), {local: nil, remote: nil}, {local: '1', remote: nil} + - else + = filter_link_to t('admin.accounts.location.local'), local: '1', remote: nil + %li + - if selected? remote: '1', local: nil + = filter_link_to t('admin.accounts.location.remote'), {remote: nil, local: nil}, {remote: '1', local: nil} + - else + = filter_link_to t('admin.accounts.location.remote'), remote: '1', local: nil + .table-wrapper %table.table %thead %tr %th= t('admin.custom_emojis.emoji') %th= t('admin.custom_emojis.shortcode') + %th= t('admin.accounts.domain') + %th + %th %th %tbody = render @custom_emojis += paginate @custom_emojis = link_to t('admin.custom_emojis.upload'), new_admin_custom_emoji_path, class: 'button' diff --git a/config/brakeman.ignore b/config/brakeman.ignore index dbb59dd0796..ed6e121d21b 100644 --- a/config/brakeman.ignore +++ b/config/brakeman.ignore @@ -1,5 +1,81 @@ { "ignored_warnings": [ + { + "warning_type": "Cross-Site Scripting", + "warning_code": 4, + "fingerprint": "0adbe361b91afff22ba51e5fc2275ec703cc13255a0cb3eecd8dab223ab9f61e", + "check_name": "LinkToHref", + "message": "Potentially unsafe model attribute in link_to href", + "file": "app/views/admin/accounts/show.html.haml", + "line": 122, + "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", + "code": "link_to(Account.find(params[:id]).inbox_url, Account.find(params[:id]).inbox_url)", + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}], + "location": { + "type": "template", + "template": "admin/accounts/show" + }, + "user_input": "Account.find(params[:id]).inbox_url", + "confidence": "Weak", + "note": "" + }, + { + "warning_type": "Cross-Site Scripting", + "warning_code": 4, + "fingerprint": "1fc29c578d0c89bf13bd5476829d272d54cd06b92ccf6df18568fa1f2674926e", + "check_name": "LinkToHref", + "message": "Potentially unsafe model attribute in link_to href", + "file": "app/views/admin/accounts/show.html.haml", + "line": 128, + "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", + "code": "link_to(Account.find(params[:id]).shared_inbox_url, Account.find(params[:id]).shared_inbox_url)", + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}], + "location": { + "type": "template", + "template": "admin/accounts/show" + }, + "user_input": "Account.find(params[:id]).shared_inbox_url", + "confidence": "Weak", + "note": "" + }, + { + "warning_type": "Cross-Site Scripting", + "warning_code": 4, + "fingerprint": "2129d4c1e63a351d28d8d2937ff0b50237809c3df6725c0c5ef82b881dbb2086", + "check_name": "LinkToHref", + "message": "Potentially unsafe model attribute in link_to href", + "file": "app/views/admin/accounts/show.html.haml", + "line": 35, + "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", + "code": "link_to(Account.find(params[:id]).url, Account.find(params[:id]).url)", + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}], + "location": { + "type": "template", + "template": "admin/accounts/show" + }, + "user_input": "Account.find(params[:id]).url", + "confidence": "Weak", + "note": "" + }, + { + "warning_type": "Dynamic Render Path", + "warning_code": 15, + "fingerprint": "3b0a20b08aef13cf8cf865384fae0cfd3324d8200a83262bf4abbc8091b5fec5", + "check_name": "Render", + "message": "Render path contains parameter value", + "file": "app/views/admin/custom_emojis/index.html.haml", + "line": 31, + "link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/", + "code": "render(action => filtered_custom_emojis.page(params[:page]), {})", + "render_path": [{"type":"controller","class":"Admin::CustomEmojisController","method":"index","line":9,"file":"app/controllers/admin/custom_emojis_controller.rb"}], + "location": { + "type": "template", + "template": "admin/custom_emojis/index" + }, + "user_input": "params[:page]", + "confidence": "Weak", + "note": "" + }, { "warning_type": "Dynamic Render Path", "warning_code": 15, @@ -19,6 +95,44 @@ "confidence": "Weak", "note": "" }, + { + "warning_type": "Cross-Site Scripting", + "warning_code": 4, + "fingerprint": "64b5b2a02ede9c2b3598881eb5a466d63f7d27fe0946aa00d570111ec7338d2e", + "check_name": "LinkToHref", + "message": "Potentially unsafe model attribute in link_to href", + "file": "app/views/admin/accounts/show.html.haml", + "line": 131, + "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", + "code": "link_to(Account.find(params[:id]).followers_url, Account.find(params[:id]).followers_url)", + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}], + "location": { + "type": "template", + "template": "admin/accounts/show" + }, + "user_input": "Account.find(params[:id]).followers_url", + "confidence": "Weak", + "note": "" + }, + { + "warning_type": "Cross-Site Scripting", + "warning_code": 4, + "fingerprint": "82f7b0d09beb3ab68e0fa16be63cedf4e820f2490326e9a1cec05761d92446cd", + "check_name": "LinkToHref", + "message": "Potentially unsafe model attribute in link_to href", + "file": "app/views/admin/accounts/show.html.haml", + "line": 106, + "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", + "code": "link_to(Account.find(params[:id]).salmon_url, Account.find(params[:id]).salmon_url)", + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}], + "location": { + "type": "template", + "template": "admin/accounts/show" + }, + "user_input": "Account.find(params[:id]).salmon_url", + "confidence": "Weak", + "note": "" + }, { "warning_type": "Dynamic Render Path", "warning_code": 15, @@ -26,7 +140,7 @@ "check_name": "Render", "message": "Render path contains parameter value", "file": "app/views/admin/accounts/index.html.haml", - "line": 63, + "line": 64, "link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/", "code": "render(action => filtered_accounts.page(params[:page]), {})", "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"index","line":10,"file":"app/controllers/admin/accounts_controller.rb"}], @@ -38,6 +152,25 @@ "confidence": "Weak", "note": "" }, + { + "warning_type": "Cross-Site Scripting", + "warning_code": 4, + "fingerprint": "bb0ad5c4a42e06e3846c2089ff5269c17f65483a69414f6ce65eecf2bb11fab7", + "check_name": "LinkToHref", + "message": "Potentially unsafe model attribute in link_to href", + "file": "app/views/admin/accounts/show.html.haml", + "line": 95, + "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", + "code": "link_to(Account.find(params[:id]).remote_url, Account.find(params[:id]).remote_url)", + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}], + "location": { + "type": "template", + "template": "admin/accounts/show" + }, + "user_input": "Account.find(params[:id]).remote_url", + "confidence": "Weak", + "note": "" + }, { "warning_type": "Redirect", "warning_code": 18, @@ -65,7 +198,7 @@ "check_name": "Render", "message": "Render path contains parameter value", "file": "app/views/admin/reports/index.html.haml", - "line": 24, + "line": 25, "link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/", "code": "render(action => filtered_reports.page(params[:page]), {})", "render_path": [{"type":"controller","class":"Admin::ReportsController","method":"index","line":9,"file":"app/controllers/admin/reports_controller.rb"}], @@ -77,6 +210,45 @@ "confidence": "Weak", "note": "" }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "cd440d9d0bcb76225f4142030cec0bdec6ad119c537c108c9d514bf87bc34d29", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "lib/mastodon/timestamp_ids.rb", + "line": 69, + "link": "http://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "ActiveRecord::Base.connection.execute(\" CREATE OR REPLACE FUNCTION timestamp_id(table_name text)\\n RETURNS bigint AS\\n $$\\n DECLARE\\n time_part bigint;\\n sequence_base bigint;\\n tail bigint;\\n BEGIN\\n -- Our ID will be composed of the following:\\n -- 6 bytes (48 bits) of millisecond-level timestamp\\n -- 2 bytes (16 bits) of sequence data\\n\\n -- The 'sequence data' is intended to be unique within a\\n -- given millisecond, yet obscure the 'serial number' of\\n -- this row.\\n\\n -- To do this, we hash the following data:\\n -- * Table name (if provided, skipped if not)\\n -- * Secret salt (should not be guessable)\\n -- * Timestamp (again, millisecond-level granularity)\\n\\n -- We then take the first two bytes of that value, and add\\n -- the lowest two bytes of the table ID sequence number\\n -- (`table_name`_id_seq). This means that even if we insert\\n -- two rows at the same millisecond, they will have\\n -- distinct 'sequence data' portions.\\n\\n -- If this happens, and an attacker can see both such IDs,\\n -- they can determine which of the two entries was inserted\\n -- first, but not the total number of entries in the table\\n -- (even mod 2**16).\\n\\n -- The table name is included in the hash to ensure that\\n -- different tables derive separate sequence bases so rows\\n -- inserted in the same millisecond in different tables do\\n -- not reveal the table ID sequence number for one another.\\n\\n -- The secret salt is included in the hash to ensure that\\n -- external users cannot derive the sequence base given the\\n -- timestamp and table name, which would allow them to\\n -- compute the table ID sequence number.\\n\\n time_part := (\\n -- Get the time in milliseconds\\n ((date_part('epoch', now()) * 1000))::bigint\\n -- And shift it over two bytes\\n << 16);\\n\\n sequence_base := (\\n 'x' ||\\n -- Take the first two bytes (four hex characters)\\n substr(\\n -- Of the MD5 hash of the data we documented\\n md5(table_name ||\\n '#{SecureRandom.hex(16)}' ||\\n time_part::text\\n ),\\n 1, 4\\n )\\n -- And turn it into a bigint\\n )::bit(16)::bigint;\\n\\n -- Finally, add our sequence number to our base, and chop\\n -- it to the last two bytes\\n tail := (\\n (sequence_base + nextval(table_name || '_id_seq'))\\n & 65535);\\n\\n -- Return the time part and the sequence part. OR appears\\n -- faster here than addition, but they're equivalent:\\n -- time_part has no trailing two bytes, and tail is only\\n -- the last two bytes.\\n RETURN time_part | tail;\\n END\\n $$ LANGUAGE plpgsql VOLATILE;\\n\")", + "render_path": null, + "location": { + "type": "method", + "class": "Mastodon::TimestampIds", + "method": "s(:self).define_timestamp_id" + }, + "user_input": "SecureRandom.hex(16)", + "confidence": "Medium", + "note": "" + }, + { + "warning_type": "Cross-Site Scripting", + "warning_code": 4, + "fingerprint": "e04aafe1e06cf8317fb6ac0a7f35783e45aa1274272ee6eaf28d39adfdad489b", + "check_name": "LinkToHref", + "message": "Potentially unsafe model attribute in link_to href", + "file": "app/views/admin/accounts/show.html.haml", + "line": 125, + "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", + "code": "link_to(Account.find(params[:id]).outbox_url, Account.find(params[:id]).outbox_url)", + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}], + "location": { + "type": "template", + "template": "admin/accounts/show" + }, + "user_input": "Account.find(params[:id]).outbox_url", + "confidence": "Weak", + "note": "" + }, { "warning_type": "Dynamic Render Path", "warning_code": 15, @@ -84,7 +256,7 @@ "check_name": "Render", "message": "Render path contains parameter value", "file": "app/views/stream_entries/show.html.haml", - "line": 23, + "line": 21, "link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/", "code": "render(partial => \"stream_entries/#{Account.find_local!(params[:account_username]).statuses.find(params[:id]).stream_entry.activity_type.downcase}\", { :locals => ({ Account.find_local!(params[:account_username]).statuses.find(params[:id]).stream_entry.activity_type.downcase.to_sym => Account.find_local!(params[:account_username]).statuses.find(params[:id]).stream_entry.activity, :include_threads => true }) })", "render_path": [{"type":"controller","class":"StatusesController","method":"show","line":20,"file":"app/controllers/statuses_controller.rb"}], @@ -97,6 +269,6 @@ "note": "" } ], - "updated": "2017-08-30 05:14:04 +0200", - "brakeman_version": "3.7.2" + "updated": "2017-10-05 20:06:40 +0200", + "brakeman_version": "4.0.1" } diff --git a/config/locales/de.yml b/config/locales/de.yml index ec48bd5ffa5..7c0edff94eb 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -137,7 +137,7 @@ de: reject_media: Mediendateien ablehnen reject_media_hint: Entfernt lokal gespeicherte Mediendateien und verhindert deren künftiges Herunterladen. Für Sperren irrelevant severities: - none: Kein + noop: Kein silence: Stummschaltung suspend: Sperren severity: Schweregrad @@ -180,7 +180,7 @@ de: nsfw: 'false': Medienanhänge wieder anzeigen 'true': Medienanhänge verbergen - report: "Meldung #%{id}" + report: 'Meldung #%{id}' report_contents: Inhalt reported_account: Gemeldetes Konto reported_by: Gemeldet von @@ -386,7 +386,7 @@ de: body: "%{name} hat dich erwähnt:" subject: "%{name} hat dich erwähnt" reblog: - body: '%{name} hat deinen Beitrag geteilt:' + body: "%{name} hat deinen Beitrag geteilt:" subject: "%{name} hat deinen Beitrag geteilt" number: human: diff --git a/config/locales/en.yml b/config/locales/en.yml index 5d9557535c1..2059c5e2bef 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -109,10 +109,17 @@ en: username: Username web: Web custom_emojis: + copied_msg: Successfully created local copy of the emoji + copy: Copy + copy_failed_msg: Could not make a local copy of that emoji created_msg: Emoji successfully created! delete: Delete destroyed_msg: Emojo successfully destroyed! + disable: Disable + disabled_msg: Successfully disabled that emoji emoji: Emoji + enable: Enable + enabled_msg: Successfully enabled that emoji image_hint: PNG up to 50KB new: title: Add new custom emoji diff --git a/config/routes.rb b/config/routes.rb index 959afc23f86..cc1f66e52af 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -139,7 +139,13 @@ Rails.application.routes.draw do resource :two_factor_authentication, only: [:destroy] end - resources :custom_emojis, only: [:index, :new, :create, :destroy] + resources :custom_emojis, only: [:index, :new, :create, :destroy] do + member do + post :copy + post :enable + post :disable + end + end end get '/admin', to: redirect('/admin/settings/edit', status: 302) diff --git a/db/migrate/20171005171936_add_disabled_to_custom_emojis.rb b/db/migrate/20171005171936_add_disabled_to_custom_emojis.rb new file mode 100644 index 00000000000..067a7bee0cc --- /dev/null +++ b/db/migrate/20171005171936_add_disabled_to_custom_emojis.rb @@ -0,0 +1,15 @@ +require Rails.root.join('lib', 'mastodon', 'migration_helpers') + +class AddDisabledToCustomEmojis < ActiveRecord::Migration[5.1] + include Mastodon::MigrationHelpers + + disable_ddl_transaction! + + def up + safety_assured { add_column_with_default :custom_emojis, :disabled, :bool, default: false } + end + + def down + remove_column :custom_emojis, :disabled + end +end diff --git a/db/schema.rb b/db/schema.rb index 337678c67fc..3358e299790 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170928082043) do +ActiveRecord::Schema.define(version: 20171005171936) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -98,6 +98,7 @@ ActiveRecord::Schema.define(version: 20170928082043) do t.datetime "image_updated_at" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.boolean "disabled", default: false, null: false t.index ["shortcode", "domain"], name: "index_custom_emojis_on_shortcode_and_domain", unique: true end