initial checkin of full application
This commit is contained in:
45
app/controllers/api_keys_controller.rb
Normal file
45
app/controllers/api_keys_controller.rb
Normal file
@ -0,0 +1,45 @@
|
||||
class ApiKeysController < ApplicationController
|
||||
before_filter :require_user, :set_channels_menu
|
||||
|
||||
def index
|
||||
get_channel_data
|
||||
@read_keys = ApiKey.find(:all, :conditions => { :channel_id => @channel.id, :user_id => current_user.id, :write_flag => 0 })
|
||||
end
|
||||
|
||||
def destroy
|
||||
@api_key = ApiKey.find_by_api_key(params[:api_key])
|
||||
@api_key.delete if @api_key.user_id == current_user.id
|
||||
redirect_to :back
|
||||
end
|
||||
|
||||
def create
|
||||
@channel = Channel.find(params[:channel_id])
|
||||
# make sure channel belongs to current user
|
||||
check_permissions(@channel)
|
||||
|
||||
@api_key = ApiKey.find(:first, :conditions => { :channel_id => @channel.id, :user_id => current_user.id, :write_flag => 1 } )
|
||||
|
||||
# if no api key found or read api key
|
||||
if (@api_key.nil? or params[:write] == '0')
|
||||
@api_key = ApiKey.new
|
||||
@api_key.channel_id = @channel.id
|
||||
@api_key.user_id = current_user.id
|
||||
@api_key.write_flag = params[:write]
|
||||
end
|
||||
|
||||
# set new api key and save
|
||||
@api_key.api_key = generate_api_key
|
||||
@api_key.save
|
||||
|
||||
# redirect
|
||||
redirect_to channel_api_keys_path(@channel.id) and return
|
||||
end
|
||||
|
||||
def update
|
||||
@api_key = ApiKey.find_by_api_key(params[:api_key][:api_key])
|
||||
|
||||
@api_key.note = params[:api_key][:note]
|
||||
@api_key.save if current_user.id == @api_key.user_id
|
||||
redirect_to channel_api_keys_path(@api_key.channel)
|
||||
end
|
||||
end
|
166
app/controllers/application_controller.rb
Normal file
166
app/controllers/application_controller.rb
Normal file
@ -0,0 +1,166 @@
|
||||
class ApplicationController < ActionController::Base
|
||||
# include all helpers for controllers
|
||||
helper :all
|
||||
# include these helper methods for views
|
||||
helper_method :current_user_session, :current_user, :get_header_value
|
||||
protect_from_forgery
|
||||
before_filter :set_variables
|
||||
|
||||
# set up some variables across the entire application
|
||||
def set_variables
|
||||
# hard code locale as english
|
||||
I18n.locale = 'en'
|
||||
# sets timezone for current user, all DateTime outputs will be automatically formatted
|
||||
Time.zone = current_user.time_zone if current_user
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_channels_menu
|
||||
@menu = 'channels'
|
||||
end
|
||||
|
||||
def current_user_session
|
||||
return @current_user_session if defined?(@current_user_session)
|
||||
@current_user_session = UserSession.find
|
||||
end
|
||||
|
||||
def current_user
|
||||
return @current_user if defined?(@current_user)
|
||||
@current_user = current_user_session && current_user_session.record
|
||||
end
|
||||
|
||||
# check that user is logged in
|
||||
def require_user
|
||||
if current_user.nil?
|
||||
redirect_to login_path
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def require_no_user
|
||||
if current_user
|
||||
store_location
|
||||
redirect_to account_path
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def store_location
|
||||
if params[:controller] != "user_sessions"
|
||||
session[:return_to] = request.fullpath
|
||||
end
|
||||
end
|
||||
|
||||
def redirect_back_or_default(default)
|
||||
redirect_to(session[:return_to] || default)
|
||||
session[:return_to] = nil
|
||||
end
|
||||
|
||||
def domain
|
||||
u = request.url
|
||||
begin
|
||||
# the number 12 is the position at which to begin searching for '/', so we don't get the intitial '/' from http://
|
||||
u = u[0..u.index('/', 12)]
|
||||
rescue
|
||||
u += '/'
|
||||
end
|
||||
# uncomment the line below for https support in a production environment
|
||||
#u = u.sub(/http:/, 'https:') if Rails.env == 'production'
|
||||
return u
|
||||
end
|
||||
|
||||
# gets the api key
|
||||
def get_userkey
|
||||
return get_header_value('THINGSPEAKAPIKEY') || params[:key] || params[:api_key] || params[:apikey]
|
||||
end
|
||||
|
||||
# get specified header value
|
||||
def get_header_value(name)
|
||||
value = nil
|
||||
for header in request.env
|
||||
value = header[1] if (header[0].upcase.index(name.upcase))
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
# gets the same data for showing or editing
|
||||
def get_channel_data
|
||||
@channel = Channel.find(params[:channel_id]) if params[:channel_id]
|
||||
@channel = Channel.find(params[:id]) if @channel.nil? and params[:id]
|
||||
@key = ''
|
||||
# make sure channel belongs to current user
|
||||
check_permissions(@channel)
|
||||
|
||||
@api_key = ApiKey.find(:first, :conditions => { :channel_id => @channel.id, :user_id => current_user.id, :write_flag => 1 } )
|
||||
@key = @api_key.api_key if @api_key
|
||||
end
|
||||
|
||||
def check_permissions(channel)
|
||||
render :text => t(:channel_permission) and return if (current_user.nil? || (channel.user_id != current_user.id))
|
||||
end
|
||||
|
||||
# checks permission for channel using api_key
|
||||
def channel_permission?(channel, api_key)
|
||||
if channel.public_flag or (api_key and api_key.channel_id == channel.id) or (current_user and channel.user_id == current_user.id)
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
# outputs error for bad channel
|
||||
def bad_channel_xml
|
||||
channel_unauthorized = Channel.new
|
||||
channel_unauthorized.id = -1
|
||||
return channel_unauthorized.to_xml(:only => :id)
|
||||
end
|
||||
|
||||
# outputs error for bad feed
|
||||
def bad_feed_xml
|
||||
feed_unauthorized = Feed.new
|
||||
feedl_unauthorized.id = -1
|
||||
return feed_unauthorized.to_xml(:only => :entry_id)
|
||||
end
|
||||
|
||||
# generates a database unique api key
|
||||
def generate_api_key(size = 16)
|
||||
alphanumerics = ('0'..'9').to_a + ('A'..'Z').to_a
|
||||
k = (0..size).map {alphanumerics[Kernel.rand(36)]}.join
|
||||
|
||||
# if key exists in database, regenerate key
|
||||
k = generate_api_key if ApiKey.find_by_api_key(k)
|
||||
|
||||
# output the key
|
||||
return k
|
||||
end
|
||||
|
||||
# options: days = how many days ago, start = start date, end = end date, offset = timezone offset
|
||||
def get_date_range(params)
|
||||
# set timezone correctly
|
||||
set_time_zone(params)
|
||||
|
||||
start_date = Time.now - 1.day
|
||||
end_date = Time.now
|
||||
start_date = (Time.now - params[:days].to_i.days) if params[:days]
|
||||
start_date = DateTime.strptime(params[:start]) if params[:start]
|
||||
end_date = DateTime.strptime(params[:end]) if params[:end]
|
||||
date_range = (start_date..end_date)
|
||||
# only get a maximum of 30 days worth of data
|
||||
date_range = (end_date - 30.days..end_date) if (end_date - start_date) > 30.days
|
||||
|
||||
return date_range
|
||||
end
|
||||
|
||||
def set_time_zone(params)
|
||||
# set timezone correctly
|
||||
if params[:offset]
|
||||
Time.zone = params[:offset].to_i
|
||||
elsif current_user
|
||||
Time.zone = current_user.time_zone
|
||||
else
|
||||
Time.zone = 0
|
||||
end
|
||||
end
|
||||
|
||||
end
|
125
app/controllers/channels_controller.rb
Normal file
125
app/controllers/channels_controller.rb
Normal file
@ -0,0 +1,125 @@
|
||||
class ChannelsController < ApplicationController
|
||||
before_filter :require_user, :except => [ :show, :post_data ]
|
||||
before_filter :set_channels_menu
|
||||
protect_from_forgery :except => :post_data
|
||||
|
||||
def index
|
||||
@channels = current_user.channels
|
||||
end
|
||||
|
||||
def show
|
||||
@channel = Channel.find(params[:id]) if params[:id]
|
||||
|
||||
# if owner of channel
|
||||
get_channel_data if current_user and @channel.user_id == current_user.id
|
||||
end
|
||||
|
||||
def edit
|
||||
get_channel_data
|
||||
end
|
||||
|
||||
def update
|
||||
@channel = Channel.find(params[:id])
|
||||
# make sure channel belongs to current user
|
||||
check_permissions(@channel)
|
||||
# protect against bots
|
||||
render :text => '' and return if params[:userlogin].length > 0
|
||||
|
||||
@channel.update_attributes(params[:channel])
|
||||
@channel.name = "#{t(:channel_default_name)} #{@channel.id}" if params[:channel][:name].empty?
|
||||
@channel.save
|
||||
redirect_to channel_path(@channel.id) and return
|
||||
end
|
||||
|
||||
def create
|
||||
# protect against bots
|
||||
render :text => '' and return if params[:userlogin].length > 0
|
||||
|
||||
# get default name for field
|
||||
@d = t(:channel_default_field)
|
||||
|
||||
# add channel with defaults
|
||||
@channel = Channel.new(:field1 => "#{@d}1")
|
||||
@channel.user_id = current_user.id
|
||||
@channel.save
|
||||
|
||||
# now that the channel is saved, we can create the default name
|
||||
@channel.name = "#{t(:channel_default_name)} #{@channel.id}"
|
||||
@channel.save
|
||||
|
||||
# create an api key for this channel
|
||||
@api_key = ApiKey.new
|
||||
@api_key.channel_id = @channel.id
|
||||
@api_key.user_id = current_user.id
|
||||
@api_key.write_flag = 1
|
||||
@api_key.api_key = generate_api_key
|
||||
@api_key.save
|
||||
|
||||
# redirect to edit the newly created channel
|
||||
redirect_to edit_channel_path(@channel.id)
|
||||
end
|
||||
|
||||
def destroy
|
||||
@channel = Channel.find(params[:id])
|
||||
# make sure channel belongs to current user
|
||||
check_permissions(@channel)
|
||||
|
||||
# do the delete
|
||||
@channel.delete
|
||||
redirect_to channels_path
|
||||
end
|
||||
|
||||
# response is '0' if failure, 'entry_id' if success
|
||||
def post_data
|
||||
status = '0'
|
||||
feed = Feed.new
|
||||
|
||||
api_key = ApiKey.find_by_api_key(get_userkey)
|
||||
|
||||
# if write persmission, allow post
|
||||
if (api_key && api_key.write_flag)
|
||||
channel = Channel.find(api_key.channel_id)
|
||||
|
||||
# update entry_id for channel and feed
|
||||
entry_id = channel.last_entry_id.nil? ? 1 : channel.last_entry_id + 1
|
||||
channel.last_entry_id = entry_id
|
||||
feed.entry_id = entry_id
|
||||
|
||||
# try to get created_at datetime if appropriate
|
||||
if params[:created_at]
|
||||
begin
|
||||
@feed.created_at = DateTime.parse(params[:created_at])
|
||||
# if invalid datetime, don't do anything--rails will set created_at
|
||||
rescue
|
||||
end
|
||||
end
|
||||
|
||||
# strip line feeds from end of parameters
|
||||
params.each do |key, value|
|
||||
params[key] = value.sub(/\\n$/, '').sub(/\\r$/, '')
|
||||
end
|
||||
|
||||
# set feed details
|
||||
feed.channel_id = channel.id
|
||||
feed.raw_data = params
|
||||
feed.field1 = params[:field1] if params[:field1]
|
||||
feed.field2 = params[:field2] if params[:field2]
|
||||
feed.field3 = params[:field3] if params[:field3]
|
||||
feed.field4 = params[:field4] if params[:field4]
|
||||
feed.field5 = params[:field5] if params[:field5]
|
||||
feed.field6 = params[:field6] if params[:field6]
|
||||
feed.field7 = params[:field7] if params[:field7]
|
||||
feed.field8 = params[:field8] if params[:field8]
|
||||
feed.status = params[:status] if params[:status]
|
||||
|
||||
if channel.save && feed.save
|
||||
status = entry_id
|
||||
end
|
||||
end
|
||||
|
||||
# output response code
|
||||
render :text => '0', :status => 400 and return if status == '0'
|
||||
render :text => status
|
||||
end
|
||||
|
||||
end
|
78
app/controllers/charts_controller.rb
Normal file
78
app/controllers/charts_controller.rb
Normal file
@ -0,0 +1,78 @@
|
||||
class ChartsController < ApplicationController
|
||||
|
||||
def index
|
||||
set_channels_menu
|
||||
@channel = Channel.find(params[:channel_id])
|
||||
@channel_id = params[:channel_id]
|
||||
@domain = domain
|
||||
|
||||
# default chart size
|
||||
@width = default_width
|
||||
@height = default_height
|
||||
|
||||
check_permissions(@channel)
|
||||
end
|
||||
|
||||
def show
|
||||
# allow these parameters when creating feed querystring
|
||||
feed_params = ['key','days','start','end','round','timescale','average','median','sum']
|
||||
|
||||
# default chart size
|
||||
@width = default_width
|
||||
@height = default_height
|
||||
|
||||
# add extra parameters to querystring
|
||||
@qs = ''
|
||||
params.each do |p|
|
||||
@qs += "&#{p[0]}=#{p[1]}" if feed_params.include?(p[0])
|
||||
end
|
||||
|
||||
# fix chart colors if necessary
|
||||
params[:color] = fix_color(params[:color])
|
||||
params[:bgcolor] = fix_color(params[:bgcolor])
|
||||
|
||||
@domain = domain
|
||||
render :layout => false
|
||||
end
|
||||
|
||||
# save chart options
|
||||
def update
|
||||
@channel = Channel.find(params[:channel_id])
|
||||
@status = 0
|
||||
|
||||
# check permissions
|
||||
if @channel.user_id == current_user.id
|
||||
|
||||
# save data
|
||||
@channel["options#{params[:id]}"] = params[:options]
|
||||
if @channel.save
|
||||
@status = 1
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# return response: 1=success, 0=failure
|
||||
render :json => @status.to_json
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def default_width
|
||||
450
|
||||
end
|
||||
|
||||
def default_height
|
||||
250
|
||||
end
|
||||
|
||||
# fixes chart color if user forgets the leading '#'
|
||||
def fix_color(color)
|
||||
# check for 3 or 6 character hexadecimal value
|
||||
if (color and color.match(/^([0-9]|[a-f]|[A-F]){3}(([0-9]|[a-f]|[A-F]){3})?$/))
|
||||
color = '#' + color
|
||||
end
|
||||
|
||||
return color
|
||||
end
|
||||
|
||||
end
|
501
app/controllers/feed_controller.rb
Normal file
501
app/controllers/feed_controller.rb
Normal file
@ -0,0 +1,501 @@
|
||||
class FeedController < ApplicationController
|
||||
require 'csv'
|
||||
|
||||
def index
|
||||
channel = Channel.find(params[:channel_id])
|
||||
api_key = ApiKey.find_by_api_key(get_userkey)
|
||||
@success = channel_permission?(channel, api_key)
|
||||
|
||||
# check for access
|
||||
if @success
|
||||
# create options hash
|
||||
channel_options = { :only => channel_select_data(channel) }
|
||||
select_options = feed_select_data(channel)
|
||||
|
||||
# get feed based on conditions
|
||||
feeds = Feed.find(
|
||||
:all,
|
||||
:conditions => { :channel_id => channel.id, :created_at => get_date_range(params) },
|
||||
:select => select_options,
|
||||
:order => 'created_at'
|
||||
)
|
||||
|
||||
# if a feed has data
|
||||
if !feeds.empty?
|
||||
# convert to timescales if necessary
|
||||
if timeparam_valid?(params[:timescale])
|
||||
feeds = feeds_into_timescales(feeds)
|
||||
# convert to sums if necessary
|
||||
elsif timeparam_valid?(params[:sum])
|
||||
feeds = feeds_into_sums(feeds)
|
||||
# convert to averages if necessary
|
||||
elsif timeparam_valid?(params[:average])
|
||||
feeds = feeds_into_averages(feeds)
|
||||
# convert to medians if necessary
|
||||
elsif timeparam_valid?(params[:median])
|
||||
feeds = feeds_into_medians(feeds)
|
||||
end
|
||||
end
|
||||
|
||||
# set output correctly
|
||||
if params[:format] == 'xml'
|
||||
@channel_output = channel.to_xml(channel_options).sub('</channel>', '').strip
|
||||
@feed_output = feeds.to_xml(:skip_instruct => true).gsub(/\n/, "\n ").chop.chop
|
||||
elsif params[:format] == 'csv'
|
||||
@feed_output = feeds
|
||||
else
|
||||
@channel_output = channel.to_json(channel_options).chop
|
||||
@feed_output = feeds.to_json
|
||||
end
|
||||
|
||||
# else no access, set error code
|
||||
else
|
||||
if params[:format] == 'xml'
|
||||
@channel_output = bad_channel_xml
|
||||
else
|
||||
@channel_output = '-1'.to_json
|
||||
end
|
||||
end
|
||||
|
||||
# set callback for jsonp
|
||||
@callback = params[:callback] if params[:callback]
|
||||
|
||||
# set csv headers if necessary
|
||||
@csv_headers = select_options if params[:format] == 'csv'
|
||||
|
||||
# output proper http response if error
|
||||
render :text => '-1', :status => 400 and return if !@success
|
||||
|
||||
# output data in proper format
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json
|
||||
format.xml
|
||||
format.csv
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
@channel = Channel.find(params[:channel_id])
|
||||
@api_key = ApiKey.find_by_api_key(get_userkey)
|
||||
output = '-1'
|
||||
|
||||
# get most recent entry if necessary
|
||||
params[:id] = @channel.last_entry_id if params[:id] == 'last'
|
||||
|
||||
# set timezone correctly
|
||||
set_time_zone(params)
|
||||
|
||||
@feed = Feed.find(
|
||||
:first,
|
||||
:conditions => { :channel_id => @channel.id, :entry_id => params[:id] },
|
||||
:select => feed_select_data(@channel)
|
||||
)
|
||||
@success = channel_permission?(@channel, @api_key)
|
||||
|
||||
# check for access
|
||||
if @success
|
||||
# set output correctly
|
||||
if params[:format] == 'xml'
|
||||
output = @feed.to_xml
|
||||
elsif params[:format] == 'csv'
|
||||
@csv_headers = feed_select_data(@channel)
|
||||
elsif (params[:format] == 'txt' or params[:format] == 'text')
|
||||
output = add_prepend_append(@feed["field#{params[:field_id]}"])
|
||||
else
|
||||
output = @feed.to_json
|
||||
end
|
||||
# else set error code
|
||||
else
|
||||
if params[:format] == 'xml'
|
||||
output = bad_feed_xml
|
||||
else
|
||||
output = '-1'.to_json
|
||||
end
|
||||
end
|
||||
|
||||
# output data in proper format
|
||||
respond_to do |format|
|
||||
format.html { render :json => output }
|
||||
format.json { render :json => output, :callback => params[:callback] }
|
||||
format.xml { render :xml => output }
|
||||
format.csv
|
||||
format.text { render :text => output }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# only output these fields for channel
|
||||
def channel_select_data(channel)
|
||||
only = [:name, :created_at, :updated_at, :id, :last_entry_id]
|
||||
only += [:description] unless channel.description.blank?
|
||||
only += [:latitude] unless channel.latitude.blank?
|
||||
only += [:longitude] unless channel.longitude.blank?
|
||||
only += [:elevation] unless channel.elevation.blank?
|
||||
only += [:field1] unless channel.field1.blank?
|
||||
only += [:field2] unless channel.field2.blank?
|
||||
only += [:field3] unless channel.field3.blank?
|
||||
only += [:field4] unless channel.field4.blank?
|
||||
only += [:field5] unless channel.field5.blank?
|
||||
only += [:field6] unless channel.field6.blank?
|
||||
only += [:field7] unless channel.field7.blank?
|
||||
only += [:field8] unless channel.field8.blank?
|
||||
|
||||
return only
|
||||
end
|
||||
|
||||
# only output these fields for feed
|
||||
def feed_select_data(channel)
|
||||
only = [:created_at]
|
||||
only += [:entry_id] unless timeparam_valid?(params[:timescale]) or timeparam_valid?(params[:average]) or timeparam_valid?(params[:median]) or timeparam_valid?(params[:sum])
|
||||
only += [:field1] unless channel.field1.blank? or (params[:field_id] and params[:field_id] != '1')
|
||||
only += [:field2] unless channel.field2.blank? or (params[:field_id] and params[:field_id] != '2')
|
||||
only += [:field3] unless channel.field3.blank? or (params[:field_id] and params[:field_id] != '3')
|
||||
only += [:field4] unless channel.field4.blank? or (params[:field_id] and params[:field_id] != '4')
|
||||
only += [:field5] unless channel.field5.blank? or (params[:field_id] and params[:field_id] != '5')
|
||||
only += [:field6] unless channel.field6.blank? or (params[:field_id] and params[:field_id] != '6')
|
||||
only += [:field7] unless channel.field7.blank? or (params[:field_id] and params[:field_id] != '7')
|
||||
only += [:field8] unless channel.field8.blank? or (params[:field_id] and params[:field_id] != '8')
|
||||
only += [:status] if params[:status] and params[:status].upcase == 'TRUE'
|
||||
|
||||
return only
|
||||
end
|
||||
|
||||
# checks for valid timescale
|
||||
def timeparam_valid?(timeparam)
|
||||
valid_minutes = [10, 15, 20, 30, 60, 240, 720, 1440]
|
||||
if timeparam and valid_minutes.include?(timeparam.to_i)
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
# slice feed into timescales
|
||||
def feeds_into_timescales(feeds)
|
||||
# convert timescale (minutes) into seconds
|
||||
seconds = params[:timescale].to_i * 60
|
||||
# get floored time ranges
|
||||
start_time = get_floored_time(feeds.first.created_at, seconds)
|
||||
end_time = get_floored_time(feeds.last.created_at, seconds)
|
||||
|
||||
# create empty array with appropriate size
|
||||
timeslices = Array.new(((end_time - start_time) / seconds).floor)
|
||||
|
||||
# create a blank clone of the first feed so that we only get the necessary attributes
|
||||
empty_feed = create_empty_clone(feeds.first)
|
||||
|
||||
# add feeds to array
|
||||
feeds.each do |f|
|
||||
i = ((f.created_at - start_time) / seconds).floor
|
||||
f.created_at = start_time + i * seconds
|
||||
timeslices[i] = f if timeslices[i].nil?
|
||||
end
|
||||
|
||||
# fill in empty array elements
|
||||
timeslices.each_index do |i|
|
||||
if timeslices[i].nil?
|
||||
current_feed = empty_feed.clone
|
||||
current_feed.created_at = (start_time + (i * seconds))
|
||||
timeslices[i] = current_feed
|
||||
end
|
||||
end
|
||||
|
||||
return timeslices
|
||||
end
|
||||
|
||||
# slice feed into averages
|
||||
def feeds_into_averages(feeds)
|
||||
# convert timescale (minutes) into seconds
|
||||
seconds = params[:average].to_i * 60
|
||||
# get floored time ranges
|
||||
start_time = get_floored_time(feeds.first.created_at, seconds)
|
||||
end_time = get_floored_time(feeds.last.created_at, seconds)
|
||||
|
||||
# create empty array with appropriate size
|
||||
timeslices = Array.new(((end_time - start_time) / seconds).floor)
|
||||
|
||||
# create a blank clone of the first feed so that we only get the necessary attributes
|
||||
empty_feed = create_empty_clone(feeds.first)
|
||||
|
||||
# add feeds to array
|
||||
feeds.each do |f|
|
||||
i = ((f.created_at - start_time) / seconds).floor
|
||||
f.created_at = start_time + i * seconds
|
||||
# create multidimensional array
|
||||
timeslices[i] = [] if timeslices[i].nil?
|
||||
timeslices[i].push(f)
|
||||
end
|
||||
|
||||
# keep track of whether numbers use commas as decimals
|
||||
comma_flag = false
|
||||
|
||||
# fill in array
|
||||
timeslices.each_index do |i|
|
||||
# insert empty values
|
||||
if timeslices[i].nil?
|
||||
current_feed = empty_feed.clone
|
||||
current_feed.created_at = (start_time + (i * seconds))
|
||||
timeslices[i] = current_feed
|
||||
# else average the inner array
|
||||
else
|
||||
sum_feed = empty_feed.clone
|
||||
sum_feed.created_at = timeslices[i].first.created_at
|
||||
# for each feed
|
||||
timeslices[i].each do |f|
|
||||
# for each attribute, add to sum_feed so that we have the total
|
||||
sum_feed.attribute_names.each do |attr|
|
||||
|
||||
# only add non-null integer fields
|
||||
if attr.index('field') and !f[attr].nil? and is_a_number?(f[attr])
|
||||
# set comma_flag once if we find a number with a comma
|
||||
comma_flag = true if !comma_flag and f[attr].to_s.index(',')
|
||||
|
||||
# set initial data
|
||||
if sum_feed[attr].nil?
|
||||
sum_feed[attr] = parsefloat(f[attr])
|
||||
# add data
|
||||
elsif f[attr]
|
||||
sum_feed[attr] = parsefloat(sum_feed[attr]) + parsefloat(f[attr])
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
# set to the averaged feed
|
||||
timeslices[i] = object_average(sum_feed, timeslices[i].length, comma_flag, params[:round])
|
||||
end
|
||||
end
|
||||
|
||||
return timeslices
|
||||
end
|
||||
|
||||
# slice feed into medians
|
||||
def feeds_into_medians(feeds)
|
||||
# convert timescale (minutes) into seconds
|
||||
seconds = params[:median].to_i * 60
|
||||
# get floored time ranges
|
||||
start_time = get_floored_time(feeds.first.created_at, seconds)
|
||||
end_time = get_floored_time(feeds.last.created_at, seconds)
|
||||
|
||||
# create empty array with appropriate size
|
||||
timeslices = Array.new(((end_time - start_time) / seconds).floor)
|
||||
|
||||
# create a blank clone of the first feed so that we only get the necessary attributes
|
||||
empty_feed = create_empty_clone(feeds.first)
|
||||
|
||||
# add feeds to array
|
||||
feeds.each do |f|
|
||||
i = ((f.created_at - start_time) / seconds).floor
|
||||
f.created_at = start_time + i * seconds
|
||||
# create multidimensional array
|
||||
timeslices[i] = [] if timeslices[i].nil?
|
||||
timeslices[i].push(f)
|
||||
end
|
||||
|
||||
# keep track of whether numbers use commas as decimals
|
||||
comma_flag = false
|
||||
|
||||
# fill in array
|
||||
timeslices.each_index do |i|
|
||||
# insert empty values
|
||||
if timeslices[i].nil?
|
||||
current_feed = empty_feed.clone
|
||||
current_feed.created_at = (start_time + (i * seconds))
|
||||
timeslices[i] = current_feed
|
||||
# else get median values for the inner array
|
||||
else
|
||||
|
||||
# create blank hash called 'fields' to hold data
|
||||
fields = {}
|
||||
|
||||
# for each feed
|
||||
timeslices[i].each do |f|
|
||||
|
||||
# for each attribute
|
||||
f.attribute_names.each do |attr|
|
||||
if attr.index('field')
|
||||
|
||||
# create blank array for each field
|
||||
fields["#{attr}"] = [] if fields["#{attr}"].nil?
|
||||
|
||||
# push numeric field data onto its array
|
||||
if is_a_number?(f[attr])
|
||||
# set comma_flag once if we find a number with a comma
|
||||
comma_flag = true if !comma_flag and f[attr].to_s.index(',')
|
||||
|
||||
fields["#{attr}"].push(parsefloat(f[attr]))
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# sort fields arrays
|
||||
fields.each_key do |key|
|
||||
fields[key] = fields[key].compact.sort
|
||||
end
|
||||
|
||||
# get the median
|
||||
median_feed = empty_feed.clone
|
||||
median_feed.created_at = timeslices[i].first.created_at
|
||||
median_feed.attribute_names.each do |attr|
|
||||
median_feed[attr] = object_median(fields[attr], comma_flag, params[:round]) if attr.index('field')
|
||||
end
|
||||
|
||||
timeslices[i] = median_feed
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
return timeslices
|
||||
end
|
||||
|
||||
# slice feed into sums
|
||||
def feeds_into_sums(feeds)
|
||||
# convert timescale (minutes) into seconds
|
||||
seconds = params[:sum].to_i * 60
|
||||
# get floored time ranges
|
||||
start_time = get_floored_time(feeds.first.created_at, seconds)
|
||||
end_time = get_floored_time(feeds.last.created_at, seconds)
|
||||
|
||||
# create empty array with appropriate size
|
||||
timeslices = Array.new(((end_time - start_time) / seconds).floor)
|
||||
|
||||
# create a blank clone of the first feed so that we only get the necessary attributes
|
||||
empty_feed = create_empty_clone(feeds.first)
|
||||
|
||||
# add feeds to array
|
||||
feeds.each do |f|
|
||||
i = ((f.created_at - start_time) / seconds).floor
|
||||
f.created_at = start_time + i * seconds
|
||||
# create multidimensional array
|
||||
timeslices[i] = [] if timeslices[i].nil?
|
||||
timeslices[i].push(f)
|
||||
end
|
||||
|
||||
# keep track of whether numbers use commas as decimals
|
||||
comma_flag = false
|
||||
|
||||
# fill in array
|
||||
timeslices.each_index do |i|
|
||||
# insert empty values
|
||||
if timeslices[i].nil?
|
||||
current_feed = empty_feed.clone
|
||||
current_feed.created_at = (start_time + (i * seconds))
|
||||
timeslices[i] = current_feed
|
||||
# else sum the inner array
|
||||
else
|
||||
sum_feed = empty_feed.clone
|
||||
sum_feed.created_at = timeslices[i].first.created_at
|
||||
# for each feed
|
||||
timeslices[i].each do |f|
|
||||
# for each attribute, add to sum_feed so that we have the total
|
||||
sum_feed.attribute_names.each do |attr|
|
||||
# only add non-null integer fields
|
||||
if attr.index('field') and !f[attr].nil? and is_a_number?(f[attr])
|
||||
|
||||
# set comma_flag once if we find a number with a comma
|
||||
comma_flag = true if !comma_flag and f[attr].to_s.index(',')
|
||||
|
||||
# set initial data
|
||||
if sum_feed[attr].nil?
|
||||
sum_feed[attr] = parsefloat(f[attr])
|
||||
# add data
|
||||
elsif f[attr]
|
||||
sum_feed[attr] = parsefloat(sum_feed[attr]) + parsefloat(f[attr])
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# set to the summed feed
|
||||
timeslices[i] = object_sum(sum_feed, comma_flag, params[:round])
|
||||
end
|
||||
end
|
||||
|
||||
return timeslices
|
||||
end
|
||||
|
||||
def is_a_number?(s)
|
||||
s.to_s.gsub(/,/, '.').match(/\A[+-]?\d+?(\.\d+)?\Z/) == nil ? false : true
|
||||
end
|
||||
|
||||
def parsefloat(number)
|
||||
return number.to_s.gsub(/,/, '.').to_f
|
||||
end
|
||||
|
||||
# gets the median for an object
|
||||
def object_median(object, comma_flag=false, round=nil)
|
||||
return nil if object.nil?
|
||||
length = object.length
|
||||
return nil if length == 0
|
||||
output = ''
|
||||
|
||||
# do the calculation
|
||||
if length % 2 == 0
|
||||
output = (object[(length - 1) / 2] + object[length / 2]) / 2
|
||||
else
|
||||
output = object[(length - 1) / 2]
|
||||
end
|
||||
|
||||
output = sprintf "%.#{round}f", output if round and is_a_number?(output)
|
||||
|
||||
# replace decimals with commas if appropriate
|
||||
output = output.to_s.gsub(/\./, ',') if comma_flag
|
||||
|
||||
return output.to_s
|
||||
end
|
||||
|
||||
# averages a summed object over length
|
||||
def object_average(object, length, comma_flag=false, round=nil)
|
||||
object.attribute_names.each do |attr|
|
||||
# only average non-null integer fields
|
||||
if !object[attr].nil? and is_a_number?(object[attr])
|
||||
if round
|
||||
object[attr] = sprintf "%.#{round}f", (parsefloat(object[attr]) / length)
|
||||
else
|
||||
object[attr] = (parsefloat(object[attr]) / length).to_s
|
||||
end
|
||||
# replace decimals with commas if appropriate
|
||||
object[attr] = object[attr].gsub(/\./, ',') if comma_flag
|
||||
end
|
||||
end
|
||||
|
||||
return object
|
||||
end
|
||||
|
||||
# formats a summed object correctly
|
||||
def object_sum(object, comma_flag=false, round=nil)
|
||||
object.attribute_names.each do |attr|
|
||||
# only average non-null integer fields
|
||||
if !object[attr].nil? and is_a_number?(object[attr])
|
||||
if round
|
||||
object[attr] = sprintf "%.#{round}f", parsefloat(object[attr])
|
||||
else
|
||||
object[attr] = parsefloat(object[attr]).to_s
|
||||
end
|
||||
# replace decimals with commas if appropriate
|
||||
object[attr] = object[attr].gsub(/\./, ',') if comma_flag
|
||||
end
|
||||
end
|
||||
|
||||
return object
|
||||
end
|
||||
|
||||
# creates an empty clone of an object
|
||||
def create_empty_clone(object)
|
||||
empty_clone = object.clone
|
||||
empty_clone.attribute_names.each { |attr| empty_clone[attr] = nil }
|
||||
return empty_clone
|
||||
end
|
||||
|
||||
# gets time floored to proper interval
|
||||
def get_floored_time(input_time, seconds)
|
||||
return Time.zone.at((input_time.to_f / seconds).floor * seconds)
|
||||
end
|
||||
|
||||
end
|
23
app/controllers/mailer_controller.rb
Normal file
23
app/controllers/mailer_controller.rb
Normal file
@ -0,0 +1,23 @@
|
||||
class MailerController < ApplicationController
|
||||
|
||||
def resetpassword
|
||||
# protect against bots
|
||||
render :text => '' and return if params[:userlogin].length > 0
|
||||
|
||||
@user = User.find_by_login_or_email(params[:user][:login])
|
||||
if @user.nil?
|
||||
sleep 2
|
||||
session[:mail_message] = t(:account_not_found)
|
||||
else
|
||||
begin
|
||||
@user.reset_perishable_token!
|
||||
#Mailer.password_reset(@user, "https://www.thingspeak.com/users/reset_password/#{@user.id}?token=#{@user.perishable_token}").deliver
|
||||
session[:mail_message] = t(:password_reset_mailed)
|
||||
rescue
|
||||
session[:mail_message] = t(:password_reset_error)
|
||||
end
|
||||
end
|
||||
redirect_to :controller => 'user_session', :action => 'new'
|
||||
end
|
||||
|
||||
end
|
7
app/controllers/pages_controller.rb
Normal file
7
app/controllers/pages_controller.rb
Normal file
@ -0,0 +1,7 @@
|
||||
class PagesController < ApplicationController
|
||||
|
||||
def home
|
||||
@menu = 'home'
|
||||
end
|
||||
|
||||
end
|
113
app/controllers/status_controller.rb
Normal file
113
app/controllers/status_controller.rb
Normal file
@ -0,0 +1,113 @@
|
||||
class StatusController < ApplicationController
|
||||
require 'csv'
|
||||
|
||||
def index
|
||||
@channel = Channel.find(params[:channel_id])
|
||||
@api_key = ApiKey.find_by_api_key(get_userkey)
|
||||
@success = channel_permission?(@channel, @api_key)
|
||||
|
||||
# check for access
|
||||
if @success
|
||||
# create options hash
|
||||
channel_options = { :only => channel_select_terse(@channel) }
|
||||
|
||||
# display only 1 day by default
|
||||
params[:days] = 1 if !params[:days]
|
||||
|
||||
# get feed based on conditions
|
||||
@feeds = Feed.find(
|
||||
:all,
|
||||
:conditions => { :channel_id => @channel.id, :created_at => get_date_range(params) },
|
||||
:select => [:created_at, :status],
|
||||
:order => 'created_at'
|
||||
)
|
||||
|
||||
# set output correctly
|
||||
if params[:format] == 'xml'
|
||||
@channel_xml = @channel.to_xml(channel_options).sub('</channel>', '').strip
|
||||
@feed_xml = @feeds.to_xml(:skip_instruct => true).gsub(/\n/, "\n ").chop.chop
|
||||
elsif params[:format] == 'csv'
|
||||
@csv_headers = [:created_at, :status]
|
||||
else
|
||||
@channel_json = @channel.to_json(channel_options).chop
|
||||
@feed_json = @feeds.to_json
|
||||
end
|
||||
# else set error code
|
||||
else
|
||||
if params[:format] == 'xml'
|
||||
@channel_xml = bad_channel_xml
|
||||
else
|
||||
@channel_json = '-1'.to_json
|
||||
end
|
||||
end
|
||||
|
||||
# set callback for jsonp
|
||||
@callback = params[:callback] if params[:callback]
|
||||
|
||||
# output data in proper format
|
||||
respond_to do |format|
|
||||
format.html { render :text => @feed_json }
|
||||
format.json { render :action => 'feed/index' }
|
||||
format.xml { render :action => 'feed/index' }
|
||||
format.csv { render :action => 'feed/index' }
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
@channel = Channel.find(params[:channel_id])
|
||||
@api_key = ApiKey.find_by_api_key(params[:key])
|
||||
output = '-1'
|
||||
|
||||
# get most recent entry if necessary
|
||||
params[:id] = @channel.last_entry_id if params[:id] == 'last'
|
||||
|
||||
@feed = Feed.find(
|
||||
:first,
|
||||
:conditions => { :channel_id => @channel.id, :entry_id => params[:id] },
|
||||
:select => [:created_at, :status]
|
||||
)
|
||||
@success = channel_permission?(@channel, @api_key)
|
||||
|
||||
# check for access
|
||||
if @success
|
||||
# set output correctly
|
||||
if params[:format] == 'xml'
|
||||
output = @feed.to_xml
|
||||
elsif params[:format] == 'csv'
|
||||
@csv_headers = [:created_at, :entry_id, :status]
|
||||
elsif (params[:format] == 'txt' or params[:format] == 'text')
|
||||
output = add_prepend_append(@feed.status)
|
||||
else
|
||||
output = @feed.to_json
|
||||
end
|
||||
# else set error code
|
||||
else
|
||||
if params[:format] == 'xml'
|
||||
output = bad_feed_xml
|
||||
else
|
||||
output = '-1'.to_json
|
||||
end
|
||||
end
|
||||
|
||||
# output data in proper format
|
||||
respond_to do |format|
|
||||
format.html { render :json => output }
|
||||
format.json { render :json => output, :callback => params[:callback] }
|
||||
format.xml { render :xml => output }
|
||||
format.csv { render :action => 'feed/show' }
|
||||
format.text { render :text => output }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
# only output these fields for channel
|
||||
def channel_select_terse(channel)
|
||||
only = [:name]
|
||||
only += [:latitude] unless channel.latitude.nil?
|
||||
only += [:longitude] unless channel.longitude.nil?
|
||||
only += [:elevation] unless channel.elevation.nil? or channel.elevation.empty?
|
||||
|
||||
return only
|
||||
end
|
||||
|
||||
end
|
15
app/controllers/subdomains_controller.rb
Normal file
15
app/controllers/subdomains_controller.rb
Normal file
@ -0,0 +1,15 @@
|
||||
class SubdomainsController < ApplicationController
|
||||
|
||||
# show a blank page if subdomain
|
||||
def index
|
||||
render :text => ''
|
||||
end
|
||||
|
||||
# output the file crossdomain.xml.erb
|
||||
def crossdomain
|
||||
respond_to do |format|
|
||||
format.xml
|
||||
end
|
||||
end
|
||||
|
||||
end
|
44
app/controllers/user_sessions_controller.rb
Normal file
44
app/controllers/user_sessions_controller.rb
Normal file
@ -0,0 +1,44 @@
|
||||
class UserSessionsController < ApplicationController
|
||||
before_filter :require_no_user, :only => [:new, :create]
|
||||
before_filter :require_user, :only => :destroy
|
||||
|
||||
def new
|
||||
@title = t(:signin)
|
||||
@user_session = UserSession.new
|
||||
@mail_message = session[:mail_message] if !session[:mail_message].nil?
|
||||
end
|
||||
|
||||
def show
|
||||
redirect_to root_path
|
||||
end
|
||||
|
||||
def create
|
||||
if params[:userlogin].length > 0
|
||||
render :text => ''
|
||||
else
|
||||
@user_session = UserSession.new(params[:user_session])
|
||||
|
||||
# remember user_id if checkbox is checked
|
||||
if params[:user_session][:remember_id] == '1'
|
||||
cookies['user_id'] = { :value => params[:user_session][:login], :expires => 1.month.from_now }
|
||||
else
|
||||
cookies.delete 'user_id'
|
||||
end
|
||||
|
||||
if @user_session.save
|
||||
redirect_to root_path and return
|
||||
else
|
||||
# prevent timing and brute force password attacks
|
||||
sleep 1
|
||||
@failed = true
|
||||
render :action => :new
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
current_user_session.destroy
|
||||
reset_session
|
||||
redirect_to root_path
|
||||
end
|
||||
end
|
92
app/controllers/users_controller.rb
Normal file
92
app/controllers/users_controller.rb
Normal file
@ -0,0 +1,92 @@
|
||||
class UsersController < ApplicationController
|
||||
before_filter :require_no_user, :only => [:new, :create, :forgot_password]
|
||||
before_filter :require_user, :only => [:show, :edit, :update, :change_password]
|
||||
|
||||
def new
|
||||
@title = t(:signup)
|
||||
@user = User.new
|
||||
end
|
||||
|
||||
def create
|
||||
# protect against bots
|
||||
render :text => '' and return if params[:userlogin].length > 0
|
||||
|
||||
# check for invite code
|
||||
render :text => 'Sorry, you currently need an invite code to sign up.' and return if params[:invite] != '4224'
|
||||
|
||||
@user = User.new(params[:user])
|
||||
|
||||
# save user
|
||||
if @user.valid?
|
||||
if @user.save
|
||||
redirect_back_or_default account_path and return
|
||||
end
|
||||
else
|
||||
render :action => :new
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def show
|
||||
@menu = 'account'
|
||||
@user = @current_user
|
||||
end
|
||||
|
||||
def edit
|
||||
@menu = 'account'
|
||||
@user = @current_user
|
||||
end
|
||||
|
||||
# displays forgot password page
|
||||
def forgot_password
|
||||
@user = User.new
|
||||
end
|
||||
|
||||
# this action is called from an email link when a password reset is requested
|
||||
def reset_password
|
||||
# if user has been logged in (due to previous form submission)
|
||||
if !current_user.nil?
|
||||
@user = current_user
|
||||
@user.errors.add_to_base(t(:password_problem))
|
||||
@valid_link = true
|
||||
else
|
||||
@user = User.find_by_id(params[:id])
|
||||
# make sure tokens match and password reset is within last 10 minutes
|
||||
if @user.perishable_token == params[:token] && @user.updated_at > 600.seconds.ago
|
||||
@valid_link = true
|
||||
# log the user in
|
||||
@user_session = UserSession.new(@user)
|
||||
@user_session.save
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# do the actual password change
|
||||
def change_password
|
||||
# protect against bots
|
||||
render :text => '' and return if params[:userlogin].length > 0
|
||||
|
||||
@user = current_user
|
||||
# if no password entered, redirect
|
||||
redirect_to reset_password_path and return if params[:user][:password].empty?
|
||||
# check current password and update
|
||||
if @user.update_attributes(params[:user])
|
||||
redirect_to account_path
|
||||
else
|
||||
redirect_to reset_password_path
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
@menu = 'account'
|
||||
@user = @current_user # makes our views "cleaner" and more consistent
|
||||
# check current password and update
|
||||
if @user.valid_password?(params[:password_current]) && @user.update_attributes(params[:user])
|
||||
redirect_to account_path
|
||||
else
|
||||
@user.errors.add_to_base(t(:password_incorrect))
|
||||
render :action => :edit
|
||||
end
|
||||
end
|
||||
|
||||
end
|
Reference in New Issue
Block a user