From: Shaun McDonald Date: Thu, 3 Jul 2008 13:06:24 +0000 (+0000) Subject: Merge changes from trunk 7673:8632. X-Git-Tag: live~8665^2~325 X-Git-Url: https://git.openstreetmap.org/rails.git/commitdiff_plain/5f8ab9e9244550b20b8d3bd97b3567df7020d06d?hp=08d1f5aaab193054c465ebda62ae82d97a1125be Merge changes from trunk 7673:8632. --- diff --git a/app/controllers/api_controller.rb b/app/controllers/api_controller.rb index 9cf8977d3..05dfb0133 100644 --- a/app/controllers/api_controller.rb +++ b/app/controllers/api_controller.rb @@ -120,7 +120,7 @@ class ApiController < ApplicationController return end if node_ids.length == 0 - render :text => "", :content_type => "text/xml" + render :text => "", :content_type => "text/xml" return end @@ -246,8 +246,8 @@ class ApiController < ApplicationController api = XML::Node.new 'api' version = XML::Node.new 'version' - version['minimum'] = '0.5'; - version['maximum'] = '0.5'; + version['minimum'] = "#{API_VERSION}"; + version['maximum'] = "#{API_VERSION}"; api << version area = XML::Node.new 'area' area['maximum'] = MAX_REQUEST_AREA.to_s; diff --git a/app/controllers/application.rb b/app/controllers/application.rb index 918e4b617..68359585e 100644 --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -71,7 +71,7 @@ class ApplicationController < ActionController::Base # phrase from that, we can also put the error message into the status # message. For now, rails won't let us) def report_error(message) - render :nothing => true, :status => :bad_request + render :text => message, :status => :bad_request # Todo: some sort of escaping of problem characters in the message response.headers['Error'] = message end @@ -82,6 +82,8 @@ private def get_auth_data if request.env.has_key? 'X-HTTP_AUTHORIZATION' # where mod_rewrite might have put it authdata = request.env['X-HTTP_AUTHORIZATION'].to_s.split + elsif request.env.has_key? 'REDIRECT_X_HTTP_AUTHORIZATION' # mod_fcgi + authdata = request.env['REDIRECT_X_HTTP_AUTHORIZATION'].to_s.split elsif request.env.has_key? 'HTTP_AUTHORIZATION' # regular location authdata = request.env['HTTP_AUTHORIZATION'].to_s.split end diff --git a/app/controllers/changeset_controller.rb b/app/controllers/changeset_controller.rb new file mode 100644 index 000000000..8668611eb --- /dev/null +++ b/app/controllers/changeset_controller.rb @@ -0,0 +1,169 @@ +# The ChangesetController is the RESTful interface to Changeset objects + +class ChangesetController < ApplicationController + require 'xml/libxml' + + before_filter :authorize, :only => [:create, :update, :delete, :upload] + before_filter :check_write_availability, :only => [:create, :update, :delete, :upload] + before_filter :check_read_availability, :except => [:create, :update, :delete, :upload] + after_filter :compress_output + + # Create a changeset from XML. + def create + if request.put? + cs = Changeset.from_xml(request.raw_post, true) + + if cs + cs.user_id = @user.id + cs.save_with_tags! + render :text => cs.id.to_s, :content_type => "text/plain" + else + render :nothing => true, :status => :bad_request + end + else + render :nothing => true, :status => :method_not_allowed + end + end + + def create_prim(ids, prim, nd) + prim.version = 0 + prim.user_id = @user.id + prim.visible = true + prim.save_with_history! + + ids[nd['id'].to_i] = prim.id + end + + def fix_way(w, node_ids) + w.nds = w.instance_eval { @nds }. + map { |nd| node_ids[nd] || nd } + return w + end + + def fix_rel(r, ids) + r.members = r.instance_eval { @members }. + map { |memb| [memb[0], ids[memb[0]][memb[1].to_i] || memb[1], memb[2]] } + return r + end + + def read + begin + changeset = Changeset.find(params[:id]) + render :text => changeset.to_xml.to_s, :content_type => "text/xml" + rescue ActiveRecord::RecordNotFound + render :nothing => true, :status => :not_found + end + end + + def close + begin + if not request.put? + render :nothing => true, :status => :method_not_allowed + return + end + changeset = Changeset.find(params[:id]) + changeset.open = false + changeset.save + render :nothing => true + rescue ActiveRecord::RecordNotFound + render :nothing => true, :status => :not_found + end + end + + def upload + if not request.put? + render :nothing => true, :status => :method_not_allowed + return + end + + p = XML::Reader.new request.raw_post + + node_ids, way_ids, rel_ids = {}, {}, {} + ids = {"node"=>node_ids, "way"=>way_ids, "relation"=>rel_ids} + + models = {"node"=>Node, "way"=>Way, "relation"=>Relation} + + res = XML::Document.new + res.encoding = 'UTF-8' + root = XML::Node.new 'osm' + root['version'] = '0.6' + root['creator'] = 'OpenStreetMap.org' + res.root = root + + root << XML::Node.new_comment(" Warning: this is a 0.6 result document, " + + "not a normal OSM file. ") + + Changeset.transaction do + while p.read == 1 + break if p.node_type == 15 # end element + next unless p.node_type == 1 # element + + case p.name + when 'create': + while p.read == 1 + break if p.node_type == 15 # end element + next unless p.node_type == 1 # element + + model = models[p.name] + next if model.nil? + + elem = XML::Node.new p.name + nd = p.expand; p.next + osm = model.from_xml_node(nd, true) + elem['old_id'] = nd['id'] + + case nd.name + when 'way': + fix_way(osm, node_ids) + raise OSM::APIPreconditionFailedError.new if !osm.preconditions_ok? + when 'relation': + fix_rel(osm, ids) + raise OSM::APIPreconditionFailedError.new if !osm.preconditions_ok? + end + + create_prim ids[nd.name], osm, nd + elem['new_id'] = osm.id.to_s + elem['new_version'] = osm.version.to_s + root << elem + end + when 'modify': + while p.read == 1 + break if p.node_type == 15 # end element + next unless p.node_type == 1 # element + + model = models[p.name] + next if model.nil? + + elem = XML::Node.new p.name + new_osm = model.from_xml_node(p.expand); p.next + osm = model.find(new_osm.id) + osm.update_from new_osm, @user + elem['old_id'] = elem['new_id'] = osm.id.to_s + elem['new_version'] = osm.version.to_s + root << elem + end + when 'delete': + while p.read == 1 + break if p.node_type == 15 # end element + next unless p.node_type == 1 # element + + model = models[p.name] + next if model.nil? + + elem = XML::Node.new p.name + osm = model.find(p.expand['id']); p.next + osm.delete_with_history(@user) + elem['old_id'] = elem['new_id'] = osm.id.to_s + elem['new_version'] = osm.version.to_s + root << elem + end + end + end + end + + render :text => res.to_s, :content_type => "text/xml" + + rescue OSM::APIError => ex + render ex.render_opts + end +end diff --git a/app/controllers/changeset_tag_controller.rb b/app/controllers/changeset_tag_controller.rb new file mode 100644 index 000000000..3e8db3fc2 --- /dev/null +++ b/app/controllers/changeset_tag_controller.rb @@ -0,0 +1,9 @@ +class ChangesetTagController < ApplicationController + layout 'site' + + def search + @tags = ChangesetTag.find(:all, :limit => 11, :conditions => ["match(v) against (?)", params[:query][:query].to_s] ) + end + + +end diff --git a/app/controllers/node_controller.rb b/app/controllers/node_controller.rb index edc3675e5..1e0deb140 100644 --- a/app/controllers/node_controller.rb +++ b/app/controllers/node_controller.rb @@ -15,6 +15,7 @@ class NodeController < ApplicationController node = Node.from_xml(request.raw_post, true) if node + node.version = 0 node.user_id = @user.id node.visible = true node.save_with_history! @@ -50,17 +51,14 @@ class NodeController < ApplicationController new_node = Node.from_xml(request.raw_post) if new_node and new_node.id == node.id - node.user_id = @user.id - node.latitude = new_node.latitude - node.longitude = new_node.longitude - node.tags = new_node.tags - node.visible = true - node.save_with_history! - - render :nothing => true + node.update_from(new_node, @user) + render :text => node.version.to_s, :content_type => "text/plain" else render :nothing => true, :status => :bad_request end + rescue OSM::APIVersionMismatchError => ex + render :text => "Version mismatch: Provided " + ex.provided.to_s + + ", server had: " + ex.latest.to_s, :status => :bad_request rescue ActiveRecord::RecordNotFound render :nothing => true, :status => :not_found end @@ -71,24 +69,11 @@ class NodeController < ApplicationController def delete begin node = Node.find(params[:id]) - - if node.visible - if WayNode.find(:first, :joins => "INNER JOIN current_ways ON current_ways.id = current_way_nodes.id", :conditions => [ "current_ways.visible = 1 AND current_way_nodes.node_id = ?", node.id ]) - render :text => "", :status => :precondition_failed - elsif RelationMember.find(:first, :joins => "INNER JOIN current_relations ON current_relations.id=current_relation_members.id", :conditions => [ "visible = 1 AND member_type='node' and member_id=?", params[:id]]) - render :text => "", :status => :precondition_failed - else - node.user_id = @user.id - node.visible = 0 - node.save_with_history! - - render :nothing => true - end - else - render :text => "", :status => :gone - end + node.delete_with_history(@user) rescue ActiveRecord::RecordNotFound render :nothing => true, :status => :not_found + rescue OSM::APIError => ex + render ex.render_opts end end diff --git a/app/controllers/relation_controller.rb b/app/controllers/relation_controller.rb index 2b1ba6c75..c49ecd4d7 100644 --- a/app/controllers/relation_controller.rb +++ b/app/controllers/relation_controller.rb @@ -15,6 +15,7 @@ class RelationController < ApplicationController if !relation.preconditions_ok? render :text => "", :status => :precondition_failed else + relation.version = 0 relation.user_id = @user.id relation.save_with_history! @@ -50,24 +51,15 @@ class RelationController < ApplicationController new_relation = Relation.from_xml(request.raw_post) if new_relation and new_relation.id == relation.id - if !new_relation.preconditions_ok? - render :text => "", :status => :precondition_failed - else - relation.user_id = @user.id - relation.tags = new_relation.tags - relation.members = new_relation.members - relation.visible = true - relation.save_with_history! - - render :nothing => true - end + relation.update_from new_relation, user + render :text => relation.version.to_s, :content_type => "text/plain" else render :nothing => true, :status => :bad_request end rescue ActiveRecord::RecordNotFound render :nothing => true, :status => :not_found - rescue - render :nothing => true, :status => :internal_server_error + rescue OSM::APIError => ex + render ex.render_opts end end @@ -75,26 +67,11 @@ class RelationController < ApplicationController #XXX check if member somewhere! begin relation = Relation.find(params[:id]) - - if relation.visible - if RelationMember.find(:first, :joins => "INNER JOIN current_relations ON current_relations.id=current_relation_members.id", :conditions => [ "visible = 1 AND member_type='relation' and member_id=?", params[:id]]) - render :text => "", :status => :precondition_failed - else - relation.user_id = @user.id - relation.tags = [] - relation.members = [] - relation.visible = false - relation.save_with_history! - - render :nothing => true - end - else - render :text => "", :status => :gone - end + relation.delete_with_history(@user) + rescue OSM::APIError => ex + render ex.render_opts rescue ActiveRecord::RecordNotFound render :nothing => true, :status => :not_found - rescue - render :nothing => true, :status => :internal_server_error end end diff --git a/app/controllers/way_controller.rb b/app/controllers/way_controller.rb index 3b6491cf0..cf1634fa5 100644 --- a/app/controllers/way_controller.rb +++ b/app/controllers/way_controller.rb @@ -15,6 +15,7 @@ class WayController < ApplicationController if !way.preconditions_ok? render :text => "", :status => :precondition_failed else + way.version = 0 way.user_id = @user.id way.save_with_history! @@ -39,6 +40,8 @@ class WayController < ApplicationController else render :text => "", :status => :gone end + rescue OSM::APIError => ex + render ex.render_opts rescue ActiveRecord::RecordNotFound render :nothing => true, :status => :not_found end @@ -50,20 +53,13 @@ class WayController < ApplicationController new_way = Way.from_xml(request.raw_post) if new_way and new_way.id == way.id - if !new_way.preconditions_ok? - render :text => "", :status => :precondition_failed - else - way.user_id = @user.id - way.tags = new_way.tags - way.nds = new_way.nds - way.visible = true - way.save_with_history! - - render :nothing => true - end + way.update_from(new_way, @user) + render :text => way.version.to_s, :content_type => "text/plain" else render :nothing => true, :status => :bad_request end + rescue OSM::APIError => ex + render ex.render_opts rescue ActiveRecord::RecordNotFound render :nothing => true, :status => :not_found end @@ -73,14 +69,12 @@ class WayController < ApplicationController def delete begin way = Way.find(params[:id]) - way.delete_with_relations_and_history(@user) + way.delete_with_history(@user) # if we get here, all is fine, otherwise something will catch below. render :nothing => true - rescue OSM::APIAlreadyDeletedError - render :text => "", :status => :gone - rescue OSM::APIPreconditionFailedError - render :text => "", :status => :precondition_failed + rescue OSM::APIError => ex + render ex.render_opts rescue ActiveRecord::RecordNotFound render :nothing => true, :status => :not_found end diff --git a/app/models/changeset.rb b/app/models/changeset.rb new file mode 100644 index 000000000..c9eeb0018 --- /dev/null +++ b/app/models/changeset.rb @@ -0,0 +1,111 @@ +class Changeset < ActiveRecord::Base + require 'xml/libxml' + + belongs_to :user + + has_many :changeset_tags, :foreign_key => 'id' + + def self.from_xml(xml, create=false) + begin + p = XML::Parser.new + p.string = xml + doc = p.parse + + cs = Changeset.new + + doc.find('//osm/changeset').each do |pt| + if create + cs.created_at = Time.now + end + + pt.find('tag').each do |tag| + cs.add_tag_keyval(tag['k'], tag['v']) + end + end + rescue Exception => ex + print "noes "+ ex.to_s + "\n" + cs = nil + end + + return cs + end + + def tags + unless @tags + @tags = {} + self.changeset_tags.each do |tag| + @tags[tag.k] = tag.v + end + end + @tags + end + + def tags=(t) + @tags = t + end + + def add_tag_keyval(k, v) + @tags = Hash.new unless @tags + @tags[k] = v + end + + def save_with_tags! + t = Time.now + + Changeset.transaction do + # fixme update modified_at time? + self.save! + end + + ChangesetTag.transaction do + tags = self.tags + ChangesetTag.delete_all(['id = ?', self.id]) + + tags.each do |k,v| + tag = ChangesetTag.new + tag.k = k + tag.v = v + tag.id = self.id + tag.save! + end + end + end + + def to_xml + doc = OSM::API.new.get_xml_doc + doc.root << to_xml_node() + return doc + end + def to_xml_node(user_display_name_cache = nil) + el1 = XML::Node.new 'changeset' + el1['id'] = self.id.to_s + + user_display_name_cache = {} if user_display_name_cache.nil? + + if user_display_name_cache and user_display_name_cache.key?(self.user_id) + # use the cache if available + elsif self.user.data_public? + user_display_name_cache[self.user_id] = self.user.display_name + else + user_display_name_cache[self.user_id] = nil + end + + el1['user'] = user_display_name_cache[self.user_id] unless user_display_name_cache[self.user_id].nil? + + self.tags.each do |k,v| + el2 = XML::Node.new('tag') + el2['k'] = k.to_s + el2['v'] = v.to_s + el1 << el2 + end + + el1['created_at'] = self.created_at.xmlschema + el1['open'] = self.open.to_s + + # FIXME FIXME FIXME: This does not include changes yet! There is + # currently no changeset_id column in the tables as far as I can tell, + # so this is just a scaffold to build on, not a complete to_xml + + return el1 + end +end diff --git a/app/models/changeset_tag.rb b/app/models/changeset_tag.rb new file mode 100644 index 000000000..6298fbe77 --- /dev/null +++ b/app/models/changeset_tag.rb @@ -0,0 +1,5 @@ +class ChangesetTag < ActiveRecord::Base + + belongs_to :changeset, :foreign_key => 'id' + +end diff --git a/app/models/node_tag.rb b/app/models/node_tag.rb new file mode 100644 index 000000000..9795ff493 --- /dev/null +++ b/app/models/node_tag.rb @@ -0,0 +1,5 @@ +class NodeTag < ActiveRecord::Base + set_table_name 'current_node_tags' + + belongs_to :node, :foreign_key => 'id' +end diff --git a/app/models/old_node_tag.rb b/app/models/old_node_tag.rb new file mode 100644 index 000000000..26a6c92b4 --- /dev/null +++ b/app/models/old_node_tag.rb @@ -0,0 +1,7 @@ +class OldNodeTag < ActiveRecord::Base + belongs_to :user + + set_table_name 'node_tags' + + +end diff --git a/app/models/old_relation.rb b/app/models/old_relation.rb index bac03c4d2..f5885f39f 100644 --- a/app/models/old_relation.rb +++ b/app/models/old_relation.rb @@ -9,6 +9,7 @@ class OldRelation < ActiveRecord::Base old_relation.user_id = relation.user_id old_relation.timestamp = relation.timestamp old_relation.id = relation.id + old_relation.version = relation.version old_relation.members = relation.members old_relation.tags = relation.tags return old_relation @@ -91,6 +92,7 @@ class OldRelation < ActiveRecord::Base el1['visible'] = self.visible.to_s el1['timestamp'] = self.timestamp.xmlschema el1['user'] = self.user.display_name if self.user.data_public? + el1['version'] = self.version.to_s self.old_members.each do |member| e = XML::Node.new 'member' diff --git a/app/models/old_way.rb b/app/models/old_way.rb index 1abb23bbb..edf66aac3 100644 --- a/app/models/old_way.rb +++ b/app/models/old_way.rb @@ -9,6 +9,7 @@ class OldWay < ActiveRecord::Base old_way.user_id = way.user_id old_way.timestamp = way.timestamp old_way.id = way.id + old_way.version = way.version old_way.nds = way.nds old_way.tags = way.tags return old_way @@ -94,6 +95,7 @@ class OldWay < ActiveRecord::Base el1['visible'] = self.visible.to_s el1['timestamp'] = self.timestamp.xmlschema el1['user'] = self.user.display_name if self.user.data_public? + el1['version'] = self.version.to_s self.old_nodes.each do |nd| # FIXME need to make sure they come back in the right order e = XML::Node.new 'nd' diff --git a/app/models/relation.rb b/app/models/relation.rb index 9ee118f6e..eb3b06a13 100644 --- a/app/models/relation.rb +++ b/app/models/relation.rb @@ -19,32 +19,38 @@ class Relation < ActiveRecord::Base p.string = xml doc = p.parse - relation = Relation.new - doc.find('//osm/relation').each do |pt| - if !create and pt['id'] != '0' - relation.id = pt['id'].to_i - end + return Relation.from_xml_node(pt, create) + end + rescue + return nil + end + end - if create - relation.timestamp = Time.now - relation.visible = true - else - if pt['timestamp'] - relation.timestamp = Time.parse(pt['timestamp']) - end - end + def self.from_xml_node(pt, create=false) + relation = Relation.new - pt.find('tag').each do |tag| - relation.add_tag_keyval(tag['k'], tag['v']) - end + if !create and pt['id'] != '0' + relation.id = pt['id'].to_i + end - pt.find('member').each do |member| - relation.add_member(member['type'], member['ref'], member['role']) - end + relation.version = pt['version'] + + if create + relation.timestamp = Time.now + relation.visible = true + else + if pt['timestamp'] + relation.timestamp = Time.parse(pt['timestamp']) end - rescue - relation = nil + end + + pt.find('tag').each do |tag| + relation.add_tag_keyval(tag['k'], tag['v']) + end + + pt.find('member').each do |member| + relation.add_member(member['type'], member['ref'], member['role']) end return relation @@ -61,6 +67,7 @@ class Relation < ActiveRecord::Base el1['id'] = self.id.to_s el1['visible'] = self.visible.to_s el1['timestamp'] = self.timestamp.xmlschema + el1['version'] = self.version.to_s user_display_name_cache = {} if user_display_name_cache.nil? @@ -147,13 +154,12 @@ class Relation < ActiveRecord::Base def save_with_history! Relation.transaction do t = Time.now + self.version += 1 self.timestamp = t self.save! tags = self.tags - RelationTag.delete_all(['id = ?', self.id]) - tags.each do |k,v| tag = RelationTag.new tag.k = k @@ -163,9 +169,7 @@ class Relation < ActiveRecord::Base end members = self.members - RelationMember.delete_all(['id = ?', self.id]) - members.each do |n| mem = RelationMember.new mem.id = self.id @@ -181,6 +185,36 @@ class Relation < ActiveRecord::Base end end + def delete_with_history(user) + if self.visible + if RelationMember.find(:first, :joins => "INNER JOIN current_relations ON current_relations.id=current_relation_members.id", :conditions => [ "visible = 1 AND member_type='relation' and member_id=?", self.id ]) + raise OSM::APIPreconditionFailedError.new + else + self.user_id = user.id + self.tags = [] + self.members = [] + self.visible = false + save_with_history! + end + else + raise OSM::APIAlreadyDeletedError.new + end + end + + def update_from(new_relation, user) + if !new_relation.preconditions_ok? + raise OSM::APIPreconditionFailedError.new + elsif new_relation.version != version + raise OSM::APIVersionMismatchError.new(new_relation.version, version) + else + self.user_id = user.id + self.tags = new_relation.tags + self.members = new_relation.members + self.visible = true + save_with_history! + end + end + def preconditions_ok? # These are hastables that store an id in the index of all # the nodes/way/relations that have already been added. diff --git a/app/models/way.rb b/app/models/way.rb index 64b11cf67..34afc6585 100644 --- a/app/models/way.rb +++ b/app/models/way.rb @@ -21,32 +21,38 @@ class Way < ActiveRecord::Base p.string = xml doc = p.parse - way = Way.new - doc.find('//osm/way').each do |pt| - if !create and pt['id'] != '0' - way.id = pt['id'].to_i - end + return Way.from_xml_node(pt, create) + end + rescue + return nil + end + end - if create - way.timestamp = Time.now - way.visible = true - else - if pt['timestamp'] - way.timestamp = Time.parse(pt['timestamp']) - end - end + def self.from_xml_node(pt, create=false) + way = Way.new - pt.find('tag').each do |tag| - way.add_tag_keyval(tag['k'], tag['v']) - end + if !create and pt['id'] != '0' + way.id = pt['id'].to_i + end + + way.version = pt['version'] - pt.find('nd').each do |nd| - way.add_nd_num(nd['ref']) - end + if create + way.timestamp = Time.now + way.visible = true + else + if pt['timestamp'] + way.timestamp = Time.parse(pt['timestamp']) end - rescue - way = nil + end + + pt.find('tag').each do |tag| + way.add_tag_keyval(tag['k'], tag['v']) + end + + pt.find('nd').each do |nd| + way.add_nd_num(nd['ref']) end return way @@ -74,6 +80,7 @@ class Way < ActiveRecord::Base el1['id'] = self.id.to_s el1['visible'] = self.visible.to_s el1['timestamp'] = self.timestamp.xmlschema + el1['version'] = self.version.to_s user_display_name_cache = {} if user_display_name_cache.nil? @@ -162,15 +169,12 @@ class Way < ActiveRecord::Base t = Time.now Way.transaction do + self.version += 1 self.timestamp = t self.save! - end - WayTag.transaction do tags = self.tags - WayTag.delete_all(['id = ?', self.id]) - tags.each do |k,v| tag = WayTag.new tag.k = k @@ -178,13 +182,9 @@ class Way < ActiveRecord::Base tag.id = self.id tag.save! end - end - WayNode.transaction do nds = self.nds - WayNode.delete_all(['id = ?', self.id]) - sequence = 1 nds.each do |n| nd = WayNode.new @@ -193,11 +193,25 @@ class Way < ActiveRecord::Base nd.save! sequence += 1 end + + old_way = OldWay.from_way(self) + old_way.timestamp = t + old_way.save_with_dependencies! end + end - old_way = OldWay.from_way(self) - old_way.timestamp = t - old_way.save_with_dependencies! + def update_from(new_way, user) + if !new_way.preconditions_ok? + raise OSM::APIPreconditionFailedError.new + elsif new_way.version != version + raise OSM::APIVersionMismatchError.new(new_way.version, version) + else + self.user_id = user.id + self.tags = new_way.tags + self.nds = new_way.nds + self.visible = true + save_with_history! + end end def preconditions_ok? @@ -211,12 +225,13 @@ class Way < ActiveRecord::Base return true end - # Delete the way and it's relations, but don't really delete it - set its visibility to false and update the history etc to maintain wiki-like functionality. - def delete_with_relations_and_history(user) + def delete_with_history(user) if self.visible # FIXME # this should actually delete the relations, # not just throw a PreconditionFailed if it's a member of a relation!! + + # FIXME: this should probably renamed to delete_with_history if RelationMember.find(:first, :joins => "INNER JOIN current_relations ON current_relations.id=current_relation_members.id", :conditions => [ "visible = 1 AND member_type='way' and member_id=?", self.id]) raise OSM::APIPreconditionFailedError @@ -234,6 +249,8 @@ class Way < ActiveRecord::Base end # delete a way and it's nodes that aren't part of other ways, with history + + # FIXME: merge the potlatch code to delete the relations def delete_with_relations_and_nodes_and_history(user) node_ids = self.nodes.collect {|node| node.id } @@ -254,7 +271,7 @@ class Way < ActiveRecord::Base self.user_id = user.id - self.delete_with_relations_and_history(user) + self.delete_with_history(user) end diff --git a/config/database.yml b/config/database.yml index b884f3b93..fe47e11aa 100644 --- a/config/database.yml +++ b/config/database.yml @@ -23,14 +23,14 @@ development: test: adapter: mysql database: osm_test - username: root - password: + username: osm_test + password: osm_test host: localhost production: adapter: mysql - database: openstreetmap - username: openstreetmap - password: openstreetmap - host: db.openstreetmap.org + database: osm + username: osm + password: osm + host: localhost diff --git a/config/environment.rb b/config/environment.rb index 495f94d80..fb7573d2a 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -11,7 +11,7 @@ RAILS_GEM_VERSION = '2.0.2' unless defined? RAILS_GEM_VERSION SERVER_URL = ENV['OSM_SERVER_URL'] || 'www.openstreetmap.org' # Application constants needed for routes.rb - must go before Initializer call -API_VERSION = ENV['OSM_API_VERSION'] || '0.5' +API_VERSION = ENV['OSM_API_VERSION'] || '0.6' # Set application status - possible settings are: # diff --git a/config/routes.rb b/config/routes.rb index 854e7f003..592178474 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,11 @@ ActionController::Routing::Routes.draw do |map| # API + map.connect "api/#{API_VERSION}/changeset/create", :controller => 'changeset', :action => 'create' + map.connect "api/#{API_VERSION}/changeset/upload", :controller => 'changeset', :action => 'upload' + map.connect "api/#{API_VERSION}/changeset/:id", :controller => 'changeset', :action => 'read', :id => /\d+/ + map.connect "api/#{API_VERSION}/changeset/:id/close", :controller => 'changeset', :action => 'close', :id =>/\d+/ + map.connect "api/#{API_VERSION}/node/create", :controller => 'node', :action => 'create' map.connect "api/#{API_VERSION}/node/:id/ways", :controller => 'way', :action => 'ways_for_node', :id => /\d+/ map.connect "api/#{API_VERSION}/node/:id/relations", :controller => 'relation', :action => 'relations_for_node', :id => /\d+/ @@ -54,6 +59,7 @@ ActionController::Routing::Routes.draw do |map| # Potlatch API + map.connect "api/0.5/amf", :controller =>'amf', :action =>'talk' map.connect "api/#{API_VERSION}/amf", :controller =>'amf', :action =>'talk' map.connect "api/#{API_VERSION}/swf/trackpoints", :controller =>'swf', :action =>'trackpoints' diff --git a/db/migrate/012_add_timestamp_indexes.rb b/db/migrate/012_add_timestamp_indexes.rb new file mode 100644 index 000000000..c6b3bc7c2 --- /dev/null +++ b/db/migrate/012_add_timestamp_indexes.rb @@ -0,0 +1,11 @@ +class AddTimestampIndexes < ActiveRecord::Migration + def self.up + add_index :current_ways, :timestamp, :name => :current_ways_timestamp_idx + add_index :current_relations, :timestamp, :name => :current_relations_timestamp_idx + end + + def self.down + remove_index :current_ways, :name => :current_ways_timestamp_idx + remove_index :current_relations, :name => :current_relations_timestamp_idx + end +end diff --git a/db/migrate/013_populate_node_tags_and_remove.rb b/db/migrate/013_populate_node_tags_and_remove.rb new file mode 100644 index 000000000..29a91c70b --- /dev/null +++ b/db/migrate/013_populate_node_tags_and_remove.rb @@ -0,0 +1,62 @@ +class PopulateNodeTagsAndRemove < ActiveRecord::Migration + def self.up + have_nodes = select_value("SELECT count(*) FROM current_nodes").to_i != 0 + + if have_nodes + prefix = File.join Dir.tmpdir, "013_populate_node_tags_and_remove.#{$$}." + + cmd = "db/migrate/013_populate_node_tags_and_remove_helper" + src = "#{cmd}.c" + if not File.exists? cmd or File.mtime(cmd) < File.mtime(src) then + system 'cc -O3 -Wall `mysql_config --cflags --libs` ' + + "#{src} -o #{cmd}" or fail + end + + conn_opts = ActiveRecord::Base.connection. + instance_eval { @connection_options } + args = conn_opts.map { |arg| arg.to_s } + [prefix] + fail "#{cmd} failed" unless system cmd, *args + + tempfiles = ['nodes', 'node_tags', + 'current_nodes', 'current_node_tags']. + map { |base| prefix + base } + nodes, node_tags, current_nodes, current_node_tags = tempfiles + end + + execute "TRUNCATE nodes" + remove_column :nodes, :tags + remove_column :current_nodes, :tags + + add_column :nodes, :version, :bigint, :limit => 20, :null => false + + create_table :current_node_tags, innodb_table do |t| + t.column :id, :bigint, :limit => 64, :null => false + t.column :k, :string, :default => "", :null => false + t.column :v, :string, :default => "", :null => false + end + + create_table :node_tags, innodb_table do |t| + t.column :id, :bigint, :limit => 64, :null => false + t.column :version, :bigint, :limit => 20, :null => false + t.column :k, :string, :default => "", :null => false + t.column :v, :string, :default => "", :null => false + end + + # now get the data back + csvopts = "FIELDS TERMINATED BY ',' ENCLOSED BY '\"' ESCAPED BY '\"' LINES TERMINATED BY '\\n'" + + if have_nodes + execute "LOAD DATA INFILE '#{nodes}' INTO TABLE nodes #{csvopts} (id, latitude, longitude, user_id, visible, timestamp, tile, version)"; + execute "LOAD DATA INFILE '#{node_tags}' INTO TABLE node_tags #{csvopts} (id, version, k, v)" + execute "LOAD DATA INFILE '#{current_node_tags}' INTO TABLE current_node_tags #{csvopts} (id, k, v)" + end + + tempfiles.each { |fn| File.unlink fn } if have_nodes + end + + def self.down + raise IrreversibleMigration.new +# add_column :nodes, "tags", :text, :default => "", :null => false +# add_column :current_nodes, "tags", :text, :default => "", :null => false + end +end diff --git a/db/migrate/013_populate_node_tags_and_remove_helper.c b/db/migrate/013_populate_node_tags_and_remove_helper.c new file mode 100644 index 000000000..5a0fbb6cd --- /dev/null +++ b/db/migrate/013_populate_node_tags_and_remove_helper.c @@ -0,0 +1,241 @@ +#include +#include +#include +#include +#include + +static void exit_mysql_err(MYSQL *mysql) { + const char *err = mysql_error(mysql); + if (err) { + fprintf(stderr, "013_populate_node_tags_and_remove_helper: MySQL error: %s\n", err); + } else { + fprintf(stderr, "013_populate_node_tags_and_remove_helper: MySQL error\n"); + } + abort(); + exit(EXIT_FAILURE); +} + +static void write_csv_col(FILE *f, const char *str, char end) { + char *out = (char *) malloc(2 * strlen(str) + 4); + char *o = out; + size_t len; + + *(o++) = '\"'; + for (; *str; str++) { + if (*str == '\0') { + break; + } else if (*str == '\"') { + *(o++) = '\"'; + *(o++) = '\"'; + } else { + *(o++) = *str; + } + } + *(o++) = '\"'; + *(o++) = end; + *(o++) = '\0'; + + len = strlen(out); + if (fwrite(out, len, 1, f) != 1) { + perror("fwrite"); + exit(EXIT_FAILURE); + } + + free(out); +} + +static void unescape(char *str) { + char *i = str, *o = str, tmp; + + while (*i) { + if (*i == '\\') { + i++; + switch (tmp = *i++) { + case 's': *o++ = ';'; break; + case 'e': *o++ = '='; break; + case '\\': *o++ = '\\'; break; + default: *o++ = tmp; break; + } + } else { + *o++ = *i++; + } + } +} + +static int read_node_tags(char **tags, char **k, char **v) { + if (!**tags) return 0; + char *i = strchr(*tags, ';'); + if (!i) i = *tags + strlen(*tags); + char *j = strchr(*tags, '='); + *k = *tags; + if (j && j < i) { + *v = j + 1; + } else { + *v = i; + } + *tags = *i ? i + 1 : i; + *i = '\0'; + if (j) *j = '\0'; + + unescape(*k); + unescape(*v); + + return 1; +} + +struct data { + MYSQL *mysql; + size_t version_size; + uint16_t *version; +}; + +static void proc_nodes(struct data *d, const char *tbl, FILE *out, FILE *out_tags, int hist) { + MYSQL_RES *res; + MYSQL_ROW row; + char query[256]; + + snprintf(query, sizeof(query), "SELECT id, latitude, longitude, " + "user_id, visible, tags, timestamp, tile FROM %s", tbl); + if (mysql_query(d->mysql, query)) + exit_mysql_err(d->mysql); + + res = mysql_use_result(d->mysql); + if (!res) exit_mysql_err(d->mysql); + + while ((row = mysql_fetch_row(res))) { + unsigned long id = strtoul(row[0], NULL, 10); + uint32_t version; + + if (id >= d->version_size) { + fprintf(stderr, "preallocated nodes size exceeded"); + abort(); + } + + if (hist) { + version = ++(d->version[id]); + + fprintf(out, "\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%u\"\n", + row[0], row[1], row[2], row[3], row[4], row[6], row[7], version); + } else { + /*fprintf(out, "\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"\n", + row[0], row[1], row[2], row[3], row[4], row[6], row[7]);*/ + } + + char *tags_it = row[5], *k, *v; + while (read_node_tags(&tags_it, &k, &v)) { + if (hist) { + fprintf(out_tags, "\"%s\",\"%u\",", row[0], version); + } else { + fprintf(out_tags, "\"%s\",", row[0]); + } + + write_csv_col(out_tags, k, ','); + write_csv_col(out_tags, v, '\n'); + } + } + if (mysql_errno(d->mysql)) exit_mysql_err(d->mysql); + + mysql_free_result(res); +} + +static size_t select_size(MYSQL *mysql, const char *q) { + MYSQL_RES *res; + MYSQL_ROW row; + size_t ret; + + if (mysql_query(mysql, q)) + exit_mysql_err(mysql); + + res = mysql_store_result(mysql); + if (!res) exit_mysql_err(mysql); + + row = mysql_fetch_row(res); + if (!row) exit_mysql_err(mysql); + + if (row[0]) { + ret = strtoul(row[0], NULL, 10); + } else { + ret = 0; + } + + mysql_free_result(res); + + return ret; +} + +static MYSQL *connect_to_mysql(char **argv) { + MYSQL *mysql = mysql_init(NULL); + if (!mysql) exit_mysql_err(mysql); + + if (!mysql_real_connect(mysql, argv[1], argv[2], argv[3], argv[4], + argv[5][0] ? atoi(argv[5]) : 0, argv[6][0] ? argv[6] : NULL, 0)) + exit_mysql_err(mysql); + + if (mysql_set_character_set(mysql, "utf8")) + exit_mysql_err(mysql); + + return mysql; +} + +static void open_file(FILE **f, char *fn) { + *f = fopen(fn, "w+"); + if (!*f) { + perror("fopen"); + exit(EXIT_FAILURE); + } +} + +int main(int argc, char **argv) { + size_t prefix_len; + FILE *current_nodes, *current_node_tags, *nodes, *node_tags; + char *tempfn; + struct data data, *d = &data; + + if (argc != 8) { + printf("Usage: 013_populate_node_tags_and_remove_helper host user passwd database port socket prefix\n"); + exit(EXIT_FAILURE); + } + + d->mysql = connect_to_mysql(argv); + + d->version_size = 1 + select_size(d->mysql, "SELECT max(id) FROM current_nodes"); + d->version = (uint16_t *) malloc(sizeof(uint16_t) * d->version_size); + if (!d->version) { + perror("malloc"); + abort(); + exit(EXIT_FAILURE); + } + memset(d->version, 0, sizeof(uint16_t) * d->version_size); + + prefix_len = strlen(argv[7]); + tempfn = (char *) malloc(prefix_len + 32); + strcpy(tempfn, argv[7]); + + strcpy(tempfn + prefix_len, "current_nodes"); + open_file(¤t_nodes, tempfn); + + strcpy(tempfn + prefix_len, "current_node_tags"); + open_file(¤t_node_tags, tempfn); + + strcpy(tempfn + prefix_len, "nodes"); + open_file(&nodes, tempfn); + + strcpy(tempfn + prefix_len, "node_tags"); + open_file(&node_tags, tempfn); + + free(tempfn); + + proc_nodes(d, "nodes", nodes, node_tags, 1); + proc_nodes(d, "current_nodes", current_nodes, current_node_tags, 0); + + free(d->version); + + mysql_close(d->mysql); + + fclose(current_nodes); + fclose(current_node_tags); + fclose(nodes); + fclose(node_tags); + + exit(EXIT_SUCCESS); +} diff --git a/db/migrate/014_move_to_innodb.rb b/db/migrate/014_move_to_innodb.rb new file mode 100644 index 000000000..c551b0ef8 --- /dev/null +++ b/db/migrate/014_move_to_innodb.rb @@ -0,0 +1,30 @@ +class MoveToInnodb < ActiveRecord::Migration + @@conv_tables = ['nodes', 'ways', 'way_tags', 'way_nodes', + 'current_way_tags', 'relation_members', + 'relations', 'relation_tags', 'current_relation_tags'] + + @@ver_tbl = ['nodes', 'ways', 'relations'] + + def self.up + execute 'DROP INDEX current_way_tags_v_idx ON current_way_tags' + execute 'DROP INDEX current_relation_tags_v_idx ON current_relation_tags' + + @@ver_tbl.each { |tbl| + change_column tbl, "version", :bigint, :limit => 20, :null => false + } + + @@conv_tables.each { |tbl| + execute "ALTER TABLE #{tbl} ENGINE = InnoDB" + } + + @@ver_tbl.each { |tbl| + add_column "current_#{tbl}", "version", :bigint, :limit => 20, :null => false + execute "UPDATE current_#{tbl} SET version = " + + "(SELECT max(version) FROM #{tbl} WHERE #{tbl}.id = current_#{tbl}.id)" + } + end + + def self.down + raise IrreversibleMigration.new + end +end diff --git a/db/migrate/015_key_constraints.rb b/db/migrate/015_key_constraints.rb new file mode 100644 index 000000000..40f98be02 --- /dev/null +++ b/db/migrate/015_key_constraints.rb @@ -0,0 +1,50 @@ +class KeyConstraints < ActiveRecord::Migration + def self.up + # Primary keys + add_primary_key :current_node_tags, [:id, :k] + add_primary_key :current_way_tags, [:id, :k] + add_primary_key :current_relation_tags, [:id, :k] + + add_primary_key :node_tags, [:id, :version, :k] + add_primary_key :way_tags, [:id, :version, :k] + add_primary_key :relation_tags, [:id, :version, :k] + + add_primary_key :nodes, [:id, :version] + + # Remove indexes superseded by primary keys + remove_index :current_way_tags, :name => :current_way_tags_id_idx + remove_index :current_relation_tags, :name => :current_relation_tags_id_idx + + remove_index :way_tags, :name => :way_tags_id_version_idx + remove_index :relation_tags, :name => :relation_tags_id_version_idx + + remove_index :nodes, :name => :nodes_uid_idx + + # Foreign keys (between ways, way_tags, way_nodes, etc.) + add_foreign_key :current_node_tags, [:id], :current_nodes + add_foreign_key :node_tags, [:id, :version], :nodes + + add_foreign_key :current_way_tags, [:id], :current_ways + add_foreign_key :current_way_nodes, [:id], :current_ways + add_foreign_key :way_tags, [:id, :version], :ways + add_foreign_key :way_nodes, [:id, :version], :ways + + add_foreign_key :current_relation_tags, [:id], :current_relations + add_foreign_key :current_relation_members, [:id], :current_relations + add_foreign_key :relation_tags, [:id, :version], :relations + add_foreign_key :relation_members, [:id, :version], :relations + + # Foreign keys (between different types of primitives) + add_foreign_key :current_way_nodes, [:node_id], :current_nodes, [:id] + + # FIXME: We don't have foreign keys for relation members since the id + # might point to a different table depending on the `type' column. + # We'd probably need different current_relation_member_nodes, + # current_relation_member_ways and current_relation_member_relations + # tables for this to work cleanly. + end + + def self.down + raise IrreversibleMigration.new + end +end diff --git a/db/migrate/016_add_changesets.rb b/db/migrate/016_add_changesets.rb new file mode 100644 index 000000000..40455ec68 --- /dev/null +++ b/db/migrate/016_add_changesets.rb @@ -0,0 +1,32 @@ +class AddChangesets < ActiveRecord::Migration + def self.up + create_table "changesets", innodb_table do |t| + t.column "id", :bigint, :limit => 20, :null => false + t.column "user_id", :bigint, :limit => 20, :null => false + t.column "created_at", :datetime, :null => false + t.column "open", :boolean, :null => false, :default => true + t.column "min_lat", :integer, :null => true + t.column "max_lat", :integer, :null => true + t.column "min_lon", :integer, :null => true + t.column "max_lon", :integer, :null => true + end + + add_primary_key "changesets", ["id"] + # FIXME add indexes? + + change_column "changesets", "id", :bigint, :limit => 20, :null => false, :options => "AUTO_INCREMENT" + + create_table "changeset_tags", innodb_table do |t| + t.column "id", :bigint, :limit => 64, :null => false + t.column "k", :string, :default => "", :null => false + t.column "v", :string, :default => "", :null => false + end + + add_index "changeset_tags", ["id"], :name => "changeset_tags_id_idx" + end + + def self.down + drop_table "changesets" + drop_table "changeset_tags" + end +end diff --git a/lib/migrate.rb b/lib/migrate.rb index 1d32d175d..26e95a496 100644 --- a/lib/migrate.rb +++ b/lib/migrate.rb @@ -1,6 +1,10 @@ module ActiveRecord module ConnectionAdapters module SchemaStatements + def quote_column_names(column_name) + Array(column_name).map { |e| quote_column_name(e) }.join(", ") + end + def add_primary_key(table_name, column_name, options = {}) column_names = Array(column_name) quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ") @@ -11,6 +15,12 @@ module ActiveRecord execute "ALTER TABLE #{table_name} DROP PRIMARY KEY" end + def add_foreign_key(table_name, column_name, reftbl, refcol = nil) + execute "ALTER TABLE #{table_name} ADD " + + "FOREIGN KEY (#{quote_column_names(column_name)}) " + + "REFERENCES #{reftbl} (#{quote_column_names(refcol || column_name)})" + end + alias_method :old_options_include_default?, :options_include_default? def options_include_default?(options) diff --git a/lib/osm.rb b/lib/osm.rb index bd9351026..c038ab2d5 100644 --- a/lib/osm.rb +++ b/lib/osm.rb @@ -10,6 +10,9 @@ module OSM # The base class for API Errors. class APIError < RuntimeError + def render_opts + { :text => "", :status => :internal_server_error } + end end # Raised when an API object is not found. @@ -18,10 +21,30 @@ module OSM # Raised when a precondition to an API action fails sanity check. class APIPreconditionFailedError < APIError + def render_opts + { :text => "", :status => :precondition_failed } + end end # Raised when to delete an already-deleted object. class APIAlreadyDeletedError < APIError + def render_opts + { :text => "", :status => :gone } + end + end + + # Raised when the provided version is not equal to the latest in the db. + class APIVersionMismatchError < APIError + def initialize(provided, latest) + @provided, @latest = provided, latest + end + + attr_reader :provided, :latest + + def render_opts + { :text => "Version mismatch: Provided " + provided.to_s + + ", server had: " + latest.to_s, :status => :bad_request } + end end # Helper methods for going to/from mercator and lat/lng. diff --git a/lib/tasks/populate_node_tags.rake b/lib/tasks/populate_node_tags.rake deleted file mode 100644 index 86747cfe4..000000000 --- a/lib/tasks/populate_node_tags.rake +++ /dev/null @@ -1,42 +0,0 @@ -namespace 'db' do - desc 'Populate the node_tags table' - task :node_tags do - require File.dirname(__FILE__) + '/../../config/environment' - - node_count = Node.count - limit = 1000 #the number of nodes to grab in one go - offset = 0 - - while offset < node_count - Node.find(:all, :limit => limit, :offset => offset).each do |node| - seq_id = 1 - node.tags.split(';').each do |tag| - nt = NodeTag.new - nt.id = node.id - nt.k = tag.split('=')[0] || '' - nt.v = tag.split('=')[1] || '' - nt.sequence_id = seq_id - nt.save! || raise - seq_id += 1 - end - - version = 1 #version refers to one set of histories - node.old_nodes.find(:all, :order => 'timestamp asc').each do |old_node| - sequence_id = 1 #sequence_id refers to the sequence of node tags within a history - old_node.tags.split(';').each do |tag| - ont = OldNodeTag.new - ont.id = node.id #the id of the node tag - ont.k = tag.split('=')[0] || '' - ont.v = tag.split('=')[1] || '' - ont.version = version - ont.sequence_id = sequence_id - ont.save! || raise - sequence_id += 1 - end - version += 1 - end - end - offset += limit - end - end -end diff --git a/test/fixtures/current_node_tags.yml b/test/fixtures/current_node_tags.yml new file mode 100644 index 000000000..d9f5448a4 --- /dev/null +++ b/test/fixtures/current_node_tags.yml @@ -0,0 +1,15 @@ +t1: + id: visible_node.id + k: testvisible + v: yes + +t2: + id: used_node_1.id + k: testused + v: yes + +t3: + id: used_node_2.id + k: test + v: yes + diff --git a/test/fixtures/current_nodes.yml b/test/fixtures/current_nodes.yml index dd3bd2487..8fd3b781f 100644 --- a/test/fixtures/current_nodes.yml +++ b/test/fixtures/current_nodes.yml @@ -1,11 +1,11 @@ # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +<% SCALE = 10000000 unless defined?(SCALE) %> visible_node: id: 1 - latitude: 1 - longitude: 1 + latitude: <%= 1*SCALE %> + longitude: <%= 1*SCALE %> user_id: 1 visible: 1 - tags: test=yes timestamp: 2007-01-01 00:00:00 invisible_node: @@ -14,7 +14,6 @@ invisible_node: longitude: 2 user_id: 1 visible: 0 - tags: test=yes timestamp: 2007-01-01 00:00:00 used_node_1: @@ -23,7 +22,6 @@ used_node_1: longitude: 3 user_id: 1 visible: 1 - tags: test=yes timestamp: 2007-01-01 00:00:00 used_node_2: @@ -32,7 +30,6 @@ used_node_2: longitude: 4 user_id: 1 visible: 1 - tags: test=yes timestamp: 2007-01-01 00:00:00 node_used_by_relationship: @@ -41,5 +38,40 @@ node_used_by_relationship: longitude: 5 user_id: 1 visible: 1 - tags: test=yes timestamp: 2007-01-01 00:00:00 + +node_too_far_north: + id: 6 + latitude: <%= 91*SCALE %> + longitude: <%= 6*SCALE %> + user_id: 1 + timestamp: 2007-01-01 00:00:00 + +node_too_far_south: + id: 7 + latitude: -90 + longitude: 7 + user_id: 1 + timestamp: 2007-01-01 00:00:00 + +node_too_far_west: + id: 8 + latitude: 8 + longitude: -181 + user_id: 1 + timestamp: 2007-01-01 00:00:00 + +node_too_far_east: + id: 9 + latitude: 9 + longitude: 180 + user_id: 1 + timestamp: 2007-01-01 00:00:00 + +node_totally_wrong: + id: 10 + latitude: 1000 + longitude: 1000 + user_id: 1 + timestamp: 2007-01-01 00:00:00 + diff --git a/test/fixtures/messages.yml b/test/fixtures/messages.yml index b49c4eb4e..feab6536e 100644 --- a/test/fixtures/messages.yml +++ b/test/fixtures/messages.yml @@ -1,5 +1,16 @@ # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html one: - id: 1 + sender: normal_user + title: test message 1 + body: some body text + sent_on: "2008-05-01 12:34:56" + message_read: false + recipient: second_user + two: - id: 2 + sender: second_user + title: test message 2 + body: some body test + sent_on: "2008-05-02 12:45:23" + message_read: true + recipient: normal_user diff --git a/test/fixtures/node_tags.yml b/test/fixtures/node_tags.yml new file mode 100644 index 000000000..c32dc6c55 --- /dev/null +++ b/test/fixtures/node_tags.yml @@ -0,0 +1,17 @@ +t1: + id: visible_node + k: testvisible + v: yes + version: 1 + +t2: + id: used_node_1 + k: testused + v: yes + version: 1 + +t3: + id: used_node_2 + k: test + v: yes + version: 1 diff --git a/test/fixtures/nodes.yml b/test/fixtures/nodes.yml index 37152c4d3..b10ce2fe7 100644 --- a/test/fixtures/nodes.yml +++ b/test/fixtures/nodes.yml @@ -5,7 +5,6 @@ visible_node: longitude: 1 user_id: 1 visible: 1 - tags: test=yes timestamp: 2007-01-01 00:00:00 invisible_node: @@ -14,7 +13,6 @@ invisible_node: longitude: 2 user_id: 1 visible: 0 - tags: test=yes timestamp: 2007-01-01 00:00:00 used_node_1: @@ -23,7 +21,6 @@ used_node_1: longitude: 3 user_id: 1 visible: 1 - tags: test=yes timestamp: 2007-01-01 00:00:00 used_node_2: @@ -32,7 +29,6 @@ used_node_2: longitude: 4 user_id: 1 visible: 1 - tags: test=yes timestamp: 2007-01-01 00:00:00 node_used_by_relationship: @@ -41,6 +37,5 @@ node_used_by_relationship: longitude: 5 user_id: 1 visible: 1 - tags: test=yes timestamp: 2007-01-01 00:00:00 diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml index bcce2f7db..89522ef7c 100644 --- a/test/fixtures/users.yml +++ b/test/fixtures/users.yml @@ -1,7 +1,6 @@ # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html normal_user: email: test@openstreetmap.org - id: 1 active: 1 pass_crypt: <%= Digest::MD5.hexdigest('test') %> creation_time: "2007-01-01 00:00:00" @@ -11,3 +10,26 @@ normal_user: home_lat: 1 home_lon: 1 home_zoom: 3 + +second_user: + email: test@example.com + active: 1 + pass_crypt: <%= Digest::MD5.hexdigest('test') %> + creation_time: "2008-05-01 01:23:45" + display_name: test2 + data_public: 1 + description: some test description + home_lat: 12 + home_lon: 12 + home_zoom: 12 + +inactive_user: + email: inactive@openstreetmap.org + active: 0 + pass_crypt: <%= Digest::MD5::hexdigest('test2') %> + display_name: Inactive User + data_public: 1 + description: description + home_lat: 12.34 + home_lon: 12.34 + home_zoom: 15 diff --git a/test/unit/message_test.rb b/test/unit/message_test.rb index 8804fe003..b56972704 100644 --- a/test/unit/message_test.rb +++ b/test/unit/message_test.rb @@ -1,10 +1,15 @@ require File.dirname(__FILE__) + '/../test_helper' class MessageTest < Test::Unit::TestCase - fixtures :messages + fixtures :messages, :users - # Replace this with your real tests. - def test_truth - assert true + def test_check_empty_message_fails + message = Message.new + assert !message.valid? + assert message.errors.invalid?(:title) + assert message.errors.invalid?(:body) + assert message.errors.invalid?(:sent_on) + assert true, message.message_read end + end diff --git a/test/unit/node_test.rb b/test/unit/node_test.rb index 95321b5cf..d56fed50a 100644 --- a/test/unit/node_test.rb +++ b/test/unit/node_test.rb @@ -1,16 +1,44 @@ require File.dirname(__FILE__) + '/../test_helper' class NodeTest < Test::Unit::TestCase - fixtures :current_nodes, :nodes, :users + fixtures :current_nodes, :users, :current_node_tags,:nodes, :node_tags set_fixture_class :current_nodes => :Node set_fixture_class :nodes => :OldNode - + set_fixture_class :node_tags => :OldNodeTag + set_fixture_class :currenr_node_tags => :NodeTag + + def test_node_too_far_north + noden = current_nodes(:node_too_far_north) + assert_equal noden.lat, current_nodes(:node_too_far_north).latitude/SCALE + assert_equal false, noden.valid? + end + + def test_node_too_far_south + node = current_nodes(:node_too_far_south) + assert_valid node + end + + def test_node_too_far_west + node = current_nodes(:node_too_far_west) + assert_valid node + end + + def test_node_too_far_east + node = current_nodes(:node_too_far_east) + assert_valid node + end + + def test_totally_wrong + node = current_nodes(:node_totally_wrong) + assert_valid node + end + def test_create node_template = Node.new(:latitude => 12.3456, :longitude => 65.4321, - :user_id => users(:normal_user).id, - :visible => 1, - :tags => "") + :user_id => users(:normal_user), + :visible => 1, + :version => 1) assert node_template.save_with_history! node = Node.find(node_template.id) @@ -19,7 +47,6 @@ class NodeTest < Test::Unit::TestCase assert_equal node_template.longitude, node.longitude assert_equal node_template.user_id, node.user_id assert_equal node_template.visible, node.visible - assert_equal node_template.tags, node.tags assert_equal node_template.timestamp.to_i, node.timestamp.to_i assert_equal OldNode.find(:all, :conditions => [ "id = ?", node_template.id ]).length, 1 @@ -34,7 +61,7 @@ class NodeTest < Test::Unit::TestCase end def test_update - node_template = Node.find(1) + node_template = Node.find(current_nodes(:visible_node).id) assert_not_nil node_template assert_equal OldNode.find(:all, :conditions => [ "id = ?", node_template.id ]).length, 1 @@ -43,7 +70,7 @@ class NodeTest < Test::Unit::TestCase node_template.latitude = 12.3456 node_template.longitude = 65.4321 - node_template.tags = "updated=yes" + #node_template.tags = "updated=yes" assert node_template.save_with_history! node = Node.find(node_template.id) @@ -52,7 +79,7 @@ class NodeTest < Test::Unit::TestCase assert_equal node_template.longitude, node.longitude assert_equal node_template.user_id, node.user_id assert_equal node_template.visible, node.visible - assert_equal node_template.tags, node.tags + #assert_equal node_template.tags, node.tags assert_equal node_template.timestamp.to_i, node.timestamp.to_i assert_equal OldNode.find(:all, :conditions => [ "id = ?", node_template.id ]).length, 2 @@ -63,12 +90,12 @@ class NodeTest < Test::Unit::TestCase assert_equal node_template.longitude, old_node.longitude assert_equal node_template.user_id, old_node.user_id assert_equal node_template.visible, old_node.visible - assert_equal node_template.tags, old_node.tags + #assert_equal node_template.tags, old_node.tags assert_equal node_template.timestamp.to_i, old_node.timestamp.to_i end def test_delete - node_template = Node.find(1) + node_template = Node.find(current_nodes(:visible_node)) assert_not_nil node_template assert_equal OldNode.find(:all, :conditions => [ "id = ?", node_template.id ]).length, 1 @@ -84,7 +111,7 @@ class NodeTest < Test::Unit::TestCase assert_equal node_template.longitude, node.longitude assert_equal node_template.user_id, node.user_id assert_equal node_template.visible, node.visible - assert_equal node_template.tags, node.tags + #assert_equal node_template.tags, node.tags assert_equal node_template.timestamp.to_i, node.timestamp.to_i assert_equal OldNode.find(:all, :conditions => [ "id = ?", node_template.id ]).length, 2 @@ -95,7 +122,7 @@ class NodeTest < Test::Unit::TestCase assert_equal node_template.longitude, old_node.longitude assert_equal node_template.user_id, old_node.user_id assert_equal node_template.visible, old_node.visible - assert_equal node_template.tags, old_node.tags + #assert_equal node_template.tags, old_node.tags assert_equal node_template.timestamp.to_i, old_node.timestamp.to_i end end diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb index 5468f7a2d..76c32a88d 100644 --- a/test/unit/user_test.rb +++ b/test/unit/user_test.rb @@ -2,9 +2,27 @@ require File.dirname(__FILE__) + '/../test_helper' class UserTest < Test::Unit::TestCase fixtures :users - - # Replace this with your real tests. - def test_truth - assert true + + def test_invalid_with_empty_attributes + user = User.new + assert !user.valid? + assert user.errors.invalid?(:email) + assert user.errors.invalid?(:pass_crypt) + assert user.errors.invalid?(:display_name) + assert user.errors.invalid?(:email) + assert !user.errors.invalid?(:home_lat) + assert !user.errors.invalid?(:home_lon) + assert !user.errors.invalid?(:home_zoom) + end + + def test_unique_email + new_user = User.new(:email => users(:normal_user).email, + :active => 1, + :pass_crypt => Digest::MD5.hexdigest('test'), + :display_name => "new user", + :data_public => 1, + :description => "desc") + assert !new_user.save + assert_equal ActiveRecord::Errors.default_error_messages[:taken], new_user.errors.on(:email) end end