cartodb-4.42/app/controllers/carto/api/public/data_observatory_controller.rb
2024-04-06 05:25:13 +00:00

253 lines
9.5 KiB
Ruby

module Carto
class SubscriptionNotFoundError < StandardError
def initialize(username, subscription_id)
super "Subscription not found with id #{subscription_id} for user #{username}"
end
end
class EntityNotFoundError < StandardError
def initialize(entity_id)
super "Entity not found with id #{entity_id}"
end
end
module Api
module Public
class DataObservatoryController < Carto::Api::Public::ApplicationController
include Carto::Api::PagedSearcher
extend Carto::DefaultRescueFroms
ssl_required
before_action :load_user
before_action :load_filters, only: [:subscriptions]
before_action :load_id, only: [:subscription, :subscription_info, :subscribe, :unsubscribe]
before_action :load_type, only: [:subscription, :subscription_info, :subscribe]
before_action :check_api_key_permissions
before_action :check_do_enabled, only: [:subscription, :subscription_info, :subscriptions]
setup_default_rescues
rescue_from Carto::SubscriptionNotFoundError, with: :rescue_from_subscription_not_found
rescue_from Carto::EntityNotFoundError, with: :rescue_from_entity_not_found
rescue_from Carto::SubscriptionNotFoundError, with: :rescue_from_subscription_not_found
rescue_from Carto::EntityNotFoundError, with: :rescue_from_entity_not_found
respond_to :json
VALID_TYPES = %w(dataset geography).freeze
VALID_STATUSES = %w(active requested).freeze
DATASET_REGEX = /[\w\-]+\.[\w\-]+\.[\w\-]+/.freeze
VALID_ORDER_PARAMS = %i(created_at id table dataset project type).freeze
METADATA_FIELDS = %i(id estimated_delivery_days subscription_list_price tos tos_link licenses licenses_link
rights type).freeze
TABLES_BY_TYPE = { 'dataset' => 'datasets', 'geography' => 'geographies' }.freeze
REQUIRED_METADATA_FIELDS = %i(available_in estimated_delivery_days subscription_list_price).freeze
DEFAULT_DELIVERY_DAYS = 3.0
def token
response = Cartodb::Central.new.get_do_token(@user.username)
render(json: response)
end
def subscriptions
bq_subscriptions = Carto::DoLicensingService.new(@user.username).subscriptions
bq_subscriptions = bq_subscriptions.select { |sub| sub[:status] == @status } if @status.present?
response = present_subscriptions(bq_subscriptions)
render(json: { subscriptions: response })
end
def subscription
bq_subscription = Carto::DoLicensingService.new(@user.username).subscription(@id)
render(json: bq_subscription)
end
def subscription_info
response = present_metadata(subscription_metadata)
render(json: response)
end
def subscribe
metadata = subscription_metadata
if metadata[:is_public_data] == true || instant_licensing_available?(metadata)
instant_license(metadata)
else
regular_license(metadata)
end
response = present_metadata(metadata)
render(json: response)
end
def instant_licensing_available?(metadata)
@user.has_feature_flag?('do-instant-licensing') &&
REQUIRED_METADATA_FIELDS.all? { |field| metadata[field].present? } &&
metadata[:estimated_delivery_days].zero?
end
def instant_license(metadata)
licensing_service = Carto::DoLicensingService.new(@user.username)
licensing_service.subscribe(license_info(metadata, 'active'))
end
def regular_license(metadata)
DataObservatoryMailer.user_request(@user, metadata[:name], metadata[:provider_name]).deliver_now
DataObservatoryMailer.carto_request(@user, metadata[:id], metadata[:estimated_delivery_days]).deliver_now
licensing_service = Carto::DoLicensingService.new(@user.username)
licensing_service.subscribe(license_info(metadata, 'requested'))
end
def unsubscribe
Carto::DoLicensingService.new(@user.username).unsubscribe(@id)
head :no_content
end
def entity_info
doss = Carto::DoSyncServiceFactory.get_for_user(@user)
info = doss.entity_info(params[:entity_id])
raise Carto::EntityNotFoundError.new(params[:entity_id]) if info[:error].present?
render json: info
end
def sync_info
check_subscription!
render json: Carto::DoSyncServiceFactory.get_for_user(@user).sync(params[:subscription_id])
end
def create_sync
check_subscription!
render json: Carto::DoSyncServiceFactory.get_for_user(@user).create_sync!(params[:subscription_id], true)
end
def destroy_sync
check_subscription!
Carto::DoSyncServiceFactory.get_for_user(@user).remove_sync!(params[:subscription_id])
head :no_content
end
private
def check_subscription!
if @user.do_subscription(params[:subscription_id]).blank?
raise Carto::SubscriptionNotFoundError.new(@user.username, params[:subscription_id])
end
end
def rescue_from_subscription_not_found(exception)
render_jsonp({ errors: exception.message }, 404)
end
def rescue_from_entity_not_found(exception)
render_jsonp({ errors: exception.message }, 404)
end
def load_user
@user = Carto::User.find(current_viewer.id)
end
def load_filters
_, _, @order, @direction = page_per_page_order_params(
VALID_ORDER_PARAMS, default_order: 'created_at', default_order_direction: 'asc'
)
@status = VALID_STATUSES.include?(params[:status]) ? params[:status] : nil
load_type(required: false)
end
def load_id
@id = params[:id] || params[:subscription_id]
raise ParamInvalidError.new(:id) unless @id =~ DATASET_REGEX
end
def load_type(required: true)
@type = params[:type]
id = params[:id] || params[:subscription_id]
if @type.nil? && !(id.nil?) then
# If we don't have the type, we can figure it out from the id:
doss = Carto::DoSyncServiceFactory.get_for_user(@user)
parsed_entity_id = doss.parsed_entity_id(id)
@type = parsed_entity_id[:type]
end
return if @type.nil? && !required
raise ParamInvalidError.new(:type, VALID_TYPES.join(', ')) unless VALID_TYPES.include?(@type)
end
def check_api_key_permissions
api_key = Carto::ApiKey.find_by_token(params["api_key"])
raise UnauthorizedError unless api_key&.master? || api_key&.data_observatory_permissions?
end
def check_do_enabled
@user.do_enabled?
end
def rescue_from_central_error(exception)
log_rescue_from(__method__, exception)
render_jsonp({ errors: exception.errors }, 500)
end
def present_subscriptions(subscriptions)
if @type.present?
subscriptions = subscriptions.select { |subscription| subscription[:type] == @type }
end
ordered_subscriptions = subscriptions.sort_by { |subscription| subscription[@order] || subscription['id'] }
@direction == :asc ? ordered_subscriptions : ordered_subscriptions.reverse
end
def present_metadata(metadata)
metadata[:estimated_delivery_days] = present_delivery_days(metadata[:estimated_delivery_days])
metadata.slice(*METADATA_FIELDS)
end
def present_delivery_days(delivery_days)
return DEFAULT_DELIVERY_DAYS if delivery_days&.zero? && !@user.has_feature_flag?('do-instant-licensing')
delivery_days
end
def subscription_metadata
connection = Carto::Db::Connection.do_metadata_connection()
query = "SELECT t.*, p.name as provider_name, '#{@type}' as type
FROM #{TABLES_BY_TYPE[@type]} t
INNER JOIN providers p ON t.provider_id = p.id
WHERE t.id = '#{@id}'"
result = connection.execute(query).first
raise Carto::LoadError.new("No metadata found for #{@id}") unless result
cast_metadata_result(result)
end
def cast_metadata_result(metadata)
metadata = metadata.symbolize_keys
metadata[:subscription_list_price] = metadata[:subscription_list_price]&.to_f
metadata[:estimated_delivery_days] = metadata[:estimated_delivery_days]&.to_f
metadata[:available_in] = metadata[:available_in].delete('{}').split(',') unless metadata[:available_in].nil?
metadata[:is_public_data] = metadata[:is_public_data] == 't'
metadata
end
def license_info(metadata, status)
doss = Carto::DoSyncServiceFactory.get_for_user(@user)
entity_info = doss.parsed_entity_id(metadata[:id])
# 'requested' datasets may no exists yet in 'bq', but let's assume it will...
available_in = (metadata[:available_in].nil? && status == 'requested')? ['bq'] : metadata[:available_in]
entity_info.merge({
dataset_id: metadata[:id],
available_in: available_in,
price: metadata[:subscription_list_price],
created_at: entity_info[:created_at] || Time.now.round,
expires_at: entity_info[:expires_at] || Time.now.round + 1.year,
status: status
})
end
end
end
end
end