621 lines
22 KiB
Ruby
621 lines
22 KiB
Ruby
class ChannelsController < ApplicationController
|
|
include ChannelsHelper, ApiKeys
|
|
before_filter :authenticate_via_api_key!, :only => [:index]
|
|
before_filter :require_user, :except => [:realtime, :realtime_update, :show, :post_data, :social_show, :social_feed, :public]
|
|
before_filter :set_channels_menu
|
|
layout 'application', :except => [:social_show, :social_feed]
|
|
protect_from_forgery :except => [:realtime, :realtime_update, :post_data, :create, :destroy, :clear]
|
|
require 'csv'
|
|
require 'will_paginate/array'
|
|
|
|
# get list of all realtime channels
|
|
def realtime
|
|
# error if no key
|
|
respond_with_error(:error_auth_required) and return if params[:realtime_key] != REALTIME_DAEMON_KEY
|
|
channels = Channel.where("realtime_io_serial_number IS NOT NULL")
|
|
render :json => channels.to_json(:root => false, :only => [:id, :realtime_io_serial_number])
|
|
end
|
|
|
|
# view list of watched channels
|
|
def watched
|
|
@channels = current_user.watched_channels
|
|
end
|
|
|
|
# user watches a channel
|
|
def watch
|
|
@watching = Watching.find_by_user_id_and_channel_id(current_user.id, params[:id])
|
|
|
|
# add watching
|
|
if params[:flag] == 'true'
|
|
@watching = Watching.new(:user_id => current_user.id, :channel_id => params[:id]) if @watching.nil?
|
|
@watching.save
|
|
# delete watching
|
|
else
|
|
@watching.delete if !@watching.nil?
|
|
end
|
|
|
|
render :text => '1'
|
|
end
|
|
|
|
# list public channels
|
|
def public
|
|
# error if page 0
|
|
respond_with_error(:error_resource_not_found) and return if params[:page] == '0'
|
|
|
|
@domain = domain
|
|
# default blank response
|
|
@channels = Channel.where(:id => 0).paginate :page => params[:page]
|
|
|
|
# get channels by ids
|
|
if params[:channel_ids].present?
|
|
@header = t(:selected_channels)
|
|
@channels = Channel.public_viewable.by_array(params[:channel_ids]).order('ranking desc, updated_at DESC').paginate :page => params[:page]
|
|
# get channels that match a user
|
|
elsif params[:username].present?
|
|
@header = "#{t(:user).capitalize}: #{params[:username]}"
|
|
searched_user = User.find_by_login(params[:username])
|
|
@channels = searched_user.channels.public_viewable.active.order('ranking desc, updated_at DESC').paginate :page => params[:page] if searched_user.present?
|
|
# get channels that match a tag
|
|
elsif params[:tag].present?
|
|
@header = "#{t(:tag).capitalize}: #{params[:tag]}"
|
|
@channels = Channel.public_viewable.active.order('ranking desc, updated_at DESC').with_tag(params[:tag]).paginate :page => params[:page]
|
|
# get channels by location
|
|
elsif params[:latitude].present? && params[:longitude].present? && params[:distance].present?
|
|
@header = "#{t(:channels_near)}: [#{params[:latitude]}, #{params[:longitude]}]"
|
|
@channels = Channel.location_search(params).paginate :page => params[:page]
|
|
# normal channel list
|
|
else
|
|
@header = t(:featured_channels)
|
|
@channels = Channel.public_viewable.active.order('ranking desc, updated_at DESC').paginate :page => params[:page]
|
|
end
|
|
|
|
respond_to do |format|
|
|
format.html
|
|
format.json { render :json => Channel.paginated_hash(@channels).to_json }
|
|
format.xml { render :xml => Channel.paginated_hash(@channels).to_xml(:root => 'response') }
|
|
end
|
|
end
|
|
|
|
# widget for social feeds
|
|
def social_feed
|
|
# get domain based on ssl
|
|
@domain = domain((get_header_value('x_ssl') == 'true'))
|
|
end
|
|
|
|
# main page for a socialsensornetwork.com project
|
|
def social_show
|
|
@channel = Channel.find_by_slug(params[:slug])
|
|
|
|
# redirect home if wrong slug
|
|
redirect_to '/' and return if @channel.nil?
|
|
|
|
api_key = ApiKey.where(channel_id: @channel.id, write_flag: 1).first
|
|
@post_url = "/update?key=#{api_key.api_key}"
|
|
|
|
# names of non-blank channel fields
|
|
@fields = []
|
|
@channel.attribute_names.each do |attr|
|
|
@fields.push(attr) if attr.index('field') and !@channel[attr].blank?
|
|
end
|
|
end
|
|
|
|
def social_new
|
|
@channel = Channel.new
|
|
end
|
|
|
|
def social_create
|
|
@channel = Channel.new(channel_params)
|
|
|
|
# check for blank name
|
|
@channel.errors.add(:base, t(:social_channel_error_name_blank)) if @channel.name.blank?
|
|
|
|
# check for blank slug
|
|
@channel.errors.add(:base, t(:social_channel_error_slug_blank)) if @channel.slug.blank?
|
|
|
|
# check for at least one field
|
|
fields = false
|
|
@channel.attribute_names.each do |attr|
|
|
if (attr.index('field') or attr.index('status')) and !@channel[attr].blank?
|
|
fields = true
|
|
break
|
|
end
|
|
end
|
|
@channel.errors.add(:base, t(:social_channel_error_fields)) if !fields
|
|
|
|
# check for existing slug
|
|
if @channel.errors.count == 0
|
|
@channel.errors.add(:base, t(:social_channel_error_slug_exists)) if Channel.find_by_slug(@channel.slug)
|
|
end
|
|
|
|
# if there are no errors
|
|
if @channel.errors.count == 0
|
|
@channel.user_id = current_user.id
|
|
@channel.social = true
|
|
@channel.public_flag = true
|
|
@channel.save
|
|
|
|
# create an api key for this channel
|
|
channel.add_write_api_key
|
|
|
|
redirect_to channels_path
|
|
else
|
|
render :action => :social_new
|
|
end
|
|
|
|
end
|
|
|
|
def index
|
|
@channels = current_user.channels
|
|
respond_to do |format|
|
|
format.html
|
|
format.json { render :json => @channels.not_social.to_json(Channel.private_options) }
|
|
format.xml { render :xml => @channel.not_social.to_xml(Channel.private_options) }
|
|
end
|
|
end
|
|
|
|
def show
|
|
@channel = Channel.find_by_id(params[:id])
|
|
|
|
# show the public show page if no channel found
|
|
if @channel.blank?
|
|
@channel = Channel.new(public_flag: false, name: "Channel #{params[:id]}", id: params[:id])
|
|
render "public_show" and return
|
|
end
|
|
|
|
@title = @channel.name
|
|
@domain = domain
|
|
@mychannel = (current_user && current_user.id == @channel.user_id)
|
|
@width = Chart.default_width
|
|
@height = Chart.default_height
|
|
|
|
api_index @channel.id
|
|
# if owner of channel
|
|
get_channel_data if @mychannel
|
|
|
|
# if a json or xml request
|
|
if request.format == :json || request.format == :xml
|
|
# authenticate the channel if the user owns the channel
|
|
authenticated = (@mychannel) || (User.find_by_api_key(get_apikey) == @channel.user)
|
|
# set options correctly
|
|
options = authenticated ? Channel.private_options : Channel.public_options
|
|
end
|
|
|
|
respond_to do |format|
|
|
format.html do
|
|
if @mychannel
|
|
render "private_show"
|
|
else
|
|
render "public_show"
|
|
end
|
|
end
|
|
format.json { render :json => @channel.as_json(options) }
|
|
format.xml { render :xml => @channel.to_xml(options) }
|
|
end
|
|
end
|
|
|
|
def edit
|
|
get_channel_data
|
|
end
|
|
|
|
def update
|
|
# get the current user or find the user via their api key
|
|
@user = current_user || User.find_by_api_key(get_apikey)
|
|
@channel = @user.channels.find(params[:id])
|
|
|
|
# make updating attributes easier for updates via api
|
|
params[:channel] = params if params[:channel].blank?
|
|
|
|
@channel.assign_attributes(channel_params)
|
|
|
|
if !@channel.valid?
|
|
@channel.errors.add(:base, t(:channel_video_type_blank))
|
|
flash[:alert] = @channel.errors.full_messages.join('. ')
|
|
redirect_to channel_path(@channel.id, :anchor => "channelsettings") and return
|
|
end
|
|
|
|
@channel.save_tags(params[:tags][:name]) if params[:tags].present?
|
|
@channel.set_windows
|
|
@channel.save
|
|
@channel.set_ranking
|
|
flash[:notice] = t(:channel_update_success)
|
|
respond_to do |format|
|
|
format.json { render :json => @channel.to_json(Channel.private_options) }
|
|
format.xml { render :xml => @channel.to_xml(Channel.private_options) }
|
|
format.any { redirect_to channel_path(@channel.id) }
|
|
end
|
|
end
|
|
|
|
def create
|
|
# get the current user or find the user via their api key
|
|
@user = current_user || User.find_by_api_key(get_apikey)
|
|
channel = @user.channels.create(:field1 => "#{t(:channel_default_field)} 1")
|
|
|
|
# make updating attributes easier
|
|
params[:channel] = params
|
|
channel.update_attributes(channel_params)
|
|
|
|
channel.set_windows(true)
|
|
channel.save
|
|
channel.save_tags(params[:channel][:tags]) if params[:channel][:tags].present?
|
|
channel.add_write_api_key
|
|
channel.set_ranking
|
|
@channel_id = channel.id
|
|
respond_to do |format|
|
|
format.json { render :json => channel.to_json(Channel.private_options) }
|
|
format.xml { render :xml => channel.to_xml(Channel.private_options) }
|
|
format.any { redirect_to channel_path(@channel_id, :anchor => "channelsettings") }
|
|
end
|
|
end
|
|
|
|
# clear all data from a channel
|
|
def clear
|
|
# get the current user or find the user via their api key
|
|
@user = current_user || User.find_by_api_key(get_apikey)
|
|
channel = @user.channels.find(params[:id])
|
|
channel.delete_feeds
|
|
respond_to do |format|
|
|
format.json { render :json => [] }
|
|
format.xml { render :xml => [] }
|
|
format.any { redirect_to channel_path(channel.id) }
|
|
end
|
|
end
|
|
|
|
def destroy
|
|
# get the current user or find the user via their api key
|
|
@user = current_user || User.find_by_api_key(get_apikey)
|
|
@channel = @user.channels.find(params[:id])
|
|
@channel.destroy
|
|
respond_to do |format|
|
|
format.json { render :json => @channel.to_json(Channel.public_options) }
|
|
format.xml { render :xml => @channel.to_xml(Channel.public_options) }
|
|
format.any { redirect_to channels_path, :status => 303 }
|
|
end
|
|
end
|
|
|
|
# post from realtime.io daemon
|
|
def realtime_update
|
|
# exit if not authenticated
|
|
respond_with_error(:error_auth_required) and return if params[:realtime_key] != REALTIME_DAEMON_KEY
|
|
|
|
# set feed and channel
|
|
feed = Feed.new
|
|
channel = Channel.find(params[:id])
|
|
|
|
# update entry_id for channel and feed
|
|
entry_id = channel.next_entry_id
|
|
channel.last_entry_id = entry_id
|
|
feed.entry_id = entry_id
|
|
# set user agent
|
|
channel.user_agent = 'realtime.io'
|
|
|
|
# set feed details
|
|
feed.channel_id = channel.id
|
|
feed.status = params[:status]
|
|
|
|
# save channel and feed
|
|
channel.save
|
|
feed.save
|
|
|
|
render :nothing => true
|
|
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_apikey)
|
|
|
|
# if write permission, allow post
|
|
if (api_key && api_key.write_flag)
|
|
channel = api_key.channel
|
|
|
|
# don't rate limit if tstream parameter is present
|
|
tstream = params[:tstream] || false;
|
|
|
|
# don't rate limit if talkback_key parameter is present
|
|
talkback_key = params[:talkback_key] || false;
|
|
|
|
# rate limit posts if channel is not social and timespan is smaller than the allowed window
|
|
render :text => '0' and return if (RATE_LIMIT && !tstream && !talkback_key && !channel.social && channel.last_write_at.present? && Time.now < (channel.last_write_at + RATE_LIMIT_FREQUENCY.to_i.seconds))
|
|
|
|
# if social channel, latitude MUST be present
|
|
render :text => '0' and return if (channel.social && params[:latitude].blank?)
|
|
|
|
# update entry_id for channel and feed
|
|
entry_id = channel.next_entry_id
|
|
channel.last_entry_id = entry_id
|
|
feed.entry_id = entry_id
|
|
# set user agent
|
|
channel.user_agent = get_header_value('USER_AGENT')
|
|
# set the last write at time
|
|
channel.last_write_at = Time.now
|
|
|
|
# try to get created_at datetime if appropriate
|
|
if params[:created_at].present?
|
|
begin
|
|
feed.created_at = ActiveSupport::TimeZone[Time.zone.name].parse(params[:created_at])
|
|
# if invalid datetime, don't do anything--rails will set created_at
|
|
rescue
|
|
end
|
|
end
|
|
|
|
# modify parameters
|
|
params.each do |key, value|
|
|
# this fails so much due to encoding problems that we need to ignore errors
|
|
begin
|
|
# strip line feeds from end of parameters
|
|
params[key] = value.sub(/\\n$/, '').sub(/\\r$/, '') if value
|
|
# use ip address if found
|
|
params[key] = get_header_value('X_REAL_IP') if value.try(:upcase) == 'IP_ADDRESS'
|
|
rescue
|
|
end
|
|
end
|
|
|
|
# set feed details
|
|
feed.channel_id = channel.id
|
|
feed.field1 = params[:field1] || params['1'] if params[:field1] || params['1']
|
|
feed.field2 = params[:field2] || params['2'] if params[:field2] || params['2']
|
|
feed.field3 = params[:field3] || params['3'] if params[:field3] || params['3']
|
|
feed.field4 = params[:field4] || params['4'] if params[:field4] || params['4']
|
|
feed.field5 = params[:field5] || params['5'] if params[:field5] || params['5']
|
|
feed.field6 = params[:field6] || params['6'] if params[:field6] || params['6']
|
|
feed.field7 = params[:field7] || params['7'] if params[:field7] || params['7']
|
|
feed.field8 = params[:field8] || params['8'] if params[:field8] || params['8']
|
|
feed.status = params[:status] if params[:status]
|
|
feed.latitude = params[:lat] if params[:lat]
|
|
feed.latitude = params[:latitude] if params[:latitude]
|
|
feed.longitude = params[:long] if params[:long]
|
|
feed.longitude = params[:longitude] if params[:longitude]
|
|
feed.elevation = params[:elevation] if params[:elevation]
|
|
|
|
# if the saves were successful
|
|
if channel.save && feed.save
|
|
status = entry_id
|
|
|
|
# queue reacts, but don't cause an error
|
|
begin
|
|
channel.queue_react
|
|
rescue
|
|
end
|
|
|
|
# check for tweet
|
|
if params[:twitter] && params[:tweet]
|
|
# check username
|
|
twitter_account = TwitterAccount.find_by_user_id_and_screen_name(api_key.user_id, params[:twitter])
|
|
if twitter_account
|
|
twitter_account.tweet(params[:tweet])
|
|
end
|
|
end
|
|
else
|
|
raise "Channel or Feed didn't save correctly"
|
|
end
|
|
end
|
|
|
|
# if there is a talkback to execute
|
|
if params[:talkback_key].present?
|
|
talkback = Talkback.find_by_api_key(params[:talkback_key])
|
|
command = talkback.execute_command! if talkback.present?
|
|
end
|
|
|
|
# output response code
|
|
render(:text => '0', :status => 400) and return if status == '0'
|
|
|
|
# if there is a talkback_key and a command that was executed
|
|
if params[:talkback_key].present? && command.present?
|
|
respond_to do |format|
|
|
format.html { render :text => command.command_string }
|
|
format.json { render :json => command.to_json }
|
|
format.xml { render :xml => command.to_xml(Command.public_options) }
|
|
end and return
|
|
end
|
|
|
|
# if there is a talkback_key but no command
|
|
respond_with_blank and return if params[:talkback_key].present? && command.blank?
|
|
|
|
# normal route, respond with the feed
|
|
respond_to do |format|
|
|
format.html { render :text => status }
|
|
format.json { render :json => feed.to_json }
|
|
format.xml { render :xml => feed.to_xml(Feed.public_options) }
|
|
format.any { render :text => status }
|
|
end and return
|
|
end
|
|
|
|
# import view
|
|
def import
|
|
get_channel_data
|
|
end
|
|
|
|
# upload csv file to channel
|
|
def upload
|
|
channel = Channel.find(params[:id])
|
|
check_permissions(channel)
|
|
|
|
# if no data
|
|
if params[:upload].blank? || params[:upload][:csv].blank?
|
|
flash[:alert] = t(:upload_no_file)
|
|
redirect_to channel_path(channel.id, :anchor => "dataimport") and return
|
|
end
|
|
|
|
# set time zone
|
|
Time.zone = params[:feed][:time_zone]
|
|
Chronic.time_class = Time.zone
|
|
|
|
# make sure uploaded file doesn't cause errors
|
|
csv_array = nil
|
|
flash_alert = nil
|
|
begin
|
|
# read data from uploaded file
|
|
csv_array = CSV.parse(params[:upload][:csv].read)
|
|
rescue CSV::MalformedCSVError
|
|
flash_alert = t(:upload_incorrect_format)
|
|
end
|
|
|
|
# if no data read, output error message
|
|
if csv_array.nil? || csv_array.blank?
|
|
flash[:alert] = flash_alert || t(:upload_no_data)
|
|
redirect_to channel_path(channel.id, :anchor => "dataimport") and return
|
|
end
|
|
|
|
# does the column have headers
|
|
headers = has_headers?(csv_array)
|
|
|
|
# remember the column positions
|
|
entry_id_column = -1
|
|
latitude_column = -1
|
|
longitude_column = -1
|
|
elevation_column = -1
|
|
location_column = -1
|
|
status_column = -1
|
|
field1_column = -1
|
|
field2_column = -1
|
|
field3_column = -1
|
|
field4_column = -1
|
|
field5_column = -1
|
|
field6_column = -1
|
|
field7_column = -1
|
|
field8_column = -1
|
|
if headers
|
|
csv_array[0].each_with_index do |column, index|
|
|
entry_id_column = index if column.downcase == 'entry_id'
|
|
latitude_column = index if column.downcase == 'latitude'
|
|
longitude_column = index if column.downcase == 'longitude'
|
|
elevation_column = index if column.downcase == 'elevation'
|
|
location_column = index if column.downcase == 'location'
|
|
status_column = index if column.downcase == 'status'
|
|
field1_column = index if column.downcase == 'field1'
|
|
field2_column = index if column.downcase == 'field2'
|
|
field3_column = index if column.downcase == 'field3'
|
|
field4_column = index if column.downcase == 'field4'
|
|
field5_column = index if column.downcase == 'field5'
|
|
field6_column = index if column.downcase == 'field6'
|
|
field7_column = index if column.downcase == 'field7'
|
|
field8_column = index if column.downcase == 'field8'
|
|
end
|
|
end
|
|
|
|
# delete the first row if it contains headers
|
|
csv_array.delete_at(0) if headers
|
|
|
|
# determine if the date can be parsed
|
|
parse_date = date_parsable?(csv_array[0][0]) unless csv_array[0].nil? || csv_array[0][0].nil?
|
|
|
|
# if 2 or more rows
|
|
if !csv_array[1].blank?
|
|
date1 = Chronic.parse(csv_array[0][0]) if parse_date
|
|
date2 = Chronic.parse(csv_array[1][0]) if parse_date
|
|
|
|
# reverse the array if the dates exist and 1st date is larger than 2nd date
|
|
csv_array = csv_array.reverse if date1.present? && date2.present? && date1 > date2
|
|
end
|
|
|
|
# loop through each row
|
|
csv_array.each do |row|
|
|
# if row isn't blank
|
|
if !row.blank?
|
|
feed = Feed.new
|
|
|
|
# set location and status then delete the rows
|
|
# these 5 deletes must be performed in the proper (reverse) order
|
|
feed.status = row.delete_at(status_column) if status_column > 0
|
|
feed.location = row.delete_at(location_column) if location_column > 0
|
|
feed.elevation = row.delete_at(elevation_column) if elevation_column > 0
|
|
feed.longitude = row.delete_at(longitude_column) if longitude_column > 0
|
|
feed.latitude = row.delete_at(latitude_column) if latitude_column > 0
|
|
|
|
# add the fields if they are from named columns, using reverse order
|
|
feed.field8 = row.delete_at(field8_column) if field8_column != -1
|
|
feed.field7 = row.delete_at(field7_column) if field7_column != -1
|
|
feed.field6 = row.delete_at(field6_column) if field6_column != -1
|
|
feed.field5 = row.delete_at(field5_column) if field5_column != -1
|
|
feed.field4 = row.delete_at(field4_column) if field4_column != -1
|
|
feed.field3 = row.delete_at(field3_column) if field3_column != -1
|
|
feed.field2 = row.delete_at(field2_column) if field2_column != -1
|
|
feed.field1 = row.delete_at(field1_column) if field1_column != -1
|
|
|
|
# remove entry_id column if necessary
|
|
row.delete_at(entry_id_column) if entry_id_column > 0
|
|
|
|
# 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
|
|
|
|
# set feed data
|
|
feed.channel_id = channel.id
|
|
feed.created_at = Chronic.parse(row[0]) if parse_date
|
|
|
|
# add the fields normally if necessary
|
|
feed.field1 = row[1] if feed.field1.blank?
|
|
feed.field2 = row[2] if feed.field2.blank?
|
|
feed.field3 = row[3] if feed.field3.blank?
|
|
feed.field4 = row[4] if feed.field4.blank?
|
|
feed.field5 = row[5] if feed.field5.blank?
|
|
feed.field6 = row[6] if feed.field6.blank?
|
|
feed.field7 = row[7] if feed.field7.blank?
|
|
feed.field8 = row[8] if feed.field8.blank?
|
|
feed.latitude = row[9] if feed.latitude.blank?
|
|
feed.longitude = row[10] if feed.longitude.blank?
|
|
feed.elevation = row[11] if feed.elevation.blank?
|
|
feed.status = row[12] if feed.status.blank?
|
|
|
|
# save channel and feed
|
|
feed.save
|
|
channel.save
|
|
|
|
end
|
|
end
|
|
|
|
# redirect
|
|
flash[:notice] = t(:upload_successful)
|
|
redirect_to channel_path(channel.id, :anchor => "dataimport")
|
|
end
|
|
|
|
|
|
private
|
|
|
|
# only allow these params
|
|
def channel_params
|
|
params.require(:channel).permit(:name, :url, :description, :metadata, :latitude, :longitude, :field1, :field2, :field3, :field4, :field5, :field6, :field7, :field8, :elevation, :public_flag, :status, :video_id, :video_type)
|
|
end
|
|
|
|
# determine if the date can be parsed
|
|
def date_parsable?(date)
|
|
return !is_a_number?(date)
|
|
end
|
|
|
|
# determine if the csv file has headers
|
|
def has_headers?(csv_array)
|
|
headers = false
|
|
|
|
# if there are at least 2 rows
|
|
if (csv_array[0].present? && csv_array[1].present?)
|
|
row0_integers = 0
|
|
row1_integers = 0
|
|
|
|
# if first row, first value contains 'create' or 'date', assume it has headers
|
|
if (csv_array[0][0].present? && (csv_array[0][0].downcase.include?('create') || csv_array[0][0].downcase.include?('date')))
|
|
headers = true
|
|
else
|
|
# count integers in row0
|
|
csv_array[0].each_with_index do |value, i|
|
|
row0_integers += 1 if is_a_number?(value)
|
|
end
|
|
|
|
# count integers in row1
|
|
csv_array[1].each_with_index do |value, i|
|
|
row1_integers += 1 if is_a_number?(value)
|
|
end
|
|
|
|
# if row1 has more integers, assume row0 is headers
|
|
headers = true if row1_integers > row0_integers
|
|
end
|
|
end
|
|
|
|
return headers
|
|
end
|
|
|
|
end
|