From d07277efba0c0069705bbefa68f2624a9c6c9ff0 Mon Sep 17 00:00:00 2001 From: Dan Moore Date: Fri, 23 Mar 2007 23:13:19 +0000 Subject: [PATCH] API 0.4 Updates - work on traces pages + pagination, edit tab, some API testing * traces - added some routes, replicated data access / pagination, but presentation and pending file control not complete * edit - setup so that applet can be loaded + token authorisation enabled * API - tests out ok against applet, but had to change segment-node associations * misc - gems version required upgraded to 1.2.3 (latest stable rails version), changed some find_first to find(:first... calls --- app/controllers/api_controller.rb | 5 +- app/controllers/application.rb | 47 ++++++------ app/controllers/node_controller.rb | 2 +- app/controllers/segment_controller.rb | 1 - app/controllers/trace_controller.rb | 92 ++++++++++++++++++++---- app/controllers/way_controller.rb | 8 +-- app/models/node.rb | 1 + app/models/segment.rb | 5 +- app/models/trace.rb | 41 ++++++++++- app/models/user.rb | 6 +- app/models/way.rb | 15 ++-- app/views/layouts/site.rhtml | 8 ++- app/views/site/edit.rhtml | 4 +- app/views/trace/_trace.rhtml | 6 +- app/views/trace/_trace_optionals.rhtml | 17 +++++ app/views/trace/_trace_paging_nav.rhtml | 19 +++++ app/views/trace/list.rhtml | 18 ++--- app/views/trace/mine.rhtml | 12 ++-- config/database.yml | 2 +- config/environment.rb | 7 +- config/routes.rb | 14 ++-- db/migrate.sql | 2 + lib/daemons/gpx_import.rb | 5 +- public/images/RSS.gif | Bin 0 -> 652 bytes script/daemons | 4 +- 25 files changed, 248 insertions(+), 93 deletions(-) create mode 100644 app/views/trace/_trace_optionals.rhtml create mode 100644 app/views/trace/_trace_paging_nav.rhtml create mode 100644 public/images/RSS.gif diff --git a/app/controllers/api_controller.rb b/app/controllers/api_controller.rb index f13802f7f..73fd9aa8c 100644 --- a/app/controllers/api_controller.rb +++ b/app/controllers/api_controller.rb @@ -27,7 +27,7 @@ class ApiController < ApplicationController if node_ids.length > 0 node_ids_sql = "(#{node_ids.join(',')})" # get the referenced segments - segments = Segment.find_by_sql "select * from current_segments where node_a in #{node_ids_sql} or node_b in #{node_ids_sql}" + segments = Segment.find_by_sql "select * from current_segments where visible = 1 and (node_a in #{node_ids_sql} or node_b in #{node_ids_sql})" end # see if we have nay missing nodes segments_nodes = segments.collect {|segment| segment.node_a } @@ -49,8 +49,7 @@ class ApiController < ApplicationController if segment_ids.length > 0 way_segments = WaySegment.find_all_by_segment_id(segment_ids) way_ids = way_segments.collect {|way_segment| way_segment.id } - - ways = Way.find(way_ids) + ways = Way.find(way_ids) # NB: doesn't pick up segments, tags from db until accessed via way.way_segments etc. end nodes.each do |node| diff --git a/app/controllers/application.rb b/app/controllers/application.rb index 0c8b4f17a..a4f65cd54 100644 --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -6,24 +6,31 @@ class ApplicationController < ActionController::Base @user = User.find_by_token(session[:token]) end - def authorize(realm='Web Password', errormessage="Could't authenticate you") - username, passwd = get_auth_data - # check if authorized - # try to get user - if @user = User.authenticate(username, passwd) + def authorize(realm='Web Password', errormessage="Could't authenticate you") + username, passwd = get_auth_data # parse from headers + # authenticate per-scheme + if username.nil? + @user = nil # no authentication provided - perhaps first connect (client should retry after 401) + elsif username == 'token' + @user = User.authenticate_token(passwd) # preferred - random token for user from db, passed in basic auth + else + @user = User.authenticate(username, passwd) # basic auth + end + + # handle authenticate pass/fail + if @user # user exists and password is correct ... horray! - if @user.methods.include? 'lastlogin' - # note last login + if @user.methods.include? 'lastlogin' # note last login @session['lastlogin'] = user.lastlogin @user.last.login = Time.now @user.save() @session["User.id"] = @user.id end else - # the user does not exist or the password was wrong - @response.headers["Status"] = "Unauthorized" - @response.headers["WWW-Authenticate"] = "Basic realm=\"#{realm}\"" - render_text(errormessage, 401) + # no auth, the user does not exist or the password was wrong + response.headers["Status"] = "Unauthorized" + response.headers["WWW-Authenticate"] = "Basic realm=\"#{realm}\"" + render_text(errormessage, 401) # :unauthorized end end @@ -37,22 +44,18 @@ class ApplicationController < ActionController::Base return doc end + # extract authorisation credentials from headers, returns user = nil if none private def get_auth_data - user, pass = '', '' - # extract authorisation credentials - if request.env.has_key? 'X-HTTP_AUTHORIZATION' - # try to get it where mod_rewrite might have put it - authdata = @request.env['X-HTTP_AUTHORIZATION'].to_s.split - elsif request.env.has_key? 'HTTP_AUTHORIZATION' - # this is the regular location - authdata = @request.env['HTTP_AUTHORIZATION'].to_s.split + 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? 'HTTP_AUTHORIZATION' # regular location + authdata = request.env['HTTP_AUTHORIZATION'].to_s.split end - - # at the moment we only support basic authentication + # only basic authentication supported if authdata and authdata[0] == 'Basic' user, pass = Base64.decode64(authdata[1]).split(':')[0..1] - end + end return [user, pass] end diff --git a/app/controllers/node_controller.rb b/app/controllers/node_controller.rb index 5cdbca827..bd81c9aa2 100644 --- a/app/controllers/node_controller.rb +++ b/app/controllers/node_controller.rb @@ -4,7 +4,7 @@ class NodeController < ApplicationController before_filter :authorize after_filter :compress_output - def create + def create response.headers["Content-Type"] = 'application/xml' if request.put? node = nil diff --git a/app/controllers/segment_controller.rb b/app/controllers/segment_controller.rb index f92fab271..950479ad7 100644 --- a/app/controllers/segment_controller.rb +++ b/app/controllers/segment_controller.rb @@ -10,7 +10,6 @@ class SegmentController < ApplicationController segment = Segment.from_xml(request.raw_post, true) if segment - segment.user_id = @user.id segment.from_node = Node.find(segment.node_a.to_i) diff --git a/app/controllers/trace_controller.rb b/app/controllers/trace_controller.rb index fa062b728..fbc2cefca 100644 --- a/app/controllers/trace_controller.rb +++ b/app/controllers/trace_controller.rb @@ -1,24 +1,87 @@ class TraceController < ApplicationController before_filter :authorize_web layout 'site' - - def list - @page = params[:page].to_i + + # Counts and selects pages of GPX traces for various criteria (by user, tags, public etc.). + # target_user - if set, specifies the user to fetch traces for. if not set will fetch all traces + # paging_action - the action that will be linked back to from view + def list (target_user = nil, paging_action = 'list') + @traces_per_page = 4 + page_index = params[:page] ? params[:page].to_i - 1 : 0 # nice 1-based page -> 0-based page index + + # from display name, pick up user id if one user's traces only + display_name = params[:display_name] + if target_user.nil? and display_name and display_name != '' + target_user = User.find(:first, :conditions => [ "display_name = ?", display_name]) + end opt = Hash.new - opt[:conditions] = ['public = true'] - opt[:order] = 'timestamp DESC' - opt[:limit] = 20 - - if @page > 0 - opt[:offset => 20*@page] + opt[:include] = [:user, :tags] # load users and tags from db at same time as traces + + # four main cases: + # 1 - all traces, logged in = all public traces + all user's (i.e + all mine) + # 2 - all traces, not logged in = all public traces + # 3 - user's traces, logged in as same user = all user's traces + # 4 - user's traces, not logged in as that user = all user's public traces + if target_user.nil? # all traces + if @user + conditions = ["(public = 1 OR user_id = ?)", @user.id] #1 + else + conditions = ["public = 1"] #2 + end + else + if @user and @user.id == target_user.id + conditions = ["user_id = ?", @user.id] #3 (check vs user id, so no join + can't pick up non-public traces by changing name) + else + conditions = ["public = 1 AND user_id = ?", target_user.id] #4 + end end - + conditions[0] += " AND users.display_name != ''" # users need to set display name before traces will be exposed + + opt[:order] = 'timestamp DESC' if params[:tag] - + conditions[0] += " AND gpx_file_tags.tag = ?" + conditions << params[:tag]; + end + + opt[:conditions] = conditions + + # count traces using all options except limit + @max_trace = Trace.count(opt) + @max_page = Integer((@max_trace + 1) / @traces_per_page) + + # last step before fetch - add paging options + opt[:limit] = @traces_per_page + if page_index > 0 + opt[:offset] = @traces_per_page * page_index end @traces = Trace.find(:all , opt) + + # put together SET of tags across traces, for related links + tagset = Hash.new + if @traces + @traces.each do |trace| + trace.tags.reload if params[:tag] # if searched by tag, ActiveRecord won't bring back other tags, so do explicitly here + trace.tags.each do |tag| + tagset[tag.tag] = tag.tag + end + end + end + + # final helper vars for view + @display_name = display_name + @all_tags = tagset.values + @paging_action = paging_action # the action that paging requests should route back to, e.g. 'list' or 'mine' + @page = page_index + 1 # nice 1-based external page numbers + end + + def mine + if @user + list(@user, 'mine') unless @user.nil? + else + redirect_to :controller => 'user', :action => 'login' + end end def view @@ -42,7 +105,8 @@ class TraceController < ApplicationController @trace.timestamp = Time.now if @trace.save logger.info("id is #{@trace.id}") - `mv #{filename} /tmp/#{@trace.id}.gpx` + File.rename(filename, "/tmp/#{@trace.id}.gpx") + # *nix - specific `mv #{filename} /tmp/#{@trace.id}.gpx` flash[:notice] = "Your GPX file has been uploaded and is awaiting insertion in to the database. This will usually happen within half an hour, and an email will be sent to you on completion." end @@ -66,11 +130,11 @@ class TraceController < ApplicationController def picture trace = Trace.find(params[:id]) - send_data(trace.large_picture, :filename => "#{trace.id}.gif", :type => 'image/png', :disposition => 'inline') if trace.public + send_data(trace.large_picture, :filename => "#{trace.id}.gif", :type => 'image/gif', :disposition => 'inline') if trace.public end def icon trace = Trace.find(params[:id]) - send_data(trace.icon_picture, :filename => "#{trace.id}.gif", :type => 'image/gif', :disposition => 'inline') if trace.public + send_data(trace.icon_picture, :filename => "#{trace.id}_icon.gif", :type => 'image/gif', :disposition => 'inline') if trace.public end end diff --git a/app/controllers/way_controller.rb b/app/controllers/way_controller.rb index 10e0f8442..f9fd70af4 100644 --- a/app/controllers/way_controller.rb +++ b/app/controllers/way_controller.rb @@ -1,9 +1,9 @@ -class WayController < ApplicationController +class WayController < ApplicationController require 'xml/libxml' before_filter :authorize after_filter :compress_output - + def create if request.put? way = Way.from_xml(request.raw_post, true) @@ -32,7 +32,7 @@ class WayController < ApplicationController render :nothing => true, :status => 500 # something went very wrong end - def rest + def rest unless Way.exists?(params[:id]) render :nothing => true, :status => 404 return @@ -41,7 +41,7 @@ class WayController < ApplicationController way = Way.find(params[:id]) case request.method - when :get + when :get unless way.visible render :nothing => true, :status => 410 return diff --git a/app/models/node.rb b/app/models/node.rb index 16e681f2c..dfc603376 100644 --- a/app/models/node.rb +++ b/app/models/node.rb @@ -1,6 +1,7 @@ class Node < ActiveRecord::Base require 'xml/libxml' set_table_name 'current_nodes' + validates_numericality_of :latitude validates_numericality_of :longitude diff --git a/app/models/segment.rb b/app/models/segment.rb index 785701c3d..cf0ec2061 100644 --- a/app/models/segment.rb +++ b/app/models/segment.rb @@ -8,8 +8,9 @@ class Segment < ActiveRecord::Base has_many :old_segments, :foreign_key => :id belongs_to :user - has_one :from_node, :class_name => 'Node', :foreign_key => 'id' - has_one :to_node, :class_name => 'Node', :foreign_key => 'id' + # using belongs_to :foreign_key = 'node_*', since if use has_one :foreign_key = 'id', segment preconditions? fails checking for segment id in node table + belongs_to :from_node, :class_name => 'Node', :foreign_key => 'node_a' + belongs_to :to_node, :class_name => 'Node', :foreign_key => 'node_b' def self.from_xml(xml, create=false) p = XML::Parser.new diff --git a/app/models/trace.rb b/app/models/trace.rb index 7c4b5de18..f74b1d009 100644 --- a/app/models/trace.rb +++ b/app/models/trace.rb @@ -10,5 +10,44 @@ class Trace < ActiveRecord::Base tt.tag = tag tt } - end + end + + def large_picture= (data) + f = File.new(large_picture_name, "wb") + f.syswrite(data) + f.close + end + + def icon_picture= (data) + f = File.new(icon_picture_name, "wb") + f.syswrite(data) + f.close + end + + def large_picture + f = File.new(large_picture_name, "rb") + logger.info "large picture file: '#{f.path}', bytes: #{File.size(f.path)}" + data = f.sysread(File.size(f.path)) + logger.info "have read data, bytes: '#{data.length}'" + f.close + data + end + + def icon_picture + f = File.new(icon_picture_name, "rb") + logger.info "icon picture file: '#{f.path}'" + data = f.sysread(File.size(f.path)) + f.close + data + end + + # FIXME change to permanent filestore area + def large_picture_name + "/tmp/#{id}.gif" + end + + # FIXME change to permanent filestore area + def icon_picture_name + "/tmp/#{id}_icon.gif" + end end diff --git a/app/models/user.rb b/app/models/user.rb index c0468f8b0..4c7a26473 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -24,12 +24,12 @@ class User < ActiveRecord::Base write_attribute("pass_crypt_confirm", Digest::MD5.hexdigest(str)) end - def self.authenticate(email, passwd) - find_first([ "email = ? AND pass_crypt =?", email, Digest::MD5.hexdigest(passwd) ]) + def self.authenticate(email, passwd) + find(:first, :conditions => [ "email = ? AND pass_crypt = ?", email, Digest::MD5.hexdigest(passwd)]) end def self.authenticate_token(token) - find_first([ "token = ? ", token]) + find(:first, :conditions => [ "token = ? ", token]) end def self.make_token(length=30) diff --git a/app/models/way.rb b/app/models/way.rb index 55a578acd..d568cb026 100644 --- a/app/models/way.rb +++ b/app/models/way.rb @@ -63,11 +63,16 @@ class Way < ActiveRecord::Base el1['visible'] = self.visible.to_s el1['timestamp'] = self.timestamp.xmlschema - self.way_segments.each do |seg| # FIXME need to make sure they come back in the right order - e = XML::Node.new 'seg' - e['id'] = seg.segment_id.to_s - el1 << e - end + # make sure segments are output in sequence_id order + ordered_segments = [] + self.way_segments.each do |seg| + ordered_segments[seg.sequence_id] = seg.segment_id.to_s + end + ordered_segments.each do |seg_id| + e = XML::Node.new 'seg' + e['id'] = seg_id + el1 << e + end self.way_tags.each do |tag| e = XML::Node.new 'tag' diff --git a/app/views/layouts/site.rhtml b/app/views/layouts/site.rhtml index 75bac67ab..a0034a5ef 100644 --- a/app/views/layouts/site.rhtml +++ b/app/views/layouts/site.rhtml @@ -11,11 +11,11 @@
-<% if @flash[:notice] %> -
<%= @flash[:notice] %>
+<% if flash[:notice] %> +
<%= flash[:notice] %>
<% end %> - <%= @content_for_layout %> + <%= yield %>
@@ -81,6 +81,8 @@ + <%= yield :optionals %> +