From 9239e4ce4d4e958e62552d4a01183d0295c020f5 Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Thu, 14 Sep 2017 00:04:30 +0200
Subject: Uploads for admin site settings (#4913)
* Improve OpenGraph tags for about pages
* Add thumbnail admin setting
* Fix error
* Fix up
---
db/migrate/20170913000752_create_site_uploads.rb | 10 ++++++++++
1 file changed, 10 insertions(+)
create mode 100644 db/migrate/20170913000752_create_site_uploads.rb
(limited to 'db/migrate')
diff --git a/db/migrate/20170913000752_create_site_uploads.rb b/db/migrate/20170913000752_create_site_uploads.rb
new file mode 100644
index 000000000..2246e48cd
--- /dev/null
+++ b/db/migrate/20170913000752_create_site_uploads.rb
@@ -0,0 +1,10 @@
+class CreateSiteUploads < ActiveRecord::Migration[5.1]
+ def change
+ create_table :site_uploads do |t|
+ t.string :var, default: '', null: false, index: { unique: true }
+ t.attachment :file
+ t.json :meta
+ t.timestamps
+ end
+ end
+end
--
cgit
From 81cec35dbf1b348d23363559e3f4e6b1ec3415c5 Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Tue, 19 Sep 2017 02:42:40 +0200
Subject: Custom emoji (#4988)
* Custom emoji
- In OStatus: ``
- In ActivityPub: `{ type: "Emoji", name: ":coolcat:", href: "http://..." }`
- In REST API: Status object includes `emojis` array (`shortcode`, `url`)
- Domain blocks with reject media stop emojis
- Emoji file up to 50KB
- Web UI handles custom emojis
- Static pages render custom emojis as `` tags
Side effects:
- Undo #4500 optimization, as I needed to modify it to restore
shortcode handling in emojify()
- Formatter#plaintext should now make sure stripped out line-breaks
and paragraphs are replaced with newlines
* Fix emoji at the start not being converted
---
app/javascript/mastodon/emoji.js | 60 ++++++++++------
app/javascript/mastodon/reducers/statuses.js | 9 ++-
app/lib/activitypub/activity/create.rb | 13 ++++
app/lib/formatter.rb | 54 +++++++++++++-
app/lib/ostatus/activity/creation.rb | 20 ++++++
app/lib/ostatus/atom_serializer.rb | 4 ++
app/models/custom_emoji.rb | 38 ++++++++++
app/models/status.rb | 4 ++
app/serializers/activitypub/note_serializer.rb | 20 +++++-
app/serializers/rest/status_serializer.rb | 11 +++
.../stream_entries/_detailed_status.html.haml | 2 +-
app/views/stream_entries/_simple_status.html.haml | 2 +-
db/migrate/20170917153509_create_custom_emojis.rb | 13 ++++
db/schema.rb | 14 +++-
spec/fabricators/custom_emoji_fabricator.rb | 5 ++
spec/fixtures/files/emojo.png | Bin 0 -> 29814 bytes
spec/lib/activitypub/activity/create_spec.rb | 25 +++++++
spec/lib/formatter_spec.rb | 78 +++++++++++++++++++++
spec/lib/ostatus/atom_serializer_spec.rb | 16 ++++-
spec/models/custom_emoji_spec.rb | 25 +++++++
20 files changed, 382 insertions(+), 31 deletions(-)
create mode 100644 app/models/custom_emoji.rb
create mode 100644 db/migrate/20170917153509_create_custom_emojis.rb
create mode 100644 spec/fabricators/custom_emoji_fabricator.rb
create mode 100644 spec/fixtures/files/emojo.png
create mode 100644 spec/models/custom_emoji_spec.rb
(limited to 'db/migrate')
diff --git a/app/javascript/mastodon/emoji.js b/app/javascript/mastodon/emoji.js
index a41dfdd1d..865b85b61 100644
--- a/app/javascript/mastodon/emoji.js
+++ b/app/javascript/mastodon/emoji.js
@@ -3,28 +3,48 @@ import Trie from 'substring-trie';
const trie = new Trie(Object.keys(unicodeMapping));
-const emojify = str => {
- let rtn = '';
- for (;;) {
- let match, i = 0;
- while (i < str.length && str[i] !== '<' && !(match = trie.search(str.slice(i)))) {
- i += str.codePointAt(i) < 65536 ? 1 : 2;
- }
- if (i === str.length)
- break;
- else if (str[i] === '<') {
- let tagend = str.indexOf('>', i + 1) + 1;
- if (!tagend)
- break;
- rtn += str.slice(0, tagend);
- str = str.slice(tagend);
- } else {
- const [filename, shortCode] = unicodeMapping[match];
- rtn += str.slice(0, i) + ``;
- str = str.slice(i + match.length);
+const emojify = (str, customEmojis = {}) => {
+ // This walks through the string from start to end, ignoring any tags (,
, etc.)
+ // and replacing valid unicode strings
+ // that _aren't_ within tags with an version.
+ // The goal is to be the same as an emojione.regUnicode replacement, but faster.
+ let i = -1;
+ let insideTag = false;
+ let insideShortname = false;
+ let shortnameStartIndex = -1;
+ let match;
+ while (++i < str.length) {
+ const char = str.charAt(i);
+ if (insideShortname && char === ':') {
+ const shortname = str.substring(shortnameStartIndex, i + 1);
+ if (shortname in customEmojis) {
+ const replacement = ``;
+ str = str.substring(0, shortnameStartIndex) + replacement + str.substring(i + 1);
+ i += (replacement.length - shortname.length - 1); // jump ahead the length we've added to the string
+ } else {
+ i--;
+ }
+ insideShortname = false;
+ } else if (insideTag && char === '>') {
+ insideTag = false;
+ } else if (char === '<') {
+ insideTag = true;
+ insideShortname = false;
+ } else if (!insideTag && char === ':') {
+ insideShortname = true;
+ shortnameStartIndex = i;
+ } else if (!insideTag && (match = trie.search(str.substring(i)))) {
+ const unicodeStr = match;
+ if (unicodeStr in unicodeMapping) {
+ const [filename, shortCode] = unicodeMapping[unicodeStr];
+ const alt = unicodeStr;
+ const replacement = ``;
+ str = str.substring(0, i) + replacement + str.substring(i + unicodeStr.length);
+ i += (replacement.length - unicodeStr.length); // jump ahead the length we've added to the string
+ }
}
}
- return rtn + str;
+ return str;
};
export default emojify;
diff --git a/app/javascript/mastodon/reducers/statuses.js b/app/javascript/mastodon/reducers/statuses.js
index 7f906bef6..38b23504e 100644
--- a/app/javascript/mastodon/reducers/statuses.js
+++ b/app/javascript/mastodon/reducers/statuses.js
@@ -58,9 +58,14 @@ const normalizeStatus = (state, status) => {
}
const searchContent = [status.spoiler_text, status.content].join(' ').replace(/
/g, '\n').replace(/<\/p>
/g, '\n\n');
+ const emojiMap = normalStatus.emojis.reduce((obj, emoji) => {
+ obj[`:${emoji.shortcode}:`] = emoji.url;
+ return obj;
+ }, {});
+
normalStatus.search_index = domParser.parseFromString(searchContent, 'text/html').documentElement.textContent;
- normalStatus.contentHtml = emojify(normalStatus.content);
- normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(normalStatus.spoiler_text || ''));
+ normalStatus.contentHtml = emojify(normalStatus.content, emojiMap);
+ normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(normalStatus.spoiler_text || ''), emojiMap);
return state.update(status.id, ImmutableMap(), map => map.mergeDeep(fromJS(normalStatus)));
};
diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb
index 894759d9a..41f2b0bad 100644
--- a/app/lib/activitypub/activity/create.rb
+++ b/app/lib/activitypub/activity/create.rb
@@ -61,6 +61,8 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
process_hashtag tag, status
when 'Mention'
process_mention tag, status
+ when 'Emoji'
+ process_emoji tag, status
end
end
end
@@ -79,6 +81,17 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
account.mentions.create(status: status)
end
+ def process_emoji(tag, _status)
+ shortcode = tag['name'].delete(':')
+ emoji = CustomEmoji.find_by(shortcode: shortcode, domain: @account.domain)
+
+ return if !emoji.nil? || skip_download?
+
+ emoji = CustomEmoji.new(domain: @account.domain, shortcode: shortcode)
+ emoji.image_remote_url = tag['href']
+ emoji.save
+ end
+
def process_attachments(status)
return unless @object['attachment'].is_a?(Array)
diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb
index 575830190..29fea27de 100644
--- a/app/lib/formatter.rb
+++ b/app/lib/formatter.rb
@@ -9,7 +9,7 @@ class Formatter
include ActionView::Helpers::TextHelper
- def format(status)
+ def format(status, options = {})
if status.reblog?
prepend_reblog = status.reblog.account.acct
status = status.proper
@@ -19,7 +19,11 @@ class Formatter
raw_content = status.text
- return reformat(raw_content) unless status.local?
+ unless status.local?
+ html = reformat(raw_content)
+ html = encode_custom_emojis(html, status.emojis) if options[:custom_emojify]
+ return html
+ end
linkable_accounts = status.mentions.map(&:account)
linkable_accounts << status.account
@@ -27,6 +31,7 @@ class Formatter
html = raw_content
html = "RT @#{prepend_reblog} #{html}" if prepend_reblog
html = encode_and_link_urls(html, linkable_accounts)
+ html = encode_custom_emojis(html, status.emojis) if options[:custom_emojify]
html = simple_format(html, {}, sanitize: false)
html = html.delete("\n")
@@ -39,7 +44,9 @@ class Formatter
def plaintext(status)
return status.text if status.local?
- strip_tags(status.text)
+
+ text = status.text.gsub(/(
|
|<\/p>)+/) { |match| "#{match}\n" }
+ strip_tags(text)
end
def simplified_format(account)
@@ -76,6 +83,47 @@ class Formatter
end
end
+ def encode_custom_emojis(html, emojis)
+ return html if emojis.empty?
+
+ emoji_map = emojis.map { |e| [e.shortcode, full_asset_url(e.image.url)] }.to_h
+
+ i = -1
+ inside_tag = false
+ inside_shortname = false
+ shortname_start_index = -1
+
+ while i + 1 < html.size
+ i += 1
+
+ if inside_shortname && html[i] == ':'
+ shortcode = html[shortname_start_index + 1..i - 1]
+ emoji = emoji_map[shortcode]
+
+ if emoji
+ replacement = ""
+ before_html = shortname_start_index.positive? ? html[0..shortname_start_index - 1] : ''
+ html = before_html + replacement + html[i + 1..-1]
+ i += replacement.size - (shortcode.size + 2) - 1
+ else
+ i -= 1
+ end
+
+ inside_shortname = false
+ elsif inside_tag && html[i] == '>'
+ inside_tag = false
+ elsif html[i] == '<'
+ inside_tag = true
+ inside_shortname = false
+ elsif !inside_tag && html[i] == ':'
+ inside_shortname = true
+ shortname_start_index = i
+ end
+ end
+
+ html
+ end
+
def rewrite(text, entities)
chars = text.to_s.to_char_a
diff --git a/app/lib/ostatus/activity/creation.rb b/app/lib/ostatus/activity/creation.rb
index 1a23c9efa..d3f1629c4 100644
--- a/app/lib/ostatus/activity/creation.rb
+++ b/app/lib/ostatus/activity/creation.rb
@@ -42,6 +42,7 @@ class OStatus::Activity::Creation < OStatus::Activity::Base
save_mentions(status)
save_hashtags(status)
save_media(status)
+ save_emojis(status)
end
if thread? && status.thread.nil?
@@ -150,6 +151,25 @@ class OStatus::Activity::Creation < OStatus::Activity::Base
end
end
+ def save_emojis(parent)
+ do_not_download = DomainBlock.find_by(domain: parent.account.domain)&.reject_media?
+
+ return if do_not_download
+
+ @xml.xpath('./xmlns:link[@rel="emoji"]', xmlns: TagManager::XMLNS).each do |link|
+ next unless link['href'] && link['name']
+
+ shortcode = link['name'].delete(':')
+ emoji = CustomEmoji.find_by(shortcode: shortcode, domain: parent.account.domain)
+
+ next unless emoji.nil?
+
+ emoji = CustomEmoji.new(shortcode: shortcode, domain: parent.account.domain)
+ emoji.image_remote_url = link['href']
+ emoji.save
+ end
+ end
+
def account_from_href(href)
url = Addressable::URI.parse(href).normalize
diff --git a/app/lib/ostatus/atom_serializer.rb b/app/lib/ostatus/atom_serializer.rb
index b8e22a381..a6a5cb0c4 100644
--- a/app/lib/ostatus/atom_serializer.rb
+++ b/app/lib/ostatus/atom_serializer.rb
@@ -368,5 +368,9 @@ class OStatus::AtomSerializer
end
append_element(entry, 'mastodon:scope', status.visibility)
+
+ status.emojis.each do |emoji|
+ append_element(entry, 'link', nil, rel: :emoji, href: full_asset_url(emoji.image.url), name: emoji.shortcode)
+ end
end
end
diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb
new file mode 100644
index 000000000..f4d3b16a0
--- /dev/null
+++ b/app/models/custom_emoji.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+# == Schema Information
+#
+# Table name: custom_emojis
+#
+# id :integer not null, primary key
+# shortcode :string default(""), not null
+# domain :string
+# image_file_name :string
+# image_content_type :string
+# image_file_size :integer
+# image_updated_at :datetime
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+
+class CustomEmoji < ApplicationRecord
+ SHORTCODE_RE_FRAGMENT = '[a-zA-Z0-9_]{2,}'
+
+ SCAN_RE = /(?<=[^[:alnum:]:]|\n|^)
+ :(#{SHORTCODE_RE_FRAGMENT}):
+ (?=[^[:alnum:]:]|$)/x
+
+ has_attached_file :image
+
+ 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 }
+
+ include Remotable
+
+ class << self
+ def from_text(text, domain)
+ return [] if text.blank?
+ shortcodes = text.scan(SCAN_RE).map(&:first)
+ where(shortcode: shortcodes, domain: domain)
+ end
+ end
+end
diff --git a/app/models/status.rb b/app/models/status.rb
index 2a2cdcf6e..326d128d6 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -131,6 +131,10 @@ class Status < ApplicationRecord
!sensitive? && media_attachments.any?
end
+ def emojis
+ CustomEmoji.from_text(text, account.domain)
+ end
+
after_create :store_uri, if: :local?
before_validation :prepare_contents, if: :local?
diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb
index 166214eee..e5d8e3f03 100644
--- a/app/serializers/activitypub/note_serializer.rb
+++ b/app/serializers/activitypub/note_serializer.rb
@@ -57,7 +57,7 @@ class ActivityPub::NoteSerializer < ActiveModel::Serializer
end
def virtual_tags
- object.mentions + object.tags
+ object.mentions + object.tags + object.emojis
end
def atom_uri
@@ -137,4 +137,22 @@ class ActivityPub::NoteSerializer < ActiveModel::Serializer
"##{object.name}"
end
end
+
+ class CustomEmojiSerializer < ActiveModel::Serializer
+ include RoutingHelper
+
+ attributes :type, :href, :name
+
+ def type
+ 'Emoji'
+ end
+
+ def href
+ full_asset_url(object.image.url)
+ end
+
+ def name
+ ":#{object.shortcode}:"
+ end
+ end
end
diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb
index 298a3bb40..d8efa8e60 100644
--- a/app/serializers/rest/status_serializer.rb
+++ b/app/serializers/rest/status_serializer.rb
@@ -17,6 +17,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
has_many :media_attachments, serializer: REST::MediaAttachmentSerializer
has_many :mentions
has_many :tags
+ has_many :emojis
def current_user?
!current_user.nil?
@@ -106,4 +107,14 @@ class REST::StatusSerializer < ActiveModel::Serializer
tag_url(object)
end
end
+
+ class CustomEmojiSerializer < ActiveModel::Serializer
+ include RoutingHelper
+
+ attributes :shortcode, :url
+
+ def url
+ full_asset_url(object.image.url)
+ end
+ end
end
diff --git a/app/views/stream_entries/_detailed_status.html.haml b/app/views/stream_entries/_detailed_status.html.haml
index dd9456260..692d5a6d5 100644
--- a/app/views/stream_entries/_detailed_status.html.haml
+++ b/app/views/stream_entries/_detailed_status.html.haml
@@ -17,7 +17,7 @@
%p{ style: 'margin-bottom: 0' }<
%span.p-summary> #{status.spoiler_text}
%a.status__content__spoiler-link{ href: '#' }= t('statuses.show_more')
- .e-content{ lang: status.language, style: "display: #{status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }= Formatter.instance.format(status)
+ .e-content{ lang: status.language, style: "display: #{status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }= Formatter.instance.format(status, custom_emojify: true)
- if !status.media_attachments.empty?
- if status.media_attachments.first.video?
diff --git a/app/views/stream_entries/_simple_status.html.haml b/app/views/stream_entries/_simple_status.html.haml
index 55aa97f32..f9a530d38 100644
--- a/app/views/stream_entries/_simple_status.html.haml
+++ b/app/views/stream_entries/_simple_status.html.haml
@@ -18,7 +18,7 @@
%p{ style: 'margin-bottom: 0' }<
%span.p-summary> #{status.spoiler_text}
%a.status__content__spoiler-link{ href: '#' }= t('statuses.show_more')
- .e-content{ lang: status.language, style: "display: #{status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }= Formatter.instance.format(status)
+ .e-content{ lang: status.language, style: "display: #{status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }= Formatter.instance.format(status, custom_emojify: true)
- unless status.media_attachments.empty?
- if status.media_attachments.first.video?
diff --git a/db/migrate/20170917153509_create_custom_emojis.rb b/db/migrate/20170917153509_create_custom_emojis.rb
new file mode 100644
index 000000000..4040c8312
--- /dev/null
+++ b/db/migrate/20170917153509_create_custom_emojis.rb
@@ -0,0 +1,13 @@
+class CreateCustomEmojis < ActiveRecord::Migration[5.1]
+ def change
+ create_table :custom_emojis do |t|
+ t.string :shortcode, null: false, default: ''
+ t.string :domain
+ t.attachment :image
+
+ t.timestamps
+ end
+
+ add_index :custom_emojis, [:shortcode, :domain], unique: true
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index f2ca2af69..9f42d46dd 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: 20170913000752) do
+ActiveRecord::Schema.define(version: 20170917153509) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -89,6 +89,18 @@ ActiveRecord::Schema.define(version: 20170913000752) do
t.index ["uri"], name: "index_conversations_on_uri", unique: true
end
+ create_table "custom_emojis", force: :cascade do |t|
+ t.string "shortcode", default: "", null: false
+ t.string "domain"
+ t.string "image_file_name"
+ t.string "image_content_type"
+ t.integer "image_file_size"
+ t.datetime "image_updated_at"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["shortcode", "domain"], name: "index_custom_emojis_on_shortcode_and_domain", unique: true
+ end
+
create_table "domain_blocks", id: :serial, force: :cascade do |t|
t.string "domain", default: "", null: false
t.datetime "created_at", null: false
diff --git a/spec/fabricators/custom_emoji_fabricator.rb b/spec/fabricators/custom_emoji_fabricator.rb
new file mode 100644
index 000000000..18a7d23dc
--- /dev/null
+++ b/spec/fabricators/custom_emoji_fabricator.rb
@@ -0,0 +1,5 @@
+Fabricator(:custom_emoji) do
+ shortcode 'coolcat'
+ domain nil
+ image { File.open(Rails.root.join('spec', 'fixtures', 'files', 'emojo.png')) }
+end
diff --git a/spec/fixtures/files/emojo.png b/spec/fixtures/files/emojo.png
new file mode 100644
index 000000000..cb5993499
Binary files /dev/null and b/spec/fixtures/files/emojo.png differ
diff --git a/spec/lib/activitypub/activity/create_spec.rb b/spec/lib/activitypub/activity/create_spec.rb
index fcb044ebc..1a9520f04 100644
--- a/spec/lib/activitypub/activity/create_spec.rb
+++ b/spec/lib/activitypub/activity/create_spec.rb
@@ -17,6 +17,7 @@ RSpec.describe ActivityPub::Activity::Create do
before do
stub_request(:get, 'http://example.com/attachment.png').to_return(request_fixture('avatar.txt'))
+ stub_request(:get, 'http://example.com/emoji.png').to_return(body: attachment_fixture('emojo.png'))
end
describe '#perform' do
@@ -217,5 +218,29 @@ RSpec.describe ActivityPub::Activity::Create do
expect(status.tags.map(&:name)).to include('test')
end
end
+
+ context 'with emojis' do
+ let(:object_json) do
+ {
+ id: 'bar',
+ type: 'Note',
+ content: 'Lorem ipsum :tinking:',
+ tag: [
+ {
+ type: 'Emoji',
+ href: 'http://example.com/emoji.png',
+ name: 'tinking',
+ },
+ ],
+ }
+ end
+
+ it 'creates status' do
+ status = sender.statuses.first
+
+ expect(status).to_not be_nil
+ expect(status.emojis.map(&:shortcode)).to include('tinking')
+ end
+ end
end
end
diff --git a/spec/lib/formatter_spec.rb b/spec/lib/formatter_spec.rb
index b714b317a..71b6b78d2 100644
--- a/spec/lib/formatter_spec.rb
+++ b/spec/lib/formatter_spec.rb
@@ -223,6 +223,45 @@ RSpec.describe Formatter do
include_examples 'encode and link URLs'
end
+
+ context 'with custom_emojify option' do
+ let!(:emoji) { Fabricate(:custom_emoji) }
+ let(:status) { Fabricate(:status, account: local_account, text: text) }
+
+ subject { Formatter.instance.format(status, custom_emojify: true) }
+
+ context 'with emoji at the start' do
+ let(:text) { ':coolcat: Beep boop' }
+
+ it 'converts shortcode to image tag' do
+ is_expected.to match(/
:coolcat: Beep boop
' }
+
+ it 'converts shortcode to image tag' do
+ is_expected.to match(/
Beep :coolcat: boop
' }
+
+ it 'converts shortcode to image tag' do
+ is_expected.to match(/Beep :coolcat::coolcat:
' }
+
+ it 'does not touch the shortcodes' do
+ is_expected.to match(/:coolcat::coolcat:<\/p>/)
+ end
+ end
+
+ context 'with emoji at the end' do
+ let(:text) { '
Beep boop
:coolcat:
' }
+
+ it 'converts shortcode to image tag' do
+ is_expected.to match(/
Hello :coolcat:' }
+
+ it 'returns records used via shortcodes in text' do
+ is_expected.to include(emojo)
+ end
+ end
+ end
+end
--
cgit
From dd4ef69839c78e4220cd821bc3ad369eb6e41fa7 Mon Sep 17 00:00:00 2001
From: aschmitz
Date: Fri, 22 Sep 2017 06:20:04 -0500
Subject: Hardcode IdToBigints migration columns (#5039)
This addresses a comment during review:
https://github.com/tootsuite/mastodon/pull/4801#discussion_r139337452
This means we'll need to make sure that all _id columns going forward
are bigints, but that should happen automatically in most cases.
---
db/migrate/20170918125918_ids_to_bigints.rb | 127 ++++++++++++++++++++++++++++
db/schema.rb | 122 +++++++++++++-------------
2 files changed, 188 insertions(+), 61 deletions(-)
create mode 100644 db/migrate/20170918125918_ids_to_bigints.rb
(limited to 'db/migrate')
diff --git a/db/migrate/20170918125918_ids_to_bigints.rb b/db/migrate/20170918125918_ids_to_bigints.rb
new file mode 100644
index 000000000..9c81ecc05
--- /dev/null
+++ b/db/migrate/20170918125918_ids_to_bigints.rb
@@ -0,0 +1,127 @@
+class IdsToBigints < ActiveRecord::Migration[5.1]
+ def up
+ change_column :account_domain_blocks, :account_id, :bigint
+ change_column :account_domain_blocks, :id, :bigint
+ change_column :accounts, :id, :bigint
+ change_column :blocks, :account_id, :bigint
+ change_column :blocks, :id, :bigint
+ change_column :blocks, :target_account_id, :bigint
+ change_column :conversation_mutes, :account_id, :bigint
+ change_column :conversation_mutes, :id, :bigint
+ change_column :deprecated_preview_cards, :id, :bigint
+ change_column :domain_blocks, :id, :bigint
+ change_column :favourites, :account_id, :bigint
+ change_column :favourites, :id, :bigint
+ change_column :favourites, :status_id, :bigint
+ change_column :follow_requests, :account_id, :bigint
+ change_column :follow_requests, :id, :bigint
+ change_column :follow_requests, :target_account_id, :bigint
+ change_column :follows, :account_id, :bigint
+ change_column :follows, :id, :bigint
+ change_column :follows, :target_account_id, :bigint
+ change_column :imports, :account_id, :bigint
+ change_column :imports, :id, :bigint
+ change_column :media_attachments, :account_id, :bigint
+ change_column :media_attachments, :id, :bigint
+ change_column :mentions, :account_id, :bigint
+ change_column :mentions, :id, :bigint
+ change_column :mutes, :account_id, :bigint
+ change_column :mutes, :id, :bigint
+ change_column :mutes, :target_account_id, :bigint
+ change_column :notifications, :account_id, :bigint
+ change_column :notifications, :from_account_id, :bigint
+ change_column :notifications, :id, :bigint
+ change_column :oauth_access_grants, :application_id, :bigint
+ change_column :oauth_access_grants, :id, :bigint
+ change_column :oauth_access_grants, :resource_owner_id, :bigint
+ change_column :oauth_access_tokens, :application_id, :bigint
+ change_column :oauth_access_tokens, :id, :bigint
+ change_column :oauth_access_tokens, :resource_owner_id, :bigint
+ change_column :oauth_applications, :id, :bigint
+ change_column :oauth_applications, :owner_id, :bigint
+ change_column :reports, :account_id, :bigint
+ change_column :reports, :action_taken_by_account_id, :bigint
+ change_column :reports, :id, :bigint
+ change_column :reports, :target_account_id, :bigint
+ change_column :session_activations, :access_token_id, :bigint
+ change_column :session_activations, :user_id, :bigint
+ change_column :session_activations, :web_push_subscription_id, :bigint
+ change_column :settings, :id, :bigint
+ change_column :settings, :thing_id, :bigint
+ change_column :statuses, :account_id, :bigint
+ change_column :statuses, :application_id, :bigint
+ change_column :statuses, :in_reply_to_account_id, :bigint
+ change_column :stream_entries, :account_id, :bigint
+ change_column :stream_entries, :id, :bigint
+ change_column :subscriptions, :account_id, :bigint
+ change_column :subscriptions, :id, :bigint
+ change_column :tags, :id, :bigint
+ change_column :users, :account_id, :bigint
+ change_column :users, :id, :bigint
+ change_column :web_settings, :id, :bigint
+ change_column :web_settings, :user_id, :bigint
+ end
+
+ def down
+ change_column :account_domain_blocks, :account_id, :integer
+ change_column :account_domain_blocks, :id, :integer
+ change_column :accounts, :id, :integer
+ change_column :blocks, :account_id, :integer
+ change_column :blocks, :id, :integer
+ change_column :blocks, :target_account_id, :integer
+ change_column :conversation_mutes, :account_id, :integer
+ change_column :conversation_mutes, :id, :integer
+ change_column :deprecated_preview_cards, :id, :integer
+ change_column :domain_blocks, :id, :integer
+ change_column :favourites, :account_id, :integer
+ change_column :favourites, :id, :integer
+ change_column :favourites, :status_id, :integer
+ change_column :follow_requests, :account_id, :integer
+ change_column :follow_requests, :id, :integer
+ change_column :follow_requests, :target_account_id, :integer
+ change_column :follows, :account_id, :integer
+ change_column :follows, :id, :integer
+ change_column :follows, :target_account_id, :integer
+ change_column :imports, :account_id, :integer
+ change_column :imports, :id, :integer
+ change_column :media_attachments, :account_id, :integer
+ change_column :media_attachments, :id, :integer
+ change_column :mentions, :account_id, :integer
+ change_column :mentions, :id, :integer
+ change_column :mutes, :account_id, :integer
+ change_column :mutes, :id, :integer
+ change_column :mutes, :target_account_id, :integer
+ change_column :notifications, :account_id, :integer
+ change_column :notifications, :from_account_id, :integer
+ change_column :notifications, :id, :integer
+ change_column :oauth_access_grants, :application_id, :integer
+ change_column :oauth_access_grants, :id, :integer
+ change_column :oauth_access_grants, :resource_owner_id, :integer
+ change_column :oauth_access_tokens, :application_id, :integer
+ change_column :oauth_access_tokens, :id, :integer
+ change_column :oauth_access_tokens, :resource_owner_id, :integer
+ change_column :oauth_applications, :id, :integer
+ change_column :oauth_applications, :owner_id, :integer
+ change_column :reports, :account_id, :integer
+ change_column :reports, :action_taken_by_account_id, :integer
+ change_column :reports, :id, :integer
+ change_column :reports, :target_account_id, :integer
+ change_column :session_activations, :access_token_id, :integer
+ change_column :session_activations, :user_id, :integer
+ change_column :session_activations, :web_push_subscription_id, :integer
+ change_column :settings, :id, :integer
+ change_column :settings, :thing_id, :integer
+ change_column :statuses, :account_id, :integer
+ change_column :statuses, :application_id, :integer
+ change_column :statuses, :in_reply_to_account_id, :integer
+ change_column :stream_entries, :account_id, :integer
+ change_column :stream_entries, :id, :integer
+ change_column :subscriptions, :account_id, :integer
+ change_column :subscriptions, :id, :integer
+ change_column :tags, :id, :integer
+ change_column :users, :account_id, :integer
+ change_column :users, :id, :integer
+ change_column :web_settings, :id, :integer
+ change_column :web_settings, :user_id, :integer
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 9f42d46dd..cf4edf9e5 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,20 +10,20 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20170917153509) do
+ActiveRecord::Schema.define(version: 20170918125918) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
- create_table "account_domain_blocks", id: :serial, force: :cascade do |t|
- t.integer "account_id"
+ create_table "account_domain_blocks", force: :cascade do |t|
+ t.bigint "account_id"
t.string "domain"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["account_id", "domain"], name: "index_account_domain_blocks_on_account_id_and_domain", unique: true
end
- create_table "accounts", id: :serial, force: :cascade do |t|
+ create_table "accounts", force: :cascade do |t|
t.string "username", default: "", null: false
t.string "domain"
t.string "secret", default: "", null: false
@@ -68,16 +68,16 @@ ActiveRecord::Schema.define(version: 20170917153509) do
t.index ["username", "domain"], name: "index_accounts_on_username_and_domain", unique: true
end
- create_table "blocks", id: :serial, force: :cascade do |t|
- t.integer "account_id", null: false
- t.integer "target_account_id", null: false
+ create_table "blocks", force: :cascade do |t|
+ t.bigint "account_id", null: false
+ t.bigint "target_account_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["account_id", "target_account_id"], name: "index_blocks_on_account_id_and_target_account_id", unique: true
end
- create_table "conversation_mutes", id: :serial, force: :cascade do |t|
- t.integer "account_id", null: false
+ create_table "conversation_mutes", force: :cascade do |t|
+ t.bigint "account_id", null: false
t.bigint "conversation_id", null: false
t.index ["account_id", "conversation_id"], name: "index_conversation_mutes_on_account_id_and_conversation_id", unique: true
end
@@ -101,7 +101,7 @@ ActiveRecord::Schema.define(version: 20170917153509) do
t.index ["shortcode", "domain"], name: "index_custom_emojis_on_shortcode_and_domain", unique: true
end
- create_table "domain_blocks", id: :serial, force: :cascade do |t|
+ create_table "domain_blocks", force: :cascade do |t|
t.string "domain", default: "", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
@@ -110,9 +110,9 @@ ActiveRecord::Schema.define(version: 20170917153509) do
t.index ["domain"], name: "index_domain_blocks_on_domain", unique: true
end
- create_table "favourites", id: :serial, force: :cascade do |t|
- t.integer "account_id", null: false
- t.integer "status_id", null: false
+ create_table "favourites", force: :cascade do |t|
+ t.bigint "account_id", null: false
+ t.bigint "status_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["account_id", "id"], name: "index_favourites_on_account_id_and_id"
@@ -120,24 +120,24 @@ ActiveRecord::Schema.define(version: 20170917153509) do
t.index ["status_id"], name: "index_favourites_on_status_id"
end
- create_table "follow_requests", id: :serial, force: :cascade do |t|
- t.integer "account_id", null: false
- t.integer "target_account_id", null: false
+ create_table "follow_requests", force: :cascade do |t|
+ t.bigint "account_id", null: false
+ t.bigint "target_account_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["account_id", "target_account_id"], name: "index_follow_requests_on_account_id_and_target_account_id", unique: true
end
- create_table "follows", id: :serial, force: :cascade do |t|
- t.integer "account_id", null: false
- t.integer "target_account_id", null: false
+ create_table "follows", force: :cascade do |t|
+ t.bigint "account_id", null: false
+ t.bigint "target_account_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["account_id", "target_account_id"], name: "index_follows_on_account_id_and_target_account_id", unique: true
end
- create_table "imports", id: :serial, force: :cascade do |t|
- t.integer "account_id", null: false
+ create_table "imports", force: :cascade do |t|
+ t.bigint "account_id", null: false
t.integer "type", null: false
t.boolean "approved", default: false, null: false
t.datetime "created_at", null: false
@@ -148,14 +148,14 @@ ActiveRecord::Schema.define(version: 20170917153509) do
t.datetime "data_updated_at"
end
- create_table "media_attachments", id: :serial, force: :cascade do |t|
+ create_table "media_attachments", force: :cascade do |t|
t.bigint "status_id"
t.string "file_file_name"
t.string "file_content_type"
t.integer "file_file_size"
t.datetime "file_updated_at"
t.string "remote_url", default: "", null: false
- t.integer "account_id"
+ t.bigint "account_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "shortcode"
@@ -166,8 +166,8 @@ ActiveRecord::Schema.define(version: 20170917153509) do
t.index ["status_id"], name: "index_media_attachments_on_status_id"
end
- create_table "mentions", id: :serial, force: :cascade do |t|
- t.integer "account_id"
+ create_table "mentions", force: :cascade do |t|
+ t.bigint "account_id"
t.bigint "status_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
@@ -175,29 +175,29 @@ ActiveRecord::Schema.define(version: 20170917153509) do
t.index ["status_id"], name: "index_mentions_on_status_id"
end
- create_table "mutes", id: :serial, force: :cascade do |t|
- t.integer "account_id", null: false
- t.integer "target_account_id", null: false
+ create_table "mutes", force: :cascade do |t|
+ t.bigint "account_id", null: false
+ t.bigint "target_account_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["account_id", "target_account_id"], name: "index_mutes_on_account_id_and_target_account_id", unique: true
end
- create_table "notifications", id: :serial, force: :cascade do |t|
- t.integer "account_id"
+ create_table "notifications", force: :cascade do |t|
+ t.bigint "account_id"
t.bigint "activity_id"
t.string "activity_type"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
- t.integer "from_account_id"
+ t.bigint "from_account_id"
t.index ["account_id", "activity_id", "activity_type"], name: "account_activity", unique: true
t.index ["activity_id", "activity_type"], name: "index_notifications_on_activity_id_and_activity_type"
t.index ["id", "account_id", "activity_type"], name: "index_notifications_on_id_and_account_id_and_activity_type", order: { id: :desc }
end
- create_table "oauth_access_grants", id: :serial, force: :cascade do |t|
- t.integer "resource_owner_id", null: false
- t.integer "application_id", null: false
+ create_table "oauth_access_grants", force: :cascade do |t|
+ t.bigint "resource_owner_id", null: false
+ t.bigint "application_id", null: false
t.string "token", null: false
t.integer "expires_in", null: false
t.text "redirect_uri", null: false
@@ -207,9 +207,9 @@ ActiveRecord::Schema.define(version: 20170917153509) do
t.index ["token"], name: "index_oauth_access_grants_on_token", unique: true
end
- create_table "oauth_access_tokens", id: :serial, force: :cascade do |t|
- t.integer "resource_owner_id"
- t.integer "application_id"
+ create_table "oauth_access_tokens", force: :cascade do |t|
+ t.bigint "resource_owner_id"
+ t.bigint "application_id"
t.string "token", null: false
t.string "refresh_token"
t.integer "expires_in"
@@ -221,7 +221,7 @@ ActiveRecord::Schema.define(version: 20170917153509) do
t.index ["token"], name: "index_oauth_access_tokens_on_token", unique: true
end
- create_table "oauth_applications", id: :serial, force: :cascade do |t|
+ create_table "oauth_applications", force: :cascade do |t|
t.string "name", null: false
t.string "uid", null: false
t.string "secret", null: false
@@ -231,7 +231,7 @@ ActiveRecord::Schema.define(version: 20170917153509) do
t.datetime "updated_at"
t.boolean "superapp", default: false, null: false
t.string "website"
- t.integer "owner_id"
+ t.bigint "owner_id"
t.string "owner_type"
t.index ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type"
t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true
@@ -264,37 +264,37 @@ ActiveRecord::Schema.define(version: 20170917153509) do
t.index ["status_id", "preview_card_id"], name: "index_preview_cards_statuses_on_status_id_and_preview_card_id"
end
- create_table "reports", id: :serial, force: :cascade do |t|
- t.integer "account_id", null: false
- t.integer "target_account_id", null: false
+ create_table "reports", force: :cascade do |t|
+ t.bigint "account_id", null: false
+ t.bigint "target_account_id", null: false
t.bigint "status_ids", default: [], null: false, array: true
t.text "comment", default: "", null: false
t.boolean "action_taken", default: false, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
- t.integer "action_taken_by_account_id"
+ t.bigint "action_taken_by_account_id"
t.index ["account_id"], name: "index_reports_on_account_id"
t.index ["target_account_id"], name: "index_reports_on_target_account_id"
end
create_table "session_activations", force: :cascade do |t|
- t.integer "user_id", null: false
+ t.bigint "user_id", null: false
t.string "session_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "user_agent", default: "", null: false
t.inet "ip"
- t.integer "access_token_id"
- t.integer "web_push_subscription_id"
+ t.bigint "access_token_id"
+ t.bigint "web_push_subscription_id"
t.index ["session_id"], name: "index_session_activations_on_session_id", unique: true
t.index ["user_id"], name: "index_session_activations_on_user_id"
end
- create_table "settings", id: :serial, force: :cascade do |t|
+ create_table "settings", force: :cascade do |t|
t.string "var", null: false
t.text "value"
t.string "thing_type"
- t.integer "thing_id"
+ t.bigint "thing_id"
t.datetime "created_at"
t.datetime "updated_at"
t.index ["thing_type", "thing_id", "var"], name: "index_settings_on_thing_type_and_thing_id_and_var", unique: true
@@ -322,7 +322,7 @@ ActiveRecord::Schema.define(version: 20170917153509) do
create_table "statuses", force: :cascade do |t|
t.string "uri"
- t.integer "account_id", null: false
+ t.bigint "account_id", null: false
t.text "text", default: "", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
@@ -331,8 +331,8 @@ ActiveRecord::Schema.define(version: 20170917153509) do
t.string "url"
t.boolean "sensitive", default: false, null: false
t.integer "visibility", default: 0, null: false
- t.integer "in_reply_to_account_id"
- t.integer "application_id"
+ t.bigint "in_reply_to_account_id"
+ t.bigint "application_id"
t.text "spoiler_text", default: "", null: false
t.boolean "reply", default: false, null: false
t.integer "favourites_count", default: 0, null: false
@@ -349,13 +349,13 @@ ActiveRecord::Schema.define(version: 20170917153509) do
create_table "statuses_tags", id: false, force: :cascade do |t|
t.bigint "status_id", null: false
- t.integer "tag_id", null: false
+ t.bigint "tag_id", null: false
t.index ["status_id"], name: "index_statuses_tags_on_status_id"
t.index ["tag_id", "status_id"], name: "index_statuses_tags_on_tag_id_and_status_id", unique: true
end
- create_table "stream_entries", id: :serial, force: :cascade do |t|
- t.integer "account_id"
+ create_table "stream_entries", force: :cascade do |t|
+ t.bigint "account_id"
t.bigint "activity_id"
t.string "activity_type"
t.datetime "created_at", null: false
@@ -365,12 +365,12 @@ ActiveRecord::Schema.define(version: 20170917153509) do
t.index ["activity_id", "activity_type"], name: "index_stream_entries_on_activity_id_and_activity_type"
end
- create_table "subscriptions", id: :serial, force: :cascade do |t|
+ create_table "subscriptions", force: :cascade do |t|
t.string "callback_url", default: "", null: false
t.string "secret"
t.datetime "expires_at"
t.boolean "confirmed", default: false, null: false
- t.integer "account_id", null: false
+ t.bigint "account_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "last_successful_delivery_at"
@@ -378,7 +378,7 @@ ActiveRecord::Schema.define(version: 20170917153509) do
t.index ["account_id", "callback_url"], name: "index_subscriptions_on_account_id_and_callback_url", unique: true
end
- create_table "tags", id: :serial, force: :cascade do |t|
+ create_table "tags", force: :cascade do |t|
t.string "name", default: "", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
@@ -386,9 +386,9 @@ ActiveRecord::Schema.define(version: 20170917153509) do
t.index ["name"], name: "index_tags_on_name", unique: true
end
- create_table "users", id: :serial, force: :cascade do |t|
+ create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
- t.integer "account_id", null: false
+ t.bigint "account_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "encrypted_password", default: "", null: false
@@ -430,8 +430,8 @@ ActiveRecord::Schema.define(version: 20170917153509) do
t.datetime "updated_at", null: false
end
- create_table "web_settings", id: :serial, force: :cascade do |t|
- t.integer "user_id"
+ create_table "web_settings", force: :cascade do |t|
+ t.bigint "user_id"
t.json "data"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
--
cgit
From dcfc9b220462533c8bcdebc7ca25a68fa23aaf5f Mon Sep 17 00:00:00 2001
From: nullkal
Date: Sat, 23 Sep 2017 02:32:57 +0900
Subject: Fix the migration error when deprecated_preview_cards has been
deleted (#5043)
* Fix the migration error when deprecated_preview_cards has deleted
* Re-run Travis CI
---
db/migrate/20170918125918_ids_to_bigints.rb | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
(limited to 'db/migrate')
diff --git a/db/migrate/20170918125918_ids_to_bigints.rb b/db/migrate/20170918125918_ids_to_bigints.rb
index 9c81ecc05..7483dd77a 100644
--- a/db/migrate/20170918125918_ids_to_bigints.rb
+++ b/db/migrate/20170918125918_ids_to_bigints.rb
@@ -8,7 +8,7 @@ class IdsToBigints < ActiveRecord::Migration[5.1]
change_column :blocks, :target_account_id, :bigint
change_column :conversation_mutes, :account_id, :bigint
change_column :conversation_mutes, :id, :bigint
- change_column :deprecated_preview_cards, :id, :bigint
+ change_column :deprecated_preview_cards, :id, :bigint if table_exists?(:deprecated_preview_cards)
change_column :domain_blocks, :id, :bigint
change_column :favourites, :account_id, :bigint
change_column :favourites, :id, :bigint
@@ -71,7 +71,7 @@ class IdsToBigints < ActiveRecord::Migration[5.1]
change_column :blocks, :target_account_id, :integer
change_column :conversation_mutes, :account_id, :integer
change_column :conversation_mutes, :id, :integer
- change_column :deprecated_preview_cards, :id, :integer
+ change_column :deprecated_preview_cards, :id, :integer if table_exists?(:deprecated_preview_cards)
change_column :domain_blocks, :id, :integer
change_column :favourites, :account_id, :integer
change_column :favourites, :id, :integer
--
cgit
From b2820c3913266711716c6d91b2198d964881dbba Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Sun, 24 Sep 2017 05:58:07 +0200
Subject: Forgotten in #5039, change statuses_tags tag_id to bigint (#5070)
---
db/migrate/20170924022025_ids_to_bigints2.rb | 9 +++++++++
db/schema.rb | 2 +-
2 files changed, 10 insertions(+), 1 deletion(-)
create mode 100644 db/migrate/20170924022025_ids_to_bigints2.rb
(limited to 'db/migrate')
diff --git a/db/migrate/20170924022025_ids_to_bigints2.rb b/db/migrate/20170924022025_ids_to_bigints2.rb
new file mode 100644
index 000000000..db8ccd919
--- /dev/null
+++ b/db/migrate/20170924022025_ids_to_bigints2.rb
@@ -0,0 +1,9 @@
+class IdsToBigints2 < ActiveRecord::Migration[5.1]
+ def up
+ change_column :statuses_tags, :tag_id, :bigint
+ end
+
+ def down
+ change_column :statuses_tags, :tag_id, :integer
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index cf4edf9e5..e16599d32 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: 20170918125918) do
+ActiveRecord::Schema.define(version: 20170924022025) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
--
cgit