# Parse request
- headers=AMF.getint(req) # Read number of headers
+ headers=AMF.getint(req) # Read number of headers
headers.times do # Read each header
name=AMF.getstring(req) # |
when 'getpresets'; results[index]=AMF.putdata(index,getpresets())
when 'whichways'; results[index]=AMF.putdata(index,whichways(*args))
when 'whichways_deleted'; results[index]=AMF.putdata(index,whichways_deleted(*args))
- when 'getway'; results[index]=AMF.putdata(index,getway(args[0].to_i))
+ when 'getway'; r=AMF.putdata(index,getway(args[0].to_i))
+ results[index]=r
when 'getrelation'; results[index]=AMF.putdata(index,getrelation(args[0].to_i))
when 'getway_old'; results[index]=AMF.putdata(index,getway_old(args[0].to_i,args[1]))
when 'getway_history'; results[index]=AMF.putdata(index,getway_history(args[0].to_i))
[wayid, points, tags, version]
end
-
+
# Get an old version of a way, and all constituent nodes.
#
# For undelete (version<0), always uses the most recent version of each node,
# Returns
# 0. 0 (success),
# 1. original relation id (unchanged),
- # 2. new relation id.
+ # 2. new relation id,
+ # 3. version.
def putrelation(renumberednodes, renumberedways, usertoken, changeset_id, version, relid, tags, members, visible) #:doc:
user = getuser(usertoken)
members.each do |m|
mid = m[1].to_i
if mid < 0
- mid = renumberednodes[mid] if m[0] == 'node'
- mid = renumberedways[mid] if m[0] == 'way'
+ mid = renumberednodes[mid] if m[0] == 'Node'
+ mid = renumberedways[mid] if m[0] == 'Way'
end
if mid
typedmembers << [m[0], mid, m[2]]
new_relation.changeset_id = changeset_id
new_relation.version = version
- # NOTE: id or relid here? id doesn't seem to be set above
if relid <= 0
# We're creating the node
new_relation.create_with_history(user)
end
end # transaction
- if id <= 0
+ if relid <= 0
return [0, relid, new_relation.id, new_relation.version]
else
- return [0, relid, relation.id, relation.version]
+ return [0, relid, relid, relation.version]
end
rescue OSM::APIChangesetAlreadyClosedError => ex
return [-1, "The changeset #{ex.changeset.id} was closed at #{ex.changeset.closed_at}."]
return [-1, "The relation has already been deleted."]
rescue OSM::APIError => ex
# Some error that we don't specifically catch
- return [-2, "Something really bad happened :-( ."]
+ return [-2, "An unusual error happened (in 'putrelation' #{relid})."]
end
# Save a way to the database, including all nodes. Any nodes in the previous
uniques=uniques-pointlist
uniques.each do |n|
node = Node.find(n)
- deleteitemrelations(user, changeset_id, id, 'node', node.version)
+ deleteitemrelations(user, changeset_id, id, 'Node', node.version)
new_node = Node.new
new_node.changeset_id = changeset_id
new_node.version = node.version
return [-1, "The point has already been deleted."]
rescue OSM::APIError => ex
# Some error that we don't specifically catch
- return [-2, "Something really bad happened :-(."]
+ return [-2, "An unusual error happened (in 'putway' #{originalway})."]
end
# Save POI to the database.
return [-1, "The point has already been deleted"]
rescue OSM::APIError => ex
# Some error that we don't specifically catch
- return [-2, "Something really bad happened :-()"]
+ return [-2, "An unusual error happened (in 'putpoi' #{id})."]
end
# Read POI from database
# delete the way
old_way = Way.find(way_id)
+ u = old_way.unshared_node_ids
delete_way = Way.new
delete_way.version = way_version
delete_way.changeset_id = changeset_id
old_way.delete_with_history!(delete_way, user)
- old_way.unshared_node_ids.each do |node_id|
+ u.each do |node_id|
# delete the node
node = Node.find(node_id)
delete_node = Node.new
else
# in case the node wasn't passed (i.e. if it was previously removed
# from the way in Potlatch)
- deleteitemrelations(user, changeset_id, node_id, 'node', node.version)
+ deleteitemrelations(user, changeset_id, node_id, 'Node', node.version)
delete_node.version = node.version
end
node.delete_with_history!(delete_node, user)
return [-1, "The way has already been deleted."]
rescue OSM::APIError => ex
# Some error that we don't specifically catch
- return [-2, "Something really bad happened :-( ."]
+ return [-2, "An unusual error happened (in 'deleteway' #{way_id})."]
end
def deleteitemrelations(user, changeset_id, objid, type, version) #:doc:
relations = RelationMember.find(:all,
- :conditions => ['member_type = ? and member_id = ?', type, objid],
+ :conditions => ['member_type = ? and member_id = ?', type.classify, objid],
:include => :relation).collect { |rm| rm.relation }.uniq
relations.each do |rel|
SELECT DISTINCT cr.id AS relid,cr.version AS version
FROM current_relations cr
INNER JOIN current_relation_members crm ON crm.id=cr.id
- INNER JOIN current_nodes cn ON crm.member_id=cn.id AND crm.member_type='node'
+ INNER JOIN current_nodes cn ON crm.member_id=cn.id AND crm.member_type='Node'
WHERE #{OSM.sql_for_area(ymin, xmin, ymax, xmax, "cn.")}
EOF
unless way_ids.empty?
SELECT DISTINCT cr.id AS relid,cr.version AS version
FROM current_relations cr
INNER JOIN current_relation_members crm ON crm.id=cr.id
- WHERE crm.member_type='way'
+ WHERE crm.member_type='Way'
AND crm.member_id IN (#{way_ids.join(',')})
EOF
end
- return ActiveRecord::Base.connection.select_all(sql).collect { |a| [a['relid'].to_i,a['version'].to_i] }
+ ActiveRecord::Base.connection.select_all(sql).collect { |a| [a['relid'].to_i,a['version'].to_i] }
end
def sql_get_nodes_in_way(wayid)
points=[]
sql=<<-EOF
- SELECT latitude*0.0000001 AS lat,longitude*0.0000001 AS lon,current_nodes.id
+ SELECT latitude*0.0000001 AS lat,longitude*0.0000001 AS lon,current_nodes.id,current_nodes.version
FROM current_way_nodes,current_nodes
WHERE current_way_nodes.id=#{wayid.to_i}
AND current_way_nodes.node_id=current_nodes.id
nodetags[n['k']]=n['v']
end
nodetags.delete('created_by')
- points << [row['lon'].to_f,row['lat'].to_f,row['id'].to_i,nodetags]
+ points << [row['lon'].to_f,row['lat'].to_f,row['id'].to_i,nodetags,row['version'].to_i]
end
points
end
waynodes = XML::Node.new 'waynodes'
waynodes['maximum'] = APP_CONFIG['max_number_of_way_nodes'].to_s
api << waynodes
+ changesets = XML::Node.new 'changesets'
+ changesets['maximum_elements'] = Changeset::MAX_ELEMENTS.to_s
+ api << changesets
doc.root << api
# 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 :text => message, :status => :bad_request
# Todo: some sort of escaping of problem characters in the message
response.headers['Error'] = message
+ render :text => message, :status => :bad_request
end
private
else
#TODO: fix bugs in location determination for history tab (and other tabs) then uncomment this redirect
#redirect_to :action => 'list'
+
+ # For now just render immediately, and skip the db
+ render
+ return
end
conditions = conditions_bbox(bbox);
if format == "osm"
#redirect to API map get
redirect_to "http://api.openstreetmap.org/api/#{API_VERSION}/map?bbox=#{bbox}"
-
+
elsif format == "mapnik"
#redirect to a special 'export' cgi script
format = params[:mapnik_format]
scale = params[:mapnik_scale]
redirect_to "http://tile.openstreetmap.org/cgi-bin/export?bbox=#{bbox}&scale=#{scale}&format=#{format}"
-
+
elsif format == "osmarender"
#redirect to the t@h 'MapOf' service
format = params[:osmarender_format]
def version
begin
old_node = OldNode.find(:first, :conditions => {:id => params[:id], :version => params[:version]} )
+ if old_node.nil?
+ # (RecordNotFound is not raised with find :first...)
+ render :nothing => true, :status => :not_found
+ return
+ end
response.headers['Last-Modified'] = old_node.timestamp.rfc822
doc.root << old_node.to_xml_node
render :text => doc.to_s, :content_type => "text/xml"
- rescue ActiveRecord::RecordNotFound
- render :nothing => true, :status => :not_found
rescue
render :nothing => true, :status => :internal_server_error
end
def version
begin
old_relation = OldRelation.find(:first, :conditions => {:id => params[:id], :version => params[:version]} )
+ if old_relation.nil?
+ # (RecordNotFound is not raised with find :first...)
+ render :nothing => true, :status => :not_found
+ return
+ end
response.headers['Last-Modified'] = old_relation.timestamp.rfc822
doc.root << old_relation.to_xml_node
render :text => doc.to_s, :content_type => "text/xml"
- rescue ActiveRecord::RecordNotFound
- render :nothing => true, :status => :not_found
rescue
- render :nothing => true, :status => :internetal_service_error
+ render :nothing => true, :status => :internal_server_error
end
end
end
def version
begin
old_way = OldWay.find(:first, :conditions => {:id => params[:id], :version => params[:version]} )
+ if old_way.nil?
+ # (RecordNotFound is not raised with find :first...)
+ render :nothing => true, :status => :not_found
+ return
+ end
response.headers['Last-Modified'] = old_way.timestamp.rfc822
doc.root << old_way.to_xml_node
render :text => doc.to_s, :content_type => "text/xml"
- rescue ActiveRecord::RecordNotFound
- render :nothing => true, :status => :not_found
rescue
render :nothing => true, :status => :internal_server_error
end
nodes = Array.new
relations = Array.new
- # Matching for tags table
+ # Matching for node tags table
+ cond_node = Array.new
+ sql = '1=1'
+ if type
+ sql += ' AND current_node_tags.k=?'
+ cond_node += [type]
+ end
+ if value
+ sql += ' AND current_node_tags.v=?'
+ cond_node += [value]
+ end
+ cond_node = [sql] + cond_node
+
+ # Matching for way tags table
cond_way = Array.new
sql = '1=1'
if type
cond_way += [type]
end
if value
- sql += ' AND current_way_tags.v=? AND MATCH (current_way_tags.v) AGAINST (? IN BOOLEAN MODE)'
- cond_way += [value,'"' + value.sub(/[-+*<>"~()]/, ' ') + '"']
+ sql += ' AND current_way_tags.v=?'
+ cond_way += [value]
end
cond_way = [sql] + cond_way
- # Matching for tags table
+ # Matching for relation tags table
cond_rel = Array.new
sql = '1=1'
if type
cond_rel += [type]
end
if value
- sql += ' AND current_relation_tags.v=? AND MATCH (current_relation_tags.v) AGAINST (? IN BOOLEAN MODE)'
- cond_rel += [value,'"' + value.sub(/[-+*<>"~()]/, ' ') + '"']
+ sql += ' AND current_relation_tags.v=?'
+ cond_rel += [value]
end
cond_rel = [sql] + cond_rel
- # Matching for tags column
- if type and value
- cond_tags = ['tags LIKE ? OR tags LIKE ? OR tags LIKE ? OR tags LIKE ?',
- ''+type+'='+value+'',
- ''+type+'='+value+';%',
- '%;'+type+'='+value+';%',
- '%;'+type+'='+value+'' ]
- elsif type
- cond_tags = ['tags LIKE ? OR tags LIKE ?',
- ''+type+'=%',
- '%;'+type+'=%' ]
- elsif value
- cond_tags = ['tags LIKE ? OR tags LIKE ?',
- '%='+value+';%',
- '%='+value+'' ]
- else
- cond_tags = ['1=1']
- end
-
# First up, look for the relations we want
if do_relations
relations = Relation.find(:all,
# Now, nodes
if do_nodes
- nodes = Node.find(:all, :conditions => cond_tags, :limit => 2000)
+ nodes = Node.find(:all,
+ :joins => "INNER JOIN current_node_tags ON current_node_tags.id = current_nodes.id",
+ :conditions => cond_node, :limit => 2000)
end
# Fetch any node needed for our ways (only have matching nodes so far)
bounds_top =240*20
m =''
- m+=swfRecord(9,255.chr + 155.chr + 155.chr) #ÊBackground
+ m+=swfRecord(9,255.chr + 155.chr + 155.chr) # Background
absx=0
absy=0
xl=yb= 9999999
if params['token']
user=User.authenticate(:token => params[:token])
- sql="SELECT gps_points.latitude*0.0000001 AS lat,gps_points.longitude*0.0000001 AS lon,gpx_files.id AS fileid,UNIX_TIMESTAMP(gps_points.timestamp) AS ts "+
+ sql="SELECT gps_points.latitude*0.0000001 AS lat,gps_points.longitude*0.0000001 AS lon,gpx_files.id AS fileid,EXTRACT(EPOCH FROM gps_points.timestamp) AS ts "+
" FROM gpx_files,gps_points "+
"WHERE gpx_files.id=gpx_id "+
" AND gpx_files.user_id=#{user.id} "+
"ORDER BY fileid DESC,ts "+
"LIMIT 10000"
else
- sql="SELECT latitude*0.0000001 AS lat,longitude*0.0000001 AS lon,gpx_id AS fileid,UNIX_TIMESTAMP(timestamp) AS ts "+
+ sql="SELECT latitude*0.0000001 AS lat,longitude*0.0000001 AS lon,gpx_id AS fileid,EXTRACT(EPOCH FROM timestamp) AS ts "+
" FROM gps_points "+
"WHERE "+OSM.sql_for_area(ymin,xmin,ymax,xmax,"gps_points.")+
" AND (gps_points.timestamp IS NOT NULL) "+
@tag = params[:tag]
files = Tracetag.find_all_by_tag(params[:tag]).collect { |tt| tt.gpx_id }
- conditions[0] += " AND gpx_files.id IN (#{files.join(',')})"
+
+ if files.length > 0
+ conditions[0] += " AND gpx_files.id IN (#{files.join(',')})"
+ else
+ conditions[0] += " AND 0 = 1"
+ end
end
conditions[0] += " AND gpx_files.visible = ?"
if trace.inserted?
if trace.public? or (@user and @user == trace.user)
+ expires_in 7.days, :private => !trace.public, :public => trace.public
send_file(trace.large_picture_name, :filename => "#{trace.id}.gif", :type => 'image/gif', :disposition => 'inline')
else
render :nothing => true, :status => :forbidden
if trace.inserted?
if trace.public? or (@user and @user == trace.user)
+ expires_in 7.days, :private => !trace.public, :public => trace.public
send_file(trace.icon_picture_name, :filename => "#{trace.id}_icon.gif", :type => 'image/gif', :disposition => 'inline')
else
render :nothing => true, :status => :forbidden
set_table_name 'nodes'
+ # Should probably have the composite primary key set in the model
+ # however there are some weird bugs happening when you do
+ #set_primary_keys :id, :version
+
validates_presence_of :changeset_id, :timestamp
validates_inclusion_of :visible, :in => [ true, false ]
validates_numericality_of :latitude, :longitude
class OldNodeTag < ActiveRecord::Base
set_table_name 'node_tags'
-
+
belongs_to :user
validates_presence_of :id, :version
def update_from(new_relation, user)
check_consistency(self, new_relation, user)
- unless preconditions_ok?
+ unless new_relation.preconditions_ok?
raise OSM::APIPreconditionFailedError.new("Cannot update relation #{self.id}: data or member data is invalid.")
end
self.changeset_id = new_relation.changeset_id
self.timestamp = t
self.save!
- tags = self.tags
+ tags = self.tags.clone
self.relation_tags.each do |old_tag|
key = old_tag.k
# if we can match the tags we currently have to the list
# if any are different then set the flag and do the DB
# update.
if tags.has_key? key
- # rails 2.1 dirty handling should take care of making this
- # somewhat efficient... hopefully...
- old_tag.v = tags[key]
- tags_changed |= old_tag.changed?
- old_tag.save!
+ tags_changed |= (old_tag.v != tags[key])
# remove from the map, so that we can expect an empty map
# at the end if there are no new tags
else
# this means a tag was deleted
tags_changed = true
- RelationTag.delete_all ['id = ? and k = ?', self.id, old_tag.k]
end
end
# if there are left-over tags then they are new and will have to
# be added.
tags_changed |= (not tags.empty?)
- tags.each do |k,v|
+ RelationTag.delete_all(:id => self.id)
+ self.tags.each do |k,v|
+ logger.info "TAG added: #{k} -> #{v}"
tag = RelationTag.new
tag.k = k
tag.v = v
tag.save!
end
- # reload, so that all of the members are accessible in their
- # new state.
- self.reload
-
# same pattern as before, but this time we're collecting the
# changed members in an array, as the bounding box updates for
# elements are per-element, not blanked on/off like for tags.
def preconditions_ok?
return false if self.nds.empty?
if self.nds.length > APP_CONFIG['max_number_of_way_nodes']
- raise OSM::APITooManyWayNodesError.new(self.nds.count, APP_CONFIG['max_number_of_way_nodes'])
+ raise OSM::APITooManyWayNodesError.new(self.nds.length, APP_CONFIG['max_number_of_way_nodes'])
end
self.nds.each do |n|
node = Node.find(:first, :conditions => ["id = ?", n])
<td><%=maxlon -%></td>
</tr>
<tr>
- <td colspan="3" style="text-align:center"><%= minlon -%></td>
+ <td colspan="3" style="text-align:center"><%= minlat -%></td>
</tr>
</table>
</td>
<tr valign="top">
<th>Has the following <%= @node_pages.item_count %> nodes:</th>
<td>
- <table padding="0">
+ <table cellpadding="0">
<% @nodes.each do |node| %>
<tr><td><%= link_to "Node #{node.id.to_s}, version #{node.version.to_s}", :action => "node", :id => node.id.to_s %></td></tr>
<% end %>
<tr valign="top">
<th>Has the following <%= @way_pages.item_count %> ways:</th>
<td>
- <table padding="0">
+ <table cellpadding="0">
<% @ways.each do |way| %>
<tr><td><%= link_to "Way #{way.id.to_s}, version #{way.version.to_s}", :action => "way", :id => way.id.to_s %></td></tr>
<% end %>
<tr valign="top">
<th>Has the following <%= @relation_pages.item_count %> relations:</th>
<td>
- <table padding="0">
+ <table cellpadding="0">
<% @relations.each do |relation| %>
<tr><td><%= link_to "Relation #{relation.id.to_s}, version #{relation.version.to_s}", :action => "relation", :id => relation.id.to_s %></td></tr>
<% end %>
<tr valign="top">
<th>Part of:</th>
<td>
- <table padding="0">
+ <table cellpadding="0">
<% node_details.ways.each do |way| %>
<tr><td><%= link_to "Way " + way.id.to_s, :action => "way", :id => way.id.to_s %></td></tr>
<% end %>
<tr valign="top">
<th>Members:</th>
<td>
- <table padding="0">
+ <table cellpadding="0">
<%= render :partial => "relation_member", :collection => relation_details.relation_members %>
</table>
</td>
<tr>
<th>Part of:</th>
<td>
- <table padding="0">
+ <table cellpadding="0">
<%= render :partial => "containing_relation", :collection => relation_details.containing_relation_members %>
</table>
</td>
<tr>
<td>
- <%= h(relation_member.member_type.capitalize) %>
- <%= link_to relation_member.member_id.to_s, :action => relation_member.member_type, :id => relation_member.member_id %>
+ <%= relation_member.member_type.capitalize %>
+ <%= link_to relation_member.member_id.to_s, :action => relation_member.member_type.downcase, :id => relation_member.member_id %>
<% unless relation_member.member_role.blank? %>
as
<%= h(relation_member.member_role) %>
<tr valign="top">
<th>Tags:</th>
<td>
- <table padding="0">
+ <table cellpadding="0">
<%= render :partial => "tag", :collection => tag_details.tags_as_hash %>
</table>
</td>
<tr valign="top">
<th>Nodes:</th>
<td>
- <table padding="0">
+ <table cellpadding="0">
<% way_details.way_nodes.each do |wn| %>
<tr><td><%= link_to "Node " + wn.node_id.to_s, :action => "node", :id => wn.node_id.to_s %></td></tr>
<% end %>
<tr valign="top">
<th>Part of:</th>
<td>
- <table padding="0">
+ <table cellpadding="0">
<%= render :partial => "containing_relation", :collection => way_details.containing_relation_members %>
</table>
</td>
<%if showusername %>
<td class="<%= cl %>">
<% if changeset.user.data_public? %>
- <%= link_to h(changeset.user.display_name), :controller => "user", :action => "view", :display_name => changeset.user.display_name %>
+ <%= link_to h(changeset.user.display_name), :controller => "changeset", :action => "list_user", :display_name => changeset.user.display_name %>
<% else %>
<i>Anonymous</i>
<% end %>
<h1>Recent Changes</h1>
-<p>Recently closed changesets:</p>
+<p>Recently edited changesets:</p>
<table id="keyvalue" cellpadding="3">
<tr>
%>
<li><%= link_to 'View', {:controller => 'site', :action => 'index'}, {:id => 'viewanchor', :title => 'view maps', :class => viewclass} %></li>
<li><%= link_to 'Edit', {:controller => 'site', :action => 'edit'}, {:id => 'editanchor', :title => 'edit maps', :class => editclass} %></li>
- <li><%= link_to 'History', {:controller => 'history' }, {:id => 'historyanchor', :title => 'changeset history', :class => historyclass} %></li>
+ <li><%= link_to 'History', {:controller => 'changeset', :action => 'list_bbox' }, {:id => 'historyanchor', :title => 'changeset history', :class => historyclass} %></li>
<% if params['controller'] == 'site' and (params['action'] == 'index' or params['action'] == 'export') %>
<li><%= link_to_remote 'Export', {:url => {:controller => 'export', :action => 'start'}}, {:id => 'exportanchor', :title => 'export map data', :class => exportclass, :href => url_for(:controller => 'site', :action => 'export')} %></li>
<% else %>
}
}
+ function markChanged(a) { changesaved=a; }
+
function doSWF(lat,lon,sc) {
if (sc < 11) sc = 11;
fo.addVariable('scale',sc);
var layers = getMapLayers();
var extents = getMapExtent();
- updatelinks(lonlat.lon, lonlat.lat, zoom, layers, extents);
-
+ updatelinks(lonlat.lon, lonlat.lat, zoom, layers, extents.left, extents.bottom, extents.right, extents.top);
+
document.cookie = "_osm_location=" + lonlat.lon + "|" + lonlat.lat + "|" + zoom + "|" + layers;
}
<br/>
<% if @user and @this_user.id == @user.id %>
<%= link_to 'change your settings', :controller => 'user', :action => 'account', :display_name => @user.display_name %>
-<% end %>
\ No newline at end of file
+<% end %>
# And be sure to use new-style password hashing:
# http://dev.mysql.com/doc/refman/5.0/en/old-client.html
development:
- adapter: mysql
+ adapter: postgresql
database: openstreetmap
- username: openstreetmap
- password: openstreetmap
+# username: openstreetmap
+# password: openstreetmap
host: localhost
encoding: utf8
# re-generated from your development database when you run 'rake'.
# Do not set this db to the same as development or production.
test:
- adapter: mysql
+ adapter: postgresql
database: osm_test
- username: osm_test
- password: osm_test
+# username: osm_test
+# password: osm_test
host: localhost
encoding: utf8
production:
- adapter: mysql
+ adapter: postgresql
database: osm
- username: osm
- password: osm
+# username: osm
+# password: osm
host: localhost
encoding: utf8
config.gem 'composite_primary_keys', :version => '1.1.0'
config.gem 'libxml-ruby', :version => '>= 1.1.1', :lib => 'libxml'
config.gem 'rmagick', :lib => 'RMagick'
- config.gem 'mysql'
# Only load the plugins named here, in the order given. By default, all plugins
# in vendor/plugins are loaded in alphabetical order.
server.groupname = "www-data"
server.pid-file = "/var/run/lighttpd.pid"
+server.max-fds = 8192
+
#
# Setup logging
#
$HTTP["useragent"] == "tilesAtHome" {
server.error-handler-404 = "/dispatch.tah"
}
-else $HTTP["url"] =~ "^/api/0\.5/(map|trackpoints|amf|amf/read|swf/trackpoints)$" {
+else $HTTP["url"] =~ "^/api/0\.6/(map|trackpoints|amf|amf/read|swf/trackpoints)$" {
server.error-handler-404 = "/dispatch.bulkapi"
}
-else $HTTP["url"] =~ "^/api/0\.5/.*/search$" {
+else $HTTP["url"] =~ "^/api/0\.6/.*/search$" {
server.error-handler-404 = "/dispatch.bulkapi"
}
-else $HTTP["url"] =~ "^/api/0\.5/" {
+else $HTTP["url"] =~ "^/api/0\.6/" {
server.error-handler-404 = "/dispatch.api"
}
else $HTTP["url"] =~ "^/api/0\.[0-9]+/" {
--- /dev/null
+---\r
+log_file: log/mongrel.log\r
+port: 8000\r
+pid_file: tmp/mongrel.pid\r
+servers: 8
\ No newline at end of file
--- /dev/null
+# Run as www-data
+user www-data www-data;
+
+# Use two worker processes
+worker_processes 2;
+
+# Define PID files
+pid /var/run/nginx.pid;
+
+# Define error log
+error_log /var/log/nginx/error.log;
+
+events {
+ # max clients = worker_processes * worker_connections
+ worker_connections 1024;
+}
+
+http {
+ # Configure MIME types
+ include /etc/nginx/mime.types;
+ default_type application/octet-stream;
+
+ # Configure network details
+ sendfile on;
+ keepalive_timeout 65;
+ tcp_nodelay on;
+
+ # Define access log
+ access_log /var/log/nginx/access.log;
+
+ # Configure compression (text/html is compressed by default)
+ gzip on;
+ gzip_min_length 1100;
+ gzip_buffers 4 8k;
+ gzip_types text/plain application/x-javascript application/x-shockwave-flash text/css;
+
+ #NO CGI SUPPORT IN NGINX fix stat .pl later
+
+ # Define fastcgi backend for web pages
+ upstream web_backend {
+ server 127.0.0.1:8000;
+ server 127.0.0.1:8001;
+ server 127.0.0.1:8002;
+ server 127.0.0.1:8003;
+ server 127.0.0.1:8004;
+ server 127.0.0.1:8005;
+ server 127.0.0.1:8006;
+ server 127.0.0.1:8007;
+ server 127.0.0.1:8008;
+ server 127.0.0.1:8009;
+ server 127.0.0.1:8010;
+ server 127.0.0.1:8011;
+ server 127.0.0.1:8012;
+ server 127.0.0.1:8013;
+ server 127.0.0.1:8014;
+ server 127.0.0.1:8015;
+ server 127.0.0.1:8016;
+ server 127.0.0.1:8017;
+ server 127.0.0.1:8018;
+ server 127.0.0.1:8019;
+ server 127.0.0.1:8020;
+ server 127.0.0.1:8021;
+ server 127.0.0.1:8022;
+ server 127.0.0.1:8023;
+ server 127.0.0.1:8024;
+ server 127.0.0.1:8025;
+ }
+
+ # Define fastcgi backend for geocoder searches
+ upstream geocoder_backend {
+ server 127.0.0.1:8026;
+ server 127.0.0.1:8027;
+ server 127.0.0.1:8028;
+ server 127.0.0.1:8029;
+ }
+
+ # Define fastcgi backend for api requests
+ upstream api_backend {
+ server 127.0.0.1:8030;
+ server 127.0.0.1:8031;
+ server 127.0.0.1:8032;
+ server 127.0.0.1:8033;
+ server 127.0.0.1:8034;
+ server 127.0.0.1:8035;
+ server 127.0.0.1:8036;
+ server 127.0.0.1:8037;
+ server 127.0.0.1:8038;
+ server 127.0.0.1:8039;
+ server 127.0.0.1:8040;
+ server 127.0.0.1:8041;
+ server 127.0.0.1:8042;
+ server 127.0.0.1:8043;
+ server 127.0.0.1:8044;
+ }
+
+ # Define fastcgi backend for bulk api requests
+ upstream bulkapi_backend {
+ server 10.0.0.10:8000;
+ server 10.0.0.11:8000;
+ server 10.0.0.12:8000;
+ server 10.0.0.10:8001;
+ server 10.0.0.11:8001;
+ server 10.0.0.12:8001;
+ server 10.0.0.10:8002;
+ server 10.0.0.11:8002;
+ server 10.0.0.12:8002;
+ server 10.0.0.10:8003;
+ server 10.0.0.11:8003;
+ server 10.0.0.12:8003;
+ server 10.0.0.10:8004;
+ server 10.0.0.11:8004;
+ server 10.0.0.12:8004;
+ }
+
+ # Define fastcgi backend for tiles@home requests
+ upstream tah_backend {
+ server 10.0.0.10:8005;
+ server 10.0.0.11:8005;
+ server 10.0.0.12:8005;
+ }
+
+ server {
+ # Listen on port 80
+ listen 80;
+
+ # Serve rails public files
+ root /home/rails/public;
+
+ # Use index.html as the index page
+ index index.html;
+
+ # Redirect trac requests for historical reasons
+ location /trac/ {
+ rewrite ^/trac/(.*)$ http://trac.openstreetmap.org/$1 permanent;
+ }
+
+ # Redirect wiki requests for historical reasons
+ location /wiki/ {
+ rewrite ^/wiki/(.*)$ http://wiki.openstreetmap.org/$1 permanent;
+ }
+
+ # Placeholder for blocking abuse
+ include /etc/nginx/blocked_hosts;
+ allow all;
+
+ # Block some bulk download agents
+ if ($http_user_agent ~* LWP::Simple|downloadosm|BBBike) {
+ return 403;
+ }
+
+ # Block some robots
+ if ($http_user_agent ~* msnbot|twiceler) {
+ return 403;
+ }
+
+ # Map api.openstreetmap/0.n/... to api.openstreetmap/api/0.n/...
+ if ($host ~* ^api\.) {
+ rewrite ^/(0\.[0-9]+)/(.*)$ /api/$1/$2;
+ rewrite ^/capabilities$ /api/capabilities;
+ }
+
+ # Strip asset tags
+ location ~ ^/(images|javascripts|openlayers|stylesheets|user/image)/ {
+ # Strip asset tags
+ rewrite ^/(.*)/[0-9]+$ /$1;
+
+ # Set expiry to the maximum - the asset tag will change
+ # when there is a new version
+ expires max;
+
+ # Only cache OpenLayers for seven days though
+ if ($uri ~ ^/openlayers/) {
+ expires 7d;
+ }
+ }
+
+ # Cache the embedded map page for seven days
+ location ~ ^/export/embed.html$ {
+ expires 7d;
+ }
+
+ # Include fastcgi configuration
+ include /etc/nginx/fastcgi_params;
+ fastcgi_param REQUEST_URI $uri;
+
+ # Handle tiles@home requests
+ location /api/ {
+ if ($http_user_agent ~ "^tilesAtHome") {
+ #deny all;
+ fastcgi_pass tah_backend;
+ break;
+ }
+ }
+
+ # Handle bulk api requests
+ location ~ ^/api/0\.6/(map|relation|trackpoints|amf|amf/read|swf/trackpoints|trace/[0-9]+/data)$ {
+ fastcgi_read_timeout 300;
+ fastcgi_pass bulkapi_backend;
+ break;
+ }
+
+ # Send search requests to the bulk api backend
+ location ~ ^/api/0\.6/.*/search$ {
+ fastcgi_read_timeout 300;
+ fastcgi_pass bulkapi_backend;
+ break;
+ }
+
+ # Send requests for full objects to the bulk api backend
+ location ~ ^/api/0\.6/.*/full$ {
+ fastcgi_read_timeout 300;
+ fastcgi_pass bulkapi_backend;
+ break;
+ }
+
+ # Handle the remaining api requests
+ location ~ ^/api/0\.6/ {
+ fastcgi_pass api_backend;
+ break;
+ }
+
+ # Deny old and unknown API versions
+ location ~ ^/api/0\.[0-9]+/ {
+ return 404;
+ }
+
+ # Send unversioned capabilities requests to the api backend
+ location = /api/capabilities {
+ fastcgi_pass api_backend;
+ break;
+ }
+
+ # Send geocoder searches to the geocoder backend
+ location /geocoder/ {
+ fastcgi_pass geocoder_backend;
+ break;
+ }
+
+ # Send everything else to the web backend unless it exists
+ # in the rails public tree
+ location / {
+ fastcgi_index index.html;
+
+ if (!-f $request_filename) {
+ fastcgi_pass web_backend;
+ break;
+ }
+ }
+
+ # Set the MIME type for crossdomain.xml policy files
+ # or flash will ignore it
+ location ~ /crossdomain\.xml$ {
+ types {
+ text/x-cross-domain-policy xml;
+ }
+ }
+
+ # Give munin access to some statistics
+ location /server-status {
+ stub_status on;
+ access_log off;
+ allow 127.0.0.1;
+ deny all;
+ }
+ }
+}
# API
map.connect "api/capabilities", :controller => 'api', :action => 'capabilities'
-
+ map.connect "api/#{API_VERSION}/capabilities", :controller => 'api', :action => 'capabilities'
+
map.connect "api/#{API_VERSION}/changeset/create", :controller => 'changeset', :action => 'create'
map.connect "api/#{API_VERSION}/changeset/:id/upload", :controller => 'changeset', :action => 'upload', :id => /\d+/
map.connect "api/#{API_VERSION}/changeset/:id/download", :controller => 'changeset', :action => 'download', :id => /\d+/
map.connect "api/#{API_VERSION}/relation/:id/relations", :controller => 'relation', :action => 'relations_for_relation', :id => /\d+/
map.connect "api/#{API_VERSION}/relation/:id/history", :controller => 'old_relation', :action => 'history', :id => /\d+/
map.connect "api/#{API_VERSION}/relation/:id/full", :controller => 'relation', :action => 'full', :id => /\d+/
+ map.connect "api/#{API_VERSION}/relation/:id/:version", :controller => 'old_relation', :action => 'version', :id => /\d+/, :version => /\d+/
map.connect "api/#{API_VERSION}/relation/:id", :controller => 'relation', :action => 'read', :id => /\d+/, :conditions => { :method => :get }
map.connect "api/#{API_VERSION}/relation/:id", :controller => 'relation', :action => 'update', :id => /\d+/, :conditions => { :method => :put }
map.connect "api/#{API_VERSION}/relation/:id", :controller => 'relation', :action => 'delete', :id => /\d+/, :conditions => { :method => :delete }
--- /dev/null
+class AddChangesetUserIndex < ActiveRecord::Migration
+ def self.up
+ add_index "changesets", ["user_id"], :name => "changesets_user_id_idx"
+ end
+
+ def self.down
+ remove_index "changesets", :name => "changesets_user_id_idx"
+ end
+end
--- /dev/null
+class AddChangesetIndexes < ActiveRecord::Migration
+ def self.up
+ add_index "nodes", ["changeset_id"], :name => "nodes_changeset_id_idx"
+ add_index "ways", ["changeset_id"], :name => "ways_changeset_id_idx"
+ add_index "relations", ["changeset_id"], :name => "relations_changeset_id_idx"
+ end
+
+ def self.down
+ remove_index "relations", :name => "relations_changeset_id_idx"
+ remove_index "ways", :name => "ways_changeset_id_idx"
+ remove_index "nodes", :name => "nodes_changeset_id_idx"
+ end
+end
--- /dev/null
+class AddMoreChangesetIndexes < ActiveRecord::Migration
+ def self.up
+ add_index "changesets", ["created_at"], :name => "changesets_created_at_idx"
+ add_index "changesets", ["closed_at"], :name => "changesets_closed_at_idx"
+ add_index "changesets", ["min_lat","max_lat","min_lon","max_lon"], :name => "changesets_bbox_idx", :method => "GIST"
+ end
+
+ def self.down
+ remove_index "changesets", :name => "changesets_bbox_idx"
+ remove_index "changesets", :name => "changesets_closed_at_idx"
+ remove_index "changesets", :name => "changesets_created_at_idx"
+ end
+end
def interval_constant(interval)
"'#{interval}'::interval"
end
+
+ def add_index(table_name, column_name, options = {})
+ column_names = Array(column_name)
+ index_name = index_name(table_name, :column => column_names)
+
+ if Hash === options # legacy support, since this param was a string
+ index_type = options[:unique] ? "UNIQUE" : ""
+ index_name = options[:name] || index_name
+ index_method = options[:method] || "BTREE"
+ else
+ index_type = options
+ end
+ quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ")
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} USING #{index_method} (#{quoted_column_names})"
+ end
end
end
end
//Called as the user scrolls/zooms around.
//Maniplate hrefs of the view tab and various other links
-function updatelinks(lon,lat,zoom,layers,extents) {
+function updatelinks(lon,lat,zoom,layers,minlon,minlat,maxlon,maxlat) {
var decimals = Math.pow(10, Math.floor(zoom/3));
var node;
if (zoom >= 11) {
var args = new Object();
//set bbox param from 'extents' object
- minlon = extents.left;
- minlat = extents.bottom;
- maxlon = extents.right;
- maxlat = extents.top;
- minlon = Math.round(minlon * decimals) / decimals;
- minlat = Math.round(minlat * decimals) / decimals;
- maxlon = Math.round(maxlon * decimals) / decimals;
- maxlat = Math.round(maxlat * decimals) / decimals;
- args.bbox = minlon + "," + minlat + "," + maxlon + "," + maxlat;
- node.href = setArgs("history/", args);
+ if (typeof minlon == "number" &&
+ typeof minlat == "number" &&
+ typeof maxlon == "number" &&
+ typeof maxlat == "number") {
+
+ minlon = Math.round(minlon * decimals) / decimals;
+ minlat = Math.round(minlat * decimals) / decimals;
+ maxlon = Math.round(maxlon * decimals) / decimals;
+ maxlat = Math.round(maxlat * decimals) / decimals;
+ args.bbox = minlon + "," + minlat + "," + maxlon + "," + maxlat;
+ }
+
+ node.href = setArgs("/history", args);
node.style.fontStyle = 'normal';
} else {
node.href = 'javascript:alert("zoom in to see editing history");';
#header {
float: left;
width: 100%;
- background: #DAE0D2 url("bg.gif") repeat-x bottom;
+ background: #DAE0D2;
font-size: 93%;
line-height: normal;
}
}
#header li {
float: left;
- /*background:url("left.gif") no-repeat left top;*/
margin: 0px;
padding: 0px 0px 0px 9px;
}
#header li a {
float: left;
display: block;
- /*background:url("right.gif") no-repeat right top;*/
padding: 5px 15px 4px 6px;
text-decoration: none;
font-weight: bold;
#header li a:hover {
color: #333;
}
-#header #current {
- /* background-image:url("left_on.gif"); */
-}
#header #current a {
- background-image: url("right_on.gif");
color: #333;
padding-bottom: 5px;
}
puts "<h2>OpenStreetMap stats report run at #{start_time.to_s}</h2>"
begin
- user_count = User.count(:conditions => "active = true")
+ user_count = User.count(:conditions => "active = 1")
tracepoint_count = Tracepoint.count()
node_count = Node.count(:conditions => "visible = true")
way_count = Way.count(:conditions => "visible = true")
puts "<tr><th>Data Type</th><th>Day</th><th>Week</th><th>Month</th></tr>"
day_count = Trace.count(:user_id, :distinct => true,
- :conditions => "timestamp > NOW() - INTERVAL 1 DAY")
+ :conditions => "timestamp > NOW() - '1 DAY'::INTERVAL")
week_count = Trace.count(:user_id, :distinct => true,
- :conditions => "timestamp > NOW() - INTERVAL 7 DAY")
+ :conditions => "timestamp > NOW() - '7 DAYS'::INTERVAL")
month_count = Trace.count(:user_id, :distinct => true,
- :conditions => "timestamp > NOW() - INTERVAL 28 DAY")
+ :conditions => "timestamp > NOW() - '28 DAYS'::INTERVAL")
puts "<tr><th>GPX Files</th><td>#{day_count}</td><td>#{week_count}</td><td>#{month_count}</td></tr>"
day_count = OldNode.count(:user_id, :distinct => true,
:include => :changeset,
- :conditions => "timestamp > NOW() - INTERVAL 1 DAY")
+ :conditions => "timestamp > NOW() - '1 DAY'::INTERVAL")
week_count = OldNode.count(:user_id, :distinct => true,
:include => :changeset,
- :conditions => "timestamp > NOW() - INTERVAL 7 DAY")
+ :conditions => "timestamp > NOW() - '7 DAYS'::INTERVAL")
month_count = OldNode.count(:user_id, :distinct => true,
:include => :changeset,
- :conditions => "timestamp > NOW() - INTERVAL 28 DAY")
+ :conditions => "timestamp > NOW() - '28 DAYS'::INTERVAL")
puts "<tr><th>Nodes</th><td>#{day_count}</td><td>#{week_count}</td><td>#{month_count}</td></tr>"
puts "<table>"
puts "<tr><th>Day</th><th>Week</th><th>Month</th></tr>"
- day_users = OldNode.count(:conditions => "timestamp > NOW() - INTERVAL 1 DAY",
+ day_users = OldNode.count(:conditions => "timestamp > NOW() - '1 DAY'::INTERVAL",
:include => :changeset, :group => :user_id,
:order => "count_all DESC")
- week_users = OldNode.count(:conditions => "timestamp > NOW() - INTERVAL 7 DAY",
+ week_users = OldNode.count(:conditions => "timestamp > NOW() - '7 DAYS'::INTERVAL",
:include => :changeset, :group => :user_id,
:order => "count_all DESC", :limit => 60)
- month_users = OldNode.count(:conditions => "timestamp > NOW() - INTERVAL 28 DAY",
+ month_users = OldNode.count(:conditions => "timestamp > NOW() - '28 DAYS'::INTERVAL",
:include => :changeset, :group => :user_id,
:order => "count_all DESC", :limit => 60)
closed_at: "2007-01-02 00:00:00"
num_changes: 0
-normal_user_version_change:
+public_user_version_change:
id: 4
- user_id: 1
+ user_id: 2
created_at: "2008-01-01 00:00:00"
closed_at: <%= DateTime.now + Rational(1,24) %>
min_lon: <%= 1 * SCALE %>
member_role: "some"
member_type: "Node"
member_id: 5
+
+public_used:
+ id: 4
+ member_role: "used by other relation"
+ member_type: "Relation"
+ member_id: 5
id: 3
k: 'test'
v: 'yes'
+
+mt_1:
+ id: 4
+ k: 'tag1'
+ v: 'val1'
+
+mt_2:
+ id: 4
+ k: 'tag2'
+ v: 'val2'
+
+mt_3:
+ id: 4
+ k: 'tag3'
+ v: 'val3'
+
+mt_4:
+ id: 4
+ k: 'tag4'
+ v: 'val4'
invisible_relation:
id: 2
- changeset_id: 1
+ changeset_id: 3
timestamp: 2007-01-01 00:00:00
visible: false
version: 1
timestamp: 2007-01-01 00:00:00
visible: true
version: 1
+
+multi_tag_relation:
+ id: 4
+ changeset_id: 4
+ timestamp: 2009-04-21 09:50:57
+ visible: true
+ version: 1
+
+public_used_relation:
+ id: 5
+ changeset_id: 2
+ timestamp: 2009-04-22 00:30:33
+ visible: true
+ version: 1
member_type: "Node"
member_id: 5
version: 1
+
+t5:
+ id: 2
+ member_role: "some"
+ member_type: "Node"
+ member_id: 5
+ version: 1
+
+public_used:
+ id: 4
+ member_role: "used by other relation"
+ member_type: "Relation"
+ member_id: 5
+ version: 1
k: 'test'
v: 'yes'
version: 1
+
+mt_1:
+ id: 4
+ k: 'tag1'
+ v: 'val1'
+ version: 1
+
+mt_2:
+ id: 4
+ k: 'tag2'
+ v: 'val2'
+ version: 1
+
+mt_3:
+ id: 4
+ k: 'tag3'
+ v: 'val3'
+ version: 1
+
+mt_4:
+ id: 4
+ k: 'tag4'
+ v: 'val4'
+ version: 1
invisible_relation:
id: 2
- changeset_id: 1
+ changeset_id: 3
timestamp: 2007-01-01 00:00:00
visible: false
version: 1
timestamp: 2007-01-01 00:00:00
visible: true
version: 1
+
+multi_tag_relation:
+ id: 4
+ changeset_id: 4
+ timestamp: 2009-04-21 09:50:57
+ visible: true
+ version: 1
+
+public_used_relation:
+ id: 5
+ changeset_id: 2
+ timestamp: 2009-04-22 00:30:03
+ visible: true
+ version: 1
assert_select "version[minimum=#{API_VERSION}][maximum=#{API_VERSION}]", :count => 1
assert_select "area[maximum=#{APP_CONFIG['max_request_area']}]", :count => 1
assert_select "tracepoints[per_page=#{APP_CONFIG['tracepoints_per_page']}]", :count => 1
+ assert_select "changesets[maximum_elements=#{Changeset::MAX_ELEMENTS}]", :count => 1
end
end
end
# test the version call by submitting several revisions of a new node
# to the API and ensuring that later calls to version return the
# matching versions of the object.
+ #
+ ##
+ # FIXME Move this test to being an integration test since it spans multiple controllers
def test_version
+ ## First try this with a non-public user
basic_authorization(users(:normal_user).email, "test")
changeset_id = changesets(:normal_user_first_change).id
-
+
# setup a simple XML node
xml_doc = current_nodes(:visible_node).to_xml
xml_node = xml_doc.find("//osm/node").first
# save a version for later checking
versions[xml_node['version']] = xml_doc.to_s
+ # randomly move the node about
+ 20.times do
+ # move the node somewhere else
+ xml_node['lat'] = precision(rand * 180 - 90).to_s
+ xml_node['lon'] = precision(rand * 360 - 180).to_s
+ with_controller(NodeController.new) do
+ content xml_doc
+ put :update, :id => nodeid
+ assert_response :forbidden, "Should have rejected node update"
+ xml_node['version'] = @response.body.to_s
+ end
+ # save a version for later checking
+ versions[xml_node['version']] = xml_doc.to_s
+ end
+
+ # add a bunch of random tags
+ 30.times do
+ xml_tag = XML::Node.new("tag")
+ xml_tag['k'] = random_string
+ xml_tag['v'] = random_string
+ xml_node << xml_tag
+ with_controller(NodeController.new) do
+ content xml_doc
+ put :update, :id => nodeid
+ assert_response :forbidden,
+ "should have rejected node #{nodeid} (#{@response.body}) with forbidden"
+ xml_node['version'] = @response.body.to_s
+ end
+ # save a version for later checking
+ versions[xml_node['version']] = xml_doc.to_s
+ end
+
+ # probably should check that they didn't get written to the database
+
+
+ ## Now do it with the public user
+ basic_authorization(users(:public_user).email, "test")
+ changeset_id = changesets(:public_user_first_change).id
+
+ # setup a simple XML node
+ xml_doc = current_nodes(:node_with_versions).to_xml
+ xml_node = xml_doc.find("//osm/node").first
+ nodeid = current_nodes(:node_with_versions).id
+
+ # keep a hash of the versions => string, as we'll need something
+ # to test against later
+ versions = Hash.new
+
+ # save a version for later checking
+ versions[xml_node['version']] = xml_doc.to_s
+
# randomly move the node about
20.times do
# move the node somewhere else
assert_nodes_are_equal check_node, api_node
end
end
-
+
+ def test_not_found_version
+ check_not_found_id_version(70000,312344)
+ check_not_found_id_version(-1, -13)
+ check_not_found_id_version(nodes(:visible_node).id, 24354)
+ check_not_found_id_version(24356, nodes(:visible_node).version)
+ end
+
+ def check_not_found_id_version(id, version)
+ get :version, :id => id, :version => version
+ assert_response :not_found
+ end
+
##
# Test that getting the current version is identical to picking
# that version with the version URI call.
def precision(f)
return (f * GeoRecord::SCALE).round.to_f / GeoRecord::SCALE
end
-
- def basic_authorization(user, pass)
- @request.env["HTTP_AUTHORIZATION"] = "Basic %s" % Base64.encode64("#{user}:#{pass}")
- end
-
- def content(c)
- @request.env["RAW_POST_DATA"] = c.to_s
- end
-
end
# -------------------------------------
def test_create
- basic_authorization "test@openstreetmap.org", "test"
+ basic_authorization users(:normal_user).email, "test"
# put the relation in a dummy fixture changset
changeset_id = changesets(:normal_user_first_change).id
+ # create an relation without members
+ content "<osm><relation changeset='#{changeset_id}'><tag k='test' v='yes' /></relation></osm>"
+ put :create
+ # hope for forbidden, due to user
+ assert_response :forbidden,
+ "relation upload should have failed with forbidden"
+
+ ###
+ # create an relation with a node as member
+ # This time try with a role attribute in the relation
+ nid = current_nodes(:used_node_1).id
+ content "<osm><relation changeset='#{changeset_id}'>" +
+ "<member ref='#{nid}' type='node' role='some'/>" +
+ "<tag k='test' v='yes' /></relation></osm>"
+ put :create
+ # hope for forbidden due to user
+ assert_response :forbidden,
+ "relation upload did not return forbidden status"
+
+ ###
+ # create an relation with a node as member, this time test that we don't
+ # need a role attribute to be included
+ nid = current_nodes(:used_node_1).id
+ content "<osm><relation changeset='#{changeset_id}'>" +
+ "<member ref='#{nid}' type='node'/>"+
+ "<tag k='test' v='yes' /></relation></osm>"
+ put :create
+ # hope for forbidden due to user
+ assert_response :forbidden,
+ "relation upload did not return forbidden status"
+
+ ###
+ # create an relation with a way and a node as members
+ nid = current_nodes(:used_node_1).id
+ wid = current_ways(:used_way).id
+ content "<osm><relation changeset='#{changeset_id}'>" +
+ "<member type='node' ref='#{nid}' role='some'/>" +
+ "<member type='way' ref='#{wid}' role='other'/>" +
+ "<tag k='test' v='yes' /></relation></osm>"
+ put :create
+ # hope for forbidden, due to user
+ assert_response :forbidden,
+ "relation upload did not return success status"
+
+
+
+ ## Now try with the public user
+ basic_authorization users(:public_user).email, "test"
+
+ # put the relation in a dummy fixture changset
+ changeset_id = changesets(:public_user_first_change).id
+
# create an relation without members
content "<osm><relation changeset='#{changeset_id}'><tag k='test' v='yes' /></relation></osm>"
put :create
"saved relation does not contain exactly one tag"
assert_equal changeset_id, checkrelation.changeset.id,
"saved relation does not belong in the changeset it was assigned to"
- assert_equal users(:normal_user).id, checkrelation.changeset.user_id,
+ assert_equal users(:public_user).id, checkrelation.changeset.user_id,
"saved relation does not belong to user that created it"
assert_equal true, checkrelation.visible,
"saved relation is not visible"
"saved relation does not contain exactly one tag"
assert_equal changeset_id, checkrelation.changeset.id,
"saved relation does not belong in the changeset it was assigned to"
- assert_equal users(:normal_user).id, checkrelation.changeset.user_id,
+ assert_equal users(:public_user).id, checkrelation.changeset.user_id,
"saved relation does not belong to user that created it"
assert_equal true, checkrelation.visible,
"saved relation is not visible"
"saved relation does not contain exactly one tag"
assert_equal changeset_id, checkrelation.changeset.id,
"saved relation does not belong in the changeset it was assigned to"
- assert_equal users(:normal_user).id, checkrelation.changeset.user_id,
+ assert_equal users(:public_user).id, checkrelation.changeset.user_id,
"saved relation does not belong to user that created it"
assert_equal true, checkrelation.visible,
"saved relation is not visible"
"saved relation does not contain exactly one tag"
assert_equal changeset_id, checkrelation.changeset.id,
"saved relation does not belong in the changeset it was assigned to"
- assert_equal users(:normal_user).id, checkrelation.changeset.user_id,
+ assert_equal users(:public_user).id, checkrelation.changeset.user_id,
"saved relation does not belong to user that created it"
assert_equal true, checkrelation.visible,
"saved relation is not visible"
end
+ # ------------------------------------
+ # Test updating relations
+ # ------------------------------------
+
+ ##
+ # test that, when tags are updated on a relation, the correct things
+ # happen to the correct tables and the API gives sensible results.
+ # this is to test a case that gregory marler noticed and posted to
+ # josm-dev.
+ def test_update_relation_tags
+ basic_authorization "test@example.com", "test"
+ rel_id = current_relations(:multi_tag_relation).id
+ cs_id = changesets(:public_user_first_change).id
+
+ with_relation(rel_id) do |rel|
+ # alter one of the tags
+ tag = rel.find("//osm/relation/tag").first
+ tag['v'] = 'some changed value'
+ update_changeset(rel, cs_id)
+
+ # check that the downloaded tags are the same as the uploaded tags...
+ new_version = with_update(rel) do |new_rel|
+ assert_tags_equal rel, new_rel
+ end
+
+ # check the original one in the current_* table again
+ with_relation(rel_id) { |r| assert_tags_equal rel, r }
+
+ # now check the version in the history
+ with_relation(rel_id, new_version) { |r| assert_tags_equal rel, r }
+ end
+ end
+
+ ##
+ # test that, when tags are updated on a relation when using the diff
+ # upload function, the correct things happen to the correct tables
+ # and the API gives sensible results. this is to test a case that
+ # gregory marler noticed and posted to josm-dev.
+ def test_update_relation_tags_via_upload
+ basic_authorization "test@example.com", "test"
+ rel_id = current_relations(:multi_tag_relation).id
+ cs_id = changesets(:public_user_first_change).id
+
+ with_relation(rel_id) do |rel|
+ # alter one of the tags
+ tag = rel.find("//osm/relation/tag").first
+ tag['v'] = 'some changed value'
+ update_changeset(rel, cs_id)
+
+ # check that the downloaded tags are the same as the uploaded tags...
+ new_version = with_update_diff(rel) do |new_rel|
+ assert_tags_equal rel, new_rel
+ end
+
+ # check the original one in the current_* table again
+ with_relation(rel_id) { |r| assert_tags_equal rel, r }
+
+ # now check the version in the history
+ with_relation(rel_id, new_version) { |r| assert_tags_equal rel, r }
+ end
+ end
+
# -------------------------------------
# Test creating some invalid relations.
# -------------------------------------
# first try to delete relation without auth
delete :delete, :id => current_relations(:visible_relation).id
assert_response :unauthorized
+
+ ## First try with the private user, to make sure that you get a forbidden
+ basic_authorization(users(:normal_user).email, "test")
+
+ # this shouldn't work, as we should need the payload...
+ delete :delete, :id => current_relations(:visible_relation).id
+ assert_response :forbidden
+
+ # try to delete without specifying a changeset
+ content "<osm><relation id='#{current_relations(:visible_relation).id}'/></osm>"
+ delete :delete, :id => current_relations(:visible_relation).id
+ assert_response :forbidden
+
+ # try to delete with an invalid (closed) changeset
+ content update_changeset(current_relations(:visible_relation).to_xml,
+ changesets(:normal_user_closed_change).id)
+ delete :delete, :id => current_relations(:visible_relation).id
+ assert_response :forbidden
+
+ # try to delete with an invalid (non-existent) changeset
+ content update_changeset(current_relations(:visible_relation).to_xml,0)
+ delete :delete, :id => current_relations(:visible_relation).id
+ assert_response :forbidden
- # now set auth
- basic_authorization("test@openstreetmap.org", "test");
+ # this won't work because the relation is in-use by another relation
+ content(relations(:used_relation).to_xml)
+ delete :delete, :id => current_relations(:used_relation).id
+ assert_response :forbidden
+
+ # this should work when we provide the appropriate payload...
+ content(relations(:visible_relation).to_xml)
+ delete :delete, :id => current_relations(:visible_relation).id
+ assert_response :forbidden
+
+ # this won't work since the relation is already deleted
+ content(relations(:invisible_relation).to_xml)
+ delete :delete, :id => current_relations(:invisible_relation).id
+ assert_response :forbidden
+
+ # this works now because the relation which was using this one
+ # has been deleted.
+ content(relations(:used_relation).to_xml)
+ delete :delete, :id => current_relations(:used_relation).id
+ assert_response :forbidden
+
+ # this won't work since the relation never existed
+ delete :delete, :id => 0
+ assert_response :forbidden
+
+
+
+ # now set auth for the private user
+ basic_authorization(users(:public_user).email, "test");
# this shouldn't work, as we should need the payload...
delete :delete, :id => current_relations(:visible_relation).id
delete :delete, :id => current_relations(:visible_relation).id
assert_response :conflict
- # this won't work because the relation is in-use by another relation
+ # this won't work because the relation is in a changeset owned by someone else
content(relations(:used_relation).to_xml)
delete :delete, :id => current_relations(:used_relation).id
+ assert_response :conflict,
+ "shouldn't be able to delete a relation in a changeset owned by someone else (#{@response.body})"
+
+ # this won't work because the relation in the payload is different to that passed
+ content(relations(:public_used_relation).to_xml)
+ delete :delete, :id => current_relations(:used_relation).id
+ assert_not_equal relations(:public_used_relation).id, current_relations(:used_relation).id
+ assert_response :bad_request, "shouldn't be able to delete a relation when payload is different to the url"
+
+ # this won't work because the relation is in-use by another relation
+ content(relations(:public_used_relation).to_xml)
+ delete :delete, :id => current_relations(:public_used_relation).id
assert_response :precondition_failed,
"shouldn't be able to delete a relation used in a relation (#{@response.body})"
# this should work when we provide the appropriate payload...
- content(relations(:visible_relation).to_xml)
- delete :delete, :id => current_relations(:visible_relation).id
+ content(relations(:multi_tag_relation).to_xml)
+ delete :delete, :id => current_relations(:multi_tag_relation).id
assert_response :success
# valid delete should return the new version number, which should
# this works now because the relation which was using this one
# has been deleted.
- content(relations(:used_relation).to_xml)
- delete :delete, :id => current_relations(:used_relation).id
+ content(relations(:public_used_relation).to_xml)
+ delete :delete, :id => current_relations(:public_used_relation).id
assert_response :success,
"should be able to delete a relation used in an old relation (#{@response.body})"
##
# check that relations are ordered
def test_relation_member_ordering
- basic_authorization("test@openstreetmap.org", "test");
-
+ basic_authorization(users(:public_user).email, "test")
+
doc_str = <<OSM
<osm>
- <relation changeset='1'>
+ <relation changeset='4'>
<member ref='1' type='node' role='first'/>
<member ref='3' type='node' role='second'/>
<member ref='1' type='way' role='third'/>
##
# check that relations can contain duplicate members
def test_relation_member_duplicates
- basic_authorization("test@openstreetmap.org", "test");
+ ## First try with the private user
+ basic_authorization(users(:normal_user).email, "test");
doc_str = <<OSM
<osm>
- <relation changeset='1'>
+ <relation changeset='4'>
+ <member ref='1' type='node' role='forward'/>
+ <member ref='3' type='node' role='forward'/>
+ <member ref='1' type='node' role='forward'/>
+ <member ref='3' type='node' role='forward'/>
+ </relation>
+</osm>
+OSM
+ doc = XML::Parser.string(doc_str).parse
+
+ content doc
+ put :create
+ assert_response :forbidden
+
+
+ ## Now try with the public user
+ basic_authorization(users(:public_user).email, "test");
+
+ doc_str = <<OSM
+<osm>
+ <relation changeset='4'>
<member ref='1' type='node' role='forward'/>
<member ref='3' type='node' role='forward'/>
<member ref='1' type='node' role='forward'/>
# create a changeset and yield to the caller to set it up, then assert
# that the changeset bounding box is +bbox+.
def check_changeset_modify(bbox)
- basic_authorization("test@openstreetmap.org", "test");
+ ## First test with the private user to check that you get a forbidden
+ basic_authorization(users(:normal_user).email, "test");
+
+ # create a new changeset for this operation, so we are assured
+ # that the bounding box will be newly-generated.
+ changeset_id = with_controller(ChangesetController.new) do
+ content "<osm><changeset/></osm>"
+ put :create
+ assert_response :forbidden, "shouldn't be able to create changeset for modify test, as should get forbidden"
+ end
+
+ ## Now do the whole thing with the public user
+ basic_authorization(users(:public_user).email, "test")
+
# create a new changeset for this operation, so we are assured
# that the bounding box will be newly-generated.
changeset_id = with_controller(ChangesetController.new) do
end
end
+ ##
+ # yields the relation with the given +id+ (and optional +version+
+ # to read from the history tables) into the block. the parsed XML
+ # doc is returned.
+ def with_relation(id, ver = nil)
+ if ver.nil?
+ get :read, :id => id
+ else
+ with_controller(OldRelationController.new) do
+ get :version, :id => id, :version => ver
+ end
+ end
+ assert_response :success
+ yield xml_parse(@response.body)
+ end
+
+ ##
+ # updates the relation (XML) +rel+ and
+ # yields the new version of that relation into the block.
+ # the parsed XML doc is retured.
+ def with_update(rel)
+ rel_id = rel.find("//osm/relation").first["id"].to_i
+ content rel
+ put :update, :id => rel_id
+ assert_response :success, "can't update relation: #{@response.body}"
+ version = @response.body.to_i
+
+ # now get the new version
+ get :read, :id => rel_id
+ assert_response :success
+ new_rel = xml_parse(@response.body)
+
+ yield new_rel
+
+ return version
+ end
+
+ ##
+ # updates the relation (XML) +rel+ via the diff-upload API and
+ # yields the new version of that relation into the block.
+ # the parsed XML doc is retured.
+ def with_update_diff(rel)
+ rel_id = rel.find("//osm/relation").first["id"].to_i
+ cs_id = rel.find("//osm/relation").first['changeset'].to_i
+ version = nil
+
+ with_controller(ChangesetController.new) do
+ doc = OSM::API.new.get_xml_doc
+ change = XML::Node.new 'osmChange'
+ doc.root = change
+ modify = XML::Node.new 'modify'
+ change << modify
+ modify << doc.import(rel.find("//osm/relation").first)
+
+ content doc.to_s
+ post :upload, :id => cs_id
+ assert_response :success, "can't upload diff relation: #{@response.body}"
+ version = xml_parse(@response.body).find("//diffResult/relation").first["new_version"].to_i
+ end
+
+ # now get the new version
+ get :read, :id => rel_id
+ assert_response :success
+ new_rel = xml_parse(@response.body)
+
+ yield new_rel
+
+ return version
+ end
+
+ ##
+ # returns a k->v hash of tags from an xml doc
+ def get_tags_as_hash(a)
+ a.find("//osm/relation/tag").inject({}) do |h,v|
+ h[v['k']] = v['v']
+ h
+ end
+ end
+
+ ##
+ # assert that all tags on relation documents +a+ and +b+
+ # are equal
+ def assert_tags_equal(a, b)
+ # turn the XML doc into tags hashes
+ a_tags = get_tags_as_hash(a)
+ b_tags = get_tags_as_hash(b)
+
+ assert_equal a_tags.keys, b_tags.keys, "Tag keys should be identical."
+ a_tags.each do |k, v|
+ assert_equal v, b_tags[k],
+ "Tags which were not altered should be the same. " +
+ "#{a_tags.inspect} != #{b_tags.inspect}"
+ end
+ end
+
##
# update the changeset_id of a node element
def update_changeset(xml, changeset_id)
api_fixtures
def test_tag_count
- assert_equal 3, OldRelationTag.count
+ assert_equal 7, OldRelationTag.count
end
def test_length_key_valid
api_fixtures
def test_relation_member_count
- assert_equal 5, RelationMember.count
+ assert_equal 6, RelationMember.count
end
end
api_fixtures
def test_relation_tag_count
- assert_equal 3, RelationTag.count
+ assert_equal 7, RelationTag.count
end
def test_length_key_valid
assert_raise(ActiveRecord::RecordInvalid) {tag.save!}
assert tag.new_record?
end
+
+ ##
+ # test that tags can be updated and saved uniquely, i.e: tag.save!
+ # only affects the single tag that the activerecord object
+ # represents. this amounts to testing that the primary key is
+ # unique.
+ def test_update
+ v = "probably unique string here 3142592654"
+ assert_equal 0, RelationTag.count(:conditions => ['v=?', v])
+
+ tag = RelationTag.find(:first)
+ tag.v = v
+ tag.save!
+
+ assert_equal 1, RelationTag.count(:conditions => ['v=?', v])
+ end
end
api_fixtures
def test_relation_count
- assert_equal 3, Relation.count
+ assert_equal 5, Relation.count
end
end