diff --git a/app/controllers/private_controller.rb b/app/controllers/private_controller.rb index 5ac411a..7666a40 100644 --- a/app/controllers/private_controller.rb +++ b/app/controllers/private_controller.rb @@ -1,23 +1,37 @@ class PrivateController < ApplicationController # This a private trusted API skip_before_action :verify_authenticity_token # PUT /replace_ds def replace_ds parent, child, ds = replace_ds_params.values_at(:parent, :child, :ds) Domain.replace_ds(parent, child, ds) render json: { ok: true } end + # PUT /trigger_event + def trigger_event + result = Domain + .find_by_name(action_params[:domain]) + .fire_state_event(action_params[:event], params[:args]) + render json: { ok: result } + end + private + def action_params + params.require(:domain) + params.require(:event) + params.permit(:domain, :event, args: []) + end + def replace_ds_params params.require(:parent) params.require(:child) params.require(:ds) params.permit(ds: []) params end end diff --git a/app/helpers/domains_helper.rb b/app/helpers/domains_helper.rb new file mode 100644 index 0000000..4fb5152 --- /dev/null +++ b/app/helpers/domains_helper.rb @@ -0,0 +1,17 @@ +module DomainsHelper + + # Human names for domain states + def human_state(state) + case state.to_sym + when :initial then 'Initial' + when :pending_install then 'Becoming public' + when :pending_dnssec then 'Migrating to dnssec' + when :pending_plain then 'Removing dnssec' + when :pending_remove then 'Preparing removal' + when :operational then 'Operational' + when :destroy then 'Ready to be destroyed' + else + state + end + end +end diff --git a/app/models/domain.rb b/app/models/domain.rb index ae700f9..a88c911 100644 --- a/app/models/domain.rb +++ b/app/models/domain.rb @@ -1,125 +1,187 @@ class Domain < ActiveRecord::Base class NotAChild < StandardError; end self.inheritance_column = :nx # List all supported domain types. def self.domain_types [ 'NATIVE', 'MASTER', 'SLAVE', ] end # List domain types that can be created. def self.allowed_domain_types domain_types - WebDNS.settings[:prohibit_domain_types] end belongs_to :group has_many :jobs has_many :records # BUG in bump_serial_trigger has_one :soa, -> { unscope(where: :type) }, class_name: SOA validates :group_id, presence: true validates :name, uniqueness: true, presence: true validates :type, presence: true, inclusion: { in: domain_types } validates :master, presence: true, ipv4: true, if: :slave? after_create :generate_soa after_create :generate_ns + after_create :install + before_save :fire_convert + attr_writer :serial_strategy + state_machine initial: :initial do + after_transition(any => :pending_install) { |domain, _t| Job.add_domain(domain) } + after_transition(any => :pending_remove) { |domain, _t| Job.remove_domain(domain) } + after_transition(any => :pending_signing) { |domain, _t| Job.dnssec_sign(domain) } + after_transition(any => :wait_for_ready) { |domain, _t| Job.wait_for_ready(domain) } + after_transition(any => :pending_ds) { |domain, t| Job.dnssec_push_ds(domain, *t.args) } + after_transition(any => :pending_plain) { |domain, _t| Job.convert_to_plain(domain) } + after_transition(any => :destroy) { |domain, _t| domain.destroy } + + # User events + event :install do + transition initial: :pending_install + end + + event :dnssec_sign do + transition operational: :pending_signing + end + + event :signed do + transition pending_signing: :wait_for_ready + end + + event :push_ds do + # TODO: push_ds is triggered on multiple occasions + # operational: :operational + transition wait_for_ready: :pending_ds + end + + event :plain_convert do + transition operational: :pending_plain + end + + event :remove do + transition operational: :pending_remove + end + + # Machine events + event :installed do + transition pending_install: :operational + end + + event :converted do + transition [:pending_ds, :pending_plain] => :operational + end + + event :cleaned_up do + transition pending_remove: :destroy + end + end + # Get the zone's serial strategy. # # Returns one of the supported serial strategies. def serial_strategy @serial_strategy ||= WebDNS.settings[:serial_strategy] end # Returns true if this a reverse zone. def reverse? name.end_with?('.in-addr.arpa') || name.end_with?('.ip6.arpa') end # Returns true if this a ENUM zone. def enum? name.end_with?('.e164.arpa') end # Returns true if this is a slave zone. def slave? type == 'SLAVE' end # Compute subnet for reverse records def subnet return if not reverse? if name.end_with?('.in-addr.arpa') subnet_v4 elsif name.end_with?('.ip6.arpa') subnet_v6 end end def self.replace_ds(parent, child, records) parent = find_by_name!(parent) fail NotAChild if not child.end_with?(parent.name) existing = parent.records.where(name: child, type: 'DS') recs = records.map { |rec| DS.new(domain: parent, name: child, content: rec) } ActiveRecord::Base.transaction do existing.destroy_all recs.map(&:save!) end end private def subnet_v4 # get ip octets (remove .in-addr.arpa) octets = name.split('.')[0...-2].reverse return if octets.any? { |_| false } mask = 8 * octets.size octets += [0, 0, 0, 0] ip = IPAddr.new octets[0, 4].join('.') [ip, mask].join('/') end def subnet_v6 nibbles = name.split('.')[0...-2].reverse return if nibbles.any? { |_| false } mask = 4 * nibbles.size nibbles += [0] * 32 ip = IPAddr.new nibbles[0, 32].in_groups_of(4).map(&:join).join(':') [ip, mask].join('/') end # Hooks def generate_soa soa_record = SOA.new(domain: self) soa_record.save! end def generate_ns return if slave? return if WebDNS.settings[:default_ns].empty? WebDNS.settings[:default_ns].each { |ns| Record.find_or_create_by!(domain: self, type: 'NS', name: '', content: ns) } end + def fire_convert + return if !dnssec_changed? + + event = dnssec ? :dnssec_convert : :plain_convert + return true if fire_state_event(event) + + errors.add(:dnssec, 'You cannot modify dnssec settings in this state!') + false + end end diff --git a/app/models/job.rb b/app/models/job.rb index d439ba8..be436f5 100644 --- a/app/models/job.rb +++ b/app/models/job.rb @@ -1,70 +1,81 @@ class Job < ActiveRecord::Base belongs_to :domain scope :pending, -> { where(status: 0) } scope :completed, -> { where(status: [1, 2]) } def failed? status == 2 end class << self def add_domain(domain) ActiveRecord::Base.transaction do jobs_for_domain(domain, :add_domain) trigger_event(domain, :installed) end end def remove_domain(domain) ActiveRecord::Base.transaction do jobs_for_domain(domain, :remove_domain) trigger_event(domain, :cleaned_up) end end - def convert_to_dnssec(domain) + def dnssec_sign(domain) ActiveRecord::Base.transaction do jobs_for_domain(domain, :opendnssec_add, - :bind_convert_to_dnssec, - :wait_for_ready_to_push_ds, - :publish_ds, - :wait_for_active) + :bind_convert_to_dnssec) + + trigger_event(domain, :signed) + end + end + + def wait_for_ready(domain) + jobs_for_domain(domain, + :wait_for_ready_to_push_ds) + end + + def dnssec_push_ds(domain, dss) + ActiveRecord::Base.transaction do + job_for_domain(domain, :publish_ds, dss: dss) + job_for_domain(domain, :wait_for_active) trigger_event(domain, :converted) end end def convert_to_plain(domain) ActiveRecord::Base.transaction do jobs_for_domain(domain, :remove_domain, :add_domain, :opendnssec_remove) trigger_event(domain, :converted) end end private def trigger_event(domain, event) job_for_domain(domain, :trigger_event, event: event) end def jobs_for_domain(domain, *job_names) job_names.each { |job_name| job_for_domain(domain, job_name) } end def job_for_domain(domain, job_name, args = {}) args = { zone: domain.name }.merge!(args) create!(domain: domain, job_type: job_name, args: args.to_json) end end end diff --git a/app/views/domains/index.html.erb b/app/views/domains/index.html.erb index abd65c6..f0229f9 100644 --- a/app/views/domains/index.html.erb +++ b/app/views/domains/index.html.erb @@ -1,55 +1,57 @@ <% if current_user.memberships.empty? %>

Wellcome to WebDNS!

In order to manage domains you have to be a member of a group.

You can either contact an admin to create a new group for you, or ask another user for an invite to an existing group.

<% end %> <% @domains.group_by(&:group).each do |group, domains| %> + <% domains.each do |domain| %> + <% end %> <% end %>
<%= link_to group.name, group_path(group) %> <%= link_to glyph('menu-down'), "##{group.id}", onclick: "$('tr.group-#{group.id}').toggleClass('hidden');" %> State Controls
<% if domain.reverse? %> <%= abbr_glyph('chevron-left', 'Reverse') %> <% elsif domain.enum? %> <%= abbr_glyph('phone-alt', 'Enum') %> <% else %> <%= abbr_glyph('chevron-right', 'Forward') %> <% end %> <% if domain.slave? %> <%= abbr_glyph('link', 'Slave') %> <% end %> <%= link_to domain.name, domain %><%= human_state(domain.state) %> <%= link_to_edit edit_domain_path(domain) %> <%= link_to_destroy domain, method: :delete, data: { confirm: 'Are you sure?' } %>

<% if current_user.memberships.any? %> <%= link_to 'Add Domain', new_domain_path, class: 'btn btn-primary' %> <% else %> <%= link_to 'Add Domain', new_domain_path, class: 'btn btn-primary disabled' %> <% end %>

diff --git a/app/views/groups/show.html.erb b/app/views/groups/show.html.erb index cf564ef..7eee3b4 100644 --- a/app/views/groups/show.html.erb +++ b/app/views/groups/show.html.erb @@ -1,80 +1,82 @@ <% content_for :more_breadcrumbs do %>
  • <%= link_to_edit edit_admin_group_path(@group) %> <%= link_to_destroy admin_group_path(@group), method: :delete, data: { confirm: 'Are you sure?' } %>
  • <% end if admin? %>
    + <% @group.domains.each do |domain| %> + <% end %>
    DomainState Controls
    <% if domain.reverse? %> <%= abbr_glyph('chevron-left', 'Reverse') %> <% elsif domain.enum? %> <%= abbr_glyph('phone-alt', 'Enum') %> <% else %> <%= abbr_glyph('chevron-right', 'Forward') %> <% end %> <% if domain.slave? %> <%= abbr_glyph('link', 'Slave') %> <% end %> <%= link_to domain.name, domain %><%= human_state(domain.state) %> <%= link_to_edit edit_domain_path(domain) %> <%= link_to_destroy domain, method: :delete, data: { confirm: 'Are you sure?' } %>

    <%= link_to 'Add Domain', new_domain_path(group_id: @group.id), class: 'btn btn-primary' %>

    <% @group.memberships.includes(:user).each do |membership| %> <% end %>
    Member Controls
    <%= membership.user.email %><%= " (you)" if current_user == membership.user %> <%= link_to_destroy destroy_member_group_path(@group, membership.user_id), method: :delete %>

    <%= bootstrap_form_tag(url: create_member_group_path(@group), layout: :inline) do |f| %> <%= f.text_field :email, prepend: 'Add Member', hide_label: true, id: 'js-search-member', data: { group: @group.id } %> <%= f.submit 'Add', class: 'btn btn-primary' %> <% end %>

    diff --git a/config/routes.rb b/config/routes.rb index badf31b..b2e97aa 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,43 +1,44 @@ Rails.application.routes.draw do # Override devise user removal devise_scope :users do delete :users, to: redirect('/') end devise_for :users root to: redirect('/domains') resources :groups, only: [:show] do get :search_member, to: 'groups#search_member', on: :member post :members, to: 'groups#create_member', as: :create_member, on: :member delete 'member/:user_id', to: 'groups#destroy_member', as: :destroy_member, on: :member end resources :domains do resources :records, except: [:index, :show] do # Reuse records#update instead of introducing new controller actions # # rubocop:disable Style/AlignHash put :disable, to: 'records#update', on: :member, defaults: { record: { disabled: true } } put :enable, to: 'records#update', on: :member, defaults: { record: { disabled: false } } # rubocop:enable Style/AlignHash end end # Admin namespace :admin do root to: redirect('/admin/groups') resources :groups, except: [:show] resources :jobs, only: [:index, :destroy] end # Private put 'private/replace_ds', to: 'private#replace_ds' + put 'private/trigger_event', to: 'private#trigger_event' end diff --git a/db/migrate/20151213102322_add_state.rb b/db/migrate/20151213102322_add_state.rb new file mode 100644 index 0000000..4667aa4 --- /dev/null +++ b/db/migrate/20151213102322_add_state.rb @@ -0,0 +1,5 @@ +class AddState < ActiveRecord::Migration + def change + add_column :domains, :state, :string, default: 'initial', null: false + end +end diff --git a/db/structure.sql b/db/structure.sql index 205fa60..92167c5 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -1,281 +1,283 @@ -- MySQL dump 10.15 Distrib 10.0.20-MariaDB, for debian-linux-gnu (x86_64) -- -- Host: localhost Database: webns -- ------------------------------------------------------ -- Server version 10.0.20-MariaDB-3 /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; -- -- Table structure for table `comments` -- DROP TABLE IF EXISTS `comments`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `comments` ( `id` int(11) NOT NULL AUTO_INCREMENT, `domain_id` int(11) NOT NULL, `name` varchar(255) NOT NULL, `type` varchar(10) NOT NULL, `modified_at` int(11) NOT NULL, `account` varchar(40) NOT NULL, `comment` mediumtext NOT NULL, PRIMARY KEY (`id`), KEY `comments_domain_id_idx` (`domain_id`), KEY `comments_name_type_idx` (`name`,`type`), KEY `comments_order_idx` (`domain_id`,`modified_at`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Table structure for table `cryptokeys` -- DROP TABLE IF EXISTS `cryptokeys`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `cryptokeys` ( `id` int(11) NOT NULL AUTO_INCREMENT, `domain_id` int(11) NOT NULL, `flags` int(11) NOT NULL, `active` tinyint(1) DEFAULT NULL, `content` text, PRIMARY KEY (`id`), KEY `domainidindex` (`domain_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Table structure for table `domainmetadata` -- DROP TABLE IF EXISTS `domainmetadata`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `domainmetadata` ( `id` int(11) NOT NULL AUTO_INCREMENT, `domain_id` int(11) NOT NULL, `kind` varchar(32) DEFAULT NULL, `content` text, PRIMARY KEY (`id`), KEY `domainmetadata_idx` (`domain_id`,`kind`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Table structure for table `domains` -- DROP TABLE IF EXISTS `domains`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `domains` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, `master` varchar(128) DEFAULT NULL, `last_check` int(11) DEFAULT NULL, `type` varchar(6) NOT NULL, `notified_serial` int(11) DEFAULT NULL, `account` varchar(40) DEFAULT NULL, `group_id` int(11) DEFAULT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, + `state` varchar(255) NOT NULL DEFAULT 'initial', PRIMARY KEY (`id`), UNIQUE KEY `name_index` (`name`), KEY `index_domains_on_group_id` (`group_id`) ) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Table structure for table `groups` -- DROP TABLE IF EXISTS `groups`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `groups` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, `disabled` tinyint(1) DEFAULT '0', `created_at` datetime DEFAULT NULL, `updated_at` datetime DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `index_groups_on_name` (`name`) ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Table structure for table `jobs` -- DROP TABLE IF EXISTS `jobs`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `jobs` ( `id` int(11) NOT NULL AUTO_INCREMENT, `job_type` varchar(255) NOT NULL, `domain_id` int(11) DEFAULT NULL, `args` varchar(255) NOT NULL, `status` int(11) NOT NULL DEFAULT '0', `retries` int(11) NOT NULL DEFAULT '0', `created_at` datetime DEFAULT NULL, `updated_at` datetime DEFAULT NULL, PRIMARY KEY (`id`), KEY `index_jobs_on_domain_id` (`domain_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Table structure for table `memberships` -- DROP TABLE IF EXISTS `memberships`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `memberships` ( `id` int(11) NOT NULL AUTO_INCREMENT, `group_id` int(11) DEFAULT NULL, `user_id` int(11) DEFAULT NULL, `created_at` datetime DEFAULT NULL, `updated_at` datetime DEFAULT NULL, PRIMARY KEY (`id`), KEY `index_memberships_on_group_id` (`group_id`), KEY `index_memberships_on_user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Table structure for table `records` -- DROP TABLE IF EXISTS `records`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `records` ( `id` int(11) NOT NULL AUTO_INCREMENT, `domain_id` int(11) DEFAULT NULL, `name` varchar(255) DEFAULT NULL, `type` varchar(10) DEFAULT NULL, `content` mediumtext, `ttl` int(11) DEFAULT NULL, `prio` int(11) DEFAULT NULL, `change_date` int(11) DEFAULT NULL, `disabled` tinyint(1) DEFAULT '0', `ordername` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL, `auth` tinyint(1) DEFAULT '1', `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, PRIMARY KEY (`id`), KEY `nametype_index` (`name`,`type`), KEY `domain_id` (`domain_id`), KEY `recordorder` (`domain_id`,`ordername`), CONSTRAINT `records_ibfk_1` FOREIGN KEY (`domain_id`) REFERENCES `domains` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB AUTO_INCREMENT=32 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Table structure for table `schema_migrations` -- DROP TABLE IF EXISTS `schema_migrations`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `schema_migrations` ( `version` varchar(255) NOT NULL, UNIQUE KEY `unique_schema_migrations` (`version`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Table structure for table `supermasters` -- DROP TABLE IF EXISTS `supermasters`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `supermasters` ( `ip` varchar(64) NOT NULL, `nameserver` varchar(255) NOT NULL, `account` varchar(40) NOT NULL, PRIMARY KEY (`ip`,`nameserver`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Table structure for table `tsigkeys` -- DROP TABLE IF EXISTS `tsigkeys`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tsigkeys` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, `algorithm` varchar(50) DEFAULT NULL, `secret` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `namealgoindex` (`name`,`algorithm`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Table structure for table `users` -- DROP TABLE IF EXISTS `users`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `email` varchar(255) NOT NULL DEFAULT '', `encrypted_password` varchar(255) NOT NULL DEFAULT '', `reset_password_token` varchar(255) DEFAULT NULL, `reset_password_sent_at` datetime DEFAULT NULL, `remember_created_at` datetime DEFAULT NULL, `sign_in_count` int(11) NOT NULL DEFAULT '0', `current_sign_in_at` datetime DEFAULT NULL, `last_sign_in_at` datetime DEFAULT NULL, `current_sign_in_ip` varchar(255) DEFAULT NULL, `last_sign_in_ip` varchar(255) DEFAULT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `index_users_on_email` (`email`), UNIQUE KEY `index_users_on_reset_password_token` (`reset_password_token`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; -- Dump completed on 2015-11-08 12:57:51 INSERT INTO schema_migrations (version) VALUES ('20151028123326'); INSERT INTO schema_migrations (version) VALUES ('20151028123327'); INSERT INTO schema_migrations (version) VALUES ('20151031184819'); INSERT INTO schema_migrations (version) VALUES ('20151107182656'); INSERT INTO schema_migrations (version) VALUES ('20151108093333'); INSERT INTO schema_migrations (version) VALUES ('20151108105701'); INSERT INTO schema_migrations (version) VALUES ('20151207194729'); +INSERT INTO schema_migrations (version) VALUES ('20151213102322');