506 lines
18 KiB
Ruby
506 lines
18 KiB
Ruby
|
require 'active_support/inflector'
|
||
|
require 'carto/api/vizjson3_presenter'
|
||
|
|
||
|
require_relative '../../models/table'
|
||
|
require_relative '../../models/visualization/member'
|
||
|
require_relative '../../models/visualization/collection'
|
||
|
|
||
|
class Admin::PagesController < Admin::AdminController
|
||
|
include Carto::HtmlSafe
|
||
|
|
||
|
include CartoDB
|
||
|
include VisualizationsControllerHelper
|
||
|
|
||
|
DATASETS_PER_PAGE = 9
|
||
|
MAPS_PER_PAGE = 9
|
||
|
USER_TAGS_LIMIT = 100
|
||
|
PAGE_NUMBER_PLACEHOLDER = 'PAGENUMBERPLACEHOLDER'
|
||
|
|
||
|
# TODO logic as done client-side, how and where to encapsulate this better?
|
||
|
GEOMETRY_MAPPING = {
|
||
|
'st_multipolygon' => 'polygon',
|
||
|
'st_polygon' => 'polygon',
|
||
|
'st_multilinestring' => 'line',
|
||
|
'st_linestring' => 'line',
|
||
|
'st_multipoint' => 'point',
|
||
|
'st_point' => 'point'
|
||
|
}
|
||
|
|
||
|
|
||
|
ssl_required :common_data, :public, :datasets, :maps, :user_feed
|
||
|
ssl_allowed :index, :sitemap, :datasets_for_user, :datasets_for_organization, :maps_for_user, :maps_for_organization,
|
||
|
:render_not_found
|
||
|
|
||
|
before_filter :login_required, :except => [:public, :datasets, :maps, :sitemap, :index, :user_feed]
|
||
|
before_filter :load_viewed_entity
|
||
|
before_filter :set_new_dashboard_flag
|
||
|
before_filter :ensure_organization_correct
|
||
|
skip_before_filter :browser_is_html5_compliant?, only: [:public, :datasets, :maps, :user_feed]
|
||
|
skip_before_filter :ensure_user_organization_valid, only: [:public]
|
||
|
|
||
|
helper_method :named_map_vizjson3
|
||
|
|
||
|
# Just an entrypoint to dispatch to different places according to
|
||
|
def index
|
||
|
if current_user
|
||
|
# I am logged in, visiting my subdomain -> my dashboard
|
||
|
redirect_to CartoDB.url(self, 'dashboard', user: current_user)
|
||
|
elsif CartoDB.extract_subdomain(request).present?
|
||
|
# I am visiting another user subdomain -> other user public pages
|
||
|
redirect_to CartoDB.url(self, 'public_user_feed_home')
|
||
|
elsif current_viewer
|
||
|
# I am logged in but did not specify a subdomain -> my dashboard
|
||
|
redirect_to CartoDB.url(self, 'dashboard', user: current_viewer)
|
||
|
else
|
||
|
# I am not logged in and did not specify a subdomain -> login
|
||
|
# Avoid using CartoDB.url helper, since we cannot get any user information from domain, path or session
|
||
|
redirect_to login_url
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def common_data
|
||
|
redirect_to CartoDB.url(self, 'datasets_library')
|
||
|
end
|
||
|
|
||
|
def sitemap
|
||
|
if @viewed_user.nil?
|
||
|
username = CartoDB.extract_subdomain(request)
|
||
|
org = get_organization_if_exists(username)
|
||
|
render_404 and return if org.nil?
|
||
|
visualizations = public_builder(organization_id: org.id).build
|
||
|
else
|
||
|
# Redirect to org url if has only user
|
||
|
if eligible_for_redirect?(@viewed_user)
|
||
|
redirect_to CartoDB.base_url(@viewed_user.organization.name) << CartoDB.path(self, 'public_sitemap') and return
|
||
|
end
|
||
|
|
||
|
visualizations = public_builder(user_id: @viewed_user.id).with_prefetch_user(true).build
|
||
|
end
|
||
|
|
||
|
@urls = visualizations.map { |vis|
|
||
|
case vis.type
|
||
|
when Carto::Visualization::TYPE_DERIVED
|
||
|
{
|
||
|
loc: CartoDB.url(self, 'public_visualizations_public_map', params: { id: vis.id }, user: vis.user),
|
||
|
lastfreq: vis.updated_at.strftime("%Y-%m-%dT%H:%M:%S%:z")
|
||
|
}
|
||
|
when Carto::Visualization::TYPE_CANONICAL
|
||
|
{
|
||
|
loc: CartoDB.url(self, 'public_table', params: { id: vis.name }, user: vis.user),
|
||
|
lastfreq: vis.updated_at.strftime("%Y-%m-%dT%H:%M:%S%:z")
|
||
|
}
|
||
|
end
|
||
|
}.compact
|
||
|
render :formats => [:xml]
|
||
|
end
|
||
|
|
||
|
def datasets
|
||
|
datasets = CartoDB::ControllerFlows::Public::Datasets.new(self)
|
||
|
content = CartoDB::ControllerFlows::Public::Content.new(self, request, datasets)
|
||
|
content.render()
|
||
|
end
|
||
|
|
||
|
def maps
|
||
|
maps = CartoDB::ControllerFlows::Public::Maps.new(self)
|
||
|
content = CartoDB::ControllerFlows::Public::Content.new(self, request, maps)
|
||
|
content.render()
|
||
|
end
|
||
|
|
||
|
def public
|
||
|
if current_user
|
||
|
index
|
||
|
else
|
||
|
user_feed
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def user_feed
|
||
|
# The template of this endpoint get the user_feed data calling
|
||
|
# to another endpoint in the front-end part
|
||
|
if @viewed_user.nil?
|
||
|
username = CartoDB.extract_subdomain(request).strip.downcase
|
||
|
org = get_organization_if_exists(username)
|
||
|
unless org.nil?
|
||
|
redirect_to CartoDB.url(self, 'public_maps_home') and return
|
||
|
end
|
||
|
render_404
|
||
|
else
|
||
|
|
||
|
set_layout_vars_for_user(@viewed_user, 'feed')
|
||
|
|
||
|
dataset_builder = user_datasets_public_builder(@viewed_user)
|
||
|
maps_builder = user_maps_public_builder(@viewed_user)
|
||
|
|
||
|
@name = @viewed_user.name_or_username
|
||
|
@avatar_url = @viewed_user.avatar
|
||
|
@tables_num = dataset_builder.build.count
|
||
|
@maps_count = maps_builder.build.count
|
||
|
@website = website_url(@viewed_user.website)
|
||
|
@website_clean = @website ? @website.gsub(/https?:\/\//, "") : ""
|
||
|
|
||
|
if eligible_for_redirect?(@viewed_user)
|
||
|
# redirect username.host.ext => org-name.host.ext/u/username
|
||
|
redirect_to CartoDB.base_url(@viewed_user.organization.name, @viewed_user.username) <<
|
||
|
CartoDB.path(self, 'public_user_feed_home') and return
|
||
|
end
|
||
|
|
||
|
description = @name.dup
|
||
|
|
||
|
# TODO: move to helper
|
||
|
if @maps_count == 0 && @tables_num == 0
|
||
|
description << " uses CARTO to transform location intelligence into dynamic renderings that enable discovery of trends and patterns"
|
||
|
else
|
||
|
description << " has"
|
||
|
|
||
|
unless @maps_count == 0
|
||
|
description << " created #{@maps_count} #{'map'.pluralize(@maps_count)}"
|
||
|
end
|
||
|
|
||
|
unless @maps_count == 0 || @tables_num == 0
|
||
|
description << " and"
|
||
|
end
|
||
|
|
||
|
unless @tables_num == 0
|
||
|
description << " published #{@tables_num} public #{'dataset'.pluralize(@tables_num)}"
|
||
|
end
|
||
|
|
||
|
description << " · View #{@name} CARTO profile for the latest activity and contribute to Open Data by creating an account in CARTO"
|
||
|
end
|
||
|
|
||
|
@page_description = description
|
||
|
|
||
|
respond_to do |format|
|
||
|
format.html { render 'user_feed', layout: 'public_user_feed' }
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def datasets_for_user(user)
|
||
|
set_layout_vars_for_user(user, 'datasets')
|
||
|
render_datasets(user_datasets_public_builder(user), user)
|
||
|
end
|
||
|
|
||
|
def datasets_for_organization(org)
|
||
|
set_layout_vars_for_organization(org, 'datasets')
|
||
|
render_datasets(org_datasets_public_builder(org))
|
||
|
end
|
||
|
|
||
|
def maps_for_user(user)
|
||
|
set_layout_vars_for_user(user, 'maps')
|
||
|
render_maps(user_maps_public_builder(user), user)
|
||
|
end
|
||
|
|
||
|
def maps_for_organization(org)
|
||
|
set_layout_vars_for_organization(org, 'maps')
|
||
|
render_maps(org_maps_public_builder(org))
|
||
|
end
|
||
|
|
||
|
def render_not_found
|
||
|
render_404
|
||
|
end
|
||
|
|
||
|
protected
|
||
|
|
||
|
def eligible_for_redirect?(user)
|
||
|
return false if CartoDB.subdomainless_urls?
|
||
|
user.has_organization? && CartoDB.subdomain_from_request(request) != user.organization.name
|
||
|
end
|
||
|
|
||
|
def render_datasets(vis_query_builder, user = nil)
|
||
|
home = CartoDB.url(self, 'public_datasets_home', params: { page: PAGE_NUMBER_PLACEHOLDER }, user: user)
|
||
|
set_pagination_vars(total_count: vis_query_builder.build.count,
|
||
|
per_page: DATASETS_PER_PAGE,
|
||
|
first_page_url: CartoDB.url(self, 'public_datasets_home', user: user),
|
||
|
numbered_page_url: home)
|
||
|
|
||
|
@datasets = []
|
||
|
|
||
|
vis_list = vis_query_builder.build_paged(current_page, DATASETS_PER_PAGE).map do |v|
|
||
|
Carto::Admin::VisualizationPublicMapAdapter.new(v, current_user, self)
|
||
|
end
|
||
|
|
||
|
vis_list.each do |vis|
|
||
|
@datasets << process_dataset_render(vis)
|
||
|
end
|
||
|
|
||
|
@datasets.compact!
|
||
|
|
||
|
description = @name.dup
|
||
|
|
||
|
# TODO: move to helper
|
||
|
if @datasets.size == 0
|
||
|
description << " uses CARTO to transform location intelligence into dynamic renderings that enable discovery of trends and patterns"
|
||
|
else
|
||
|
description << " has published #{@datasets.size} public #{'dataset'.pluralize(@datasets.size)}"
|
||
|
end
|
||
|
|
||
|
description << " · View #{@name} CARTO profile for the latest activity and contribute to Open Data by creating an account in CARTO"
|
||
|
|
||
|
@page_description = description
|
||
|
|
||
|
respond_to do |format|
|
||
|
format.html { render 'public_datasets', layout: 'public_dashboard' }
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def render_maps(vis_query_builder, user=nil)
|
||
|
set_pagination_vars(
|
||
|
total_count: vis_query_builder.build.count,
|
||
|
per_page: MAPS_PER_PAGE,
|
||
|
first_page_url: CartoDB.url(self, 'public_maps_home', user: user),
|
||
|
numbered_page_url: CartoDB.url(self, 'public_maps_home', params: { page: PAGE_NUMBER_PLACEHOLDER }, user: user)
|
||
|
)
|
||
|
|
||
|
vis_list = vis_query_builder.build_paged(current_page, MAPS_PER_PAGE).map do |v|
|
||
|
Carto::Admin::VisualizationPublicMapAdapter.new(v, current_user, self)
|
||
|
end
|
||
|
|
||
|
@visualizations = []
|
||
|
vis_list.each do |vis|
|
||
|
@visualizations << process_map_render(vis)
|
||
|
end
|
||
|
|
||
|
@visualizations.compact!
|
||
|
|
||
|
description = @name.dup
|
||
|
|
||
|
# TODO: move to helper
|
||
|
if @visualizations.size == 0 && @tables_num == 0
|
||
|
description << " uses CARTO to transform location intelligence into dynamic renderings that enable discovery of trends and patterns"
|
||
|
else
|
||
|
description << " has"
|
||
|
|
||
|
unless @visualizations.size == 0
|
||
|
description << " created #{@visualizations.size} #{'map'.pluralize(@visualizations.size)}"
|
||
|
end
|
||
|
|
||
|
unless @visualizations.size == 0 || @tables_num == 0
|
||
|
description << " and"
|
||
|
end
|
||
|
|
||
|
unless @tables_num == 0
|
||
|
description << " published #{@tables_num} public #{'dataset'.pluralize(@tables_num)}"
|
||
|
end
|
||
|
|
||
|
description << " · View #{@name} CARTO profile for the latest activity and contribute to Open Data by creating an account in CARTO"
|
||
|
end
|
||
|
|
||
|
@page_description = description
|
||
|
|
||
|
respond_to do |format|
|
||
|
format.html { render 'public_maps', layout: 'public_dashboard' }
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def set_new_dashboard_flag
|
||
|
ff_user = @viewed_user || @viewed_org.try(:owner)
|
||
|
|
||
|
unless ff_user.nil?
|
||
|
@has_new_dashboard = ff_user.builder_enabled?
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def set_layout_vars_for_user(user, content_type)
|
||
|
builder = user_maps_public_builder(user, visualization_version)
|
||
|
most_viewed = builder.with_order(:mapviews, :desc).build_paged(1, 1).first
|
||
|
|
||
|
set_layout_vars({
|
||
|
most_viewed_vis_map: most_viewed ? Carto::Admin::VisualizationPublicMapAdapter.new(most_viewed, current_user, self) : nil,
|
||
|
content_type: content_type,
|
||
|
default_fallback_basemap: user.default_basemap,
|
||
|
user: user,
|
||
|
base_url: user.public_url(nil, request.protocol == "https://" ? "https" : "http")
|
||
|
})
|
||
|
set_shared_layout_vars(user, {
|
||
|
name: user.name_or_username,
|
||
|
avatar_url: user.avatar,
|
||
|
}, {
|
||
|
available_for_hire: user.available_for_hire,
|
||
|
email: user.email,
|
||
|
user: user
|
||
|
})
|
||
|
end
|
||
|
|
||
|
def set_layout_vars_for_organization(org, content_type)
|
||
|
most_viewed_vis_map = org.public_vis_by_type(Carto::Visualization::TYPE_DERIVED,
|
||
|
1,
|
||
|
1,
|
||
|
nil,
|
||
|
'mapviews',
|
||
|
visualization_version).first
|
||
|
set_layout_vars(most_viewed_vis_map: most_viewed_vis_map,
|
||
|
content_type: content_type,
|
||
|
default_fallback_basemap: org.owner ? org.owner.default_basemap : nil,
|
||
|
base_url: '')
|
||
|
set_shared_layout_vars(org,
|
||
|
name: org.display_name.blank? ? org.name : org.display_name,
|
||
|
avatar_url: org.avatar_url)
|
||
|
end
|
||
|
|
||
|
def set_layout_vars(required)
|
||
|
@most_viewed_vis_map = required.fetch(:most_viewed_vis_map)
|
||
|
@content_type = required.fetch(:content_type)
|
||
|
@maps_url = CartoDB.url(view_context, 'public_maps_home', user: required.fetch(:user, nil))
|
||
|
@datasets_url = CartoDB.url(view_context, 'public_datasets_home', user: required.fetch(:user, nil))
|
||
|
@default_fallback_basemap = required.fetch(:default_fallback_basemap, {})
|
||
|
@base_url = required.fetch(:base_url, {})
|
||
|
end
|
||
|
|
||
|
def set_pagination_vars(required)
|
||
|
# Force all number pagination vars to be integers avoiding problems with
|
||
|
# undesired strings
|
||
|
@total_count = required.fetch(:total_count, 0).to_i
|
||
|
@per_page = required.fetch(:per_page, 9).to_i
|
||
|
@current_page = current_page.to_i
|
||
|
@first_page_url = required.fetch(:first_page_url)
|
||
|
@numbered_page_url = required.fetch(:numbered_page_url)
|
||
|
@page_number_placeholder = PAGE_NUMBER_PLACEHOLDER
|
||
|
end
|
||
|
|
||
|
# Shared as in shared for both new and old layout
|
||
|
def set_shared_layout_vars(model, required, optional = {})
|
||
|
@twitter_username = model.twitter_username
|
||
|
@location = model.location
|
||
|
@description = model.description
|
||
|
@website = website_url(model.website)
|
||
|
@website_clean = @website ? @website.gsub(/https?:\/\//, "") : ""
|
||
|
@name = required.fetch(:name)
|
||
|
@avatar_url = required.fetch(:avatar_url)
|
||
|
@email = optional.fetch(:email, nil)
|
||
|
@available_for_hire = optional.fetch(:available_for_hire, false)
|
||
|
@user = optional.fetch(:user, nil)
|
||
|
@is_org = model.is_a? Organization
|
||
|
@tables_num = (@is_org ? org_datasets_public_builder(model) : user_datasets_public_builder(model)).build.count
|
||
|
@maps_count = (@is_org ? org_maps_public_builder(model) : user_maps_public_builder(model)).build.count
|
||
|
|
||
|
@needs_gmaps_lib = @most_viewed_vis_map.try(:map).try(:provider) == 'googlemaps'
|
||
|
@needs_gmaps_lib ||= @default_fallback_basemap['className'] == 'googlemaps'
|
||
|
|
||
|
gmaps_user = @most_viewed_vis_map.try(:user) || @viewed_user
|
||
|
@gmaps_query_string = gmaps_user ? gmaps_user.google_maps_query_string : @viewed_org.google_maps_key
|
||
|
end
|
||
|
|
||
|
def user_datasets_public_builder(user)
|
||
|
public_builder(user_id: user.id, vis_type: Carto::Visualization::TYPE_CANONICAL)
|
||
|
end
|
||
|
|
||
|
def user_maps_public_builder(user, version = nil)
|
||
|
public_builder(user_id: user.id, vis_type: Carto::Visualization::TYPE_DERIVED, version: version)
|
||
|
end
|
||
|
|
||
|
def org_datasets_public_builder(org)
|
||
|
public_builder(vis_type: Carto::Visualization::TYPE_CANONICAL, organization_id: org.id)
|
||
|
end
|
||
|
|
||
|
def org_maps_public_builder(org)
|
||
|
public_builder(vis_type: Carto::Visualization::TYPE_DERIVED, organization_id: org.id)
|
||
|
end
|
||
|
|
||
|
def public_builder(user_id: nil, vis_type: nil, organization_id: nil, version: nil)
|
||
|
tags = tag_or_nil.nil? ? nil : [tag_or_nil]
|
||
|
|
||
|
builder = Carto::VisualizationQueryBuilder.new
|
||
|
.with_privacy(Carto::Visualization::PRIVACY_PUBLIC)
|
||
|
.with_published
|
||
|
.without_raster
|
||
|
.with_order(:updated_at, :desc)
|
||
|
.with_user_id(user_id)
|
||
|
.with_type(vis_type)
|
||
|
.with_tags(tags)
|
||
|
.with_organization_id(organization_id)
|
||
|
.with_version(version)
|
||
|
|
||
|
builder
|
||
|
end
|
||
|
|
||
|
def visualization_version
|
||
|
@has_new_dashboard ? Carto::Visualization::VERSION_BUILDER : nil
|
||
|
end
|
||
|
|
||
|
def named_map_vizjson3(visualization)
|
||
|
generate_named_map_vizjson3(Carto::Visualization.find(visualization.id))
|
||
|
end
|
||
|
|
||
|
def get_organization_if_exists(name)
|
||
|
Organization.where(name: name).first
|
||
|
end
|
||
|
|
||
|
def current_page
|
||
|
params[:page].to_i > 0 ? params[:page] : 1
|
||
|
end
|
||
|
|
||
|
def tag_or_nil
|
||
|
params[:tag]
|
||
|
end
|
||
|
|
||
|
def ensure_organization_correct
|
||
|
return if CartoDB.subdomainless_urls?
|
||
|
|
||
|
user_or_org_domain = CartoDB.subdomain_from_request(request)
|
||
|
user_domain = CartoDB.extract_subdomain(request)
|
||
|
user = ::User.where(username: user_domain).first
|
||
|
|
||
|
unless user.nil?
|
||
|
if user.username != user_or_org_domain and not user.belongs_to_organization?(get_organization_if_exists(user_or_org_domain))
|
||
|
render_404
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def process_dataset_render(dataset)
|
||
|
geometry_type = dataset.kind
|
||
|
if geometry_type != 'raster'
|
||
|
table_geometry_types = dataset.table.geometry_types
|
||
|
geometry_type = GEOMETRY_MAPPING.fetch(table_geometry_types.first.try(&:downcase), '')
|
||
|
end
|
||
|
|
||
|
vis_item(dataset).merge(
|
||
|
rows_count: dataset.table.rows_counted,
|
||
|
size_in_bytes: dataset.table.table_size,
|
||
|
geometry_type: geometry_type,
|
||
|
source: markdown_html_safe(dataset.source)
|
||
|
)
|
||
|
rescue StandardError => e
|
||
|
# A dataset might be invalid. For example, having the table deleted and not yet cleaned.
|
||
|
# We don't want public page to be broken, but error must be traced.
|
||
|
CartoDB.notify_exception(e, vis: dataset)
|
||
|
nil
|
||
|
end
|
||
|
|
||
|
def process_map_render(map)
|
||
|
vis_item(map)
|
||
|
end
|
||
|
|
||
|
def vis_item(vis)
|
||
|
return {
|
||
|
id: vis.id,
|
||
|
title: vis.name,
|
||
|
description: markdown_html_safe(vis.description),
|
||
|
tags: vis.tags,
|
||
|
updated_at: vis.updated_at,
|
||
|
owner: vis.user,
|
||
|
map_zoom: vis.map.zoom
|
||
|
}
|
||
|
end
|
||
|
|
||
|
def load_viewed_entity
|
||
|
username = CartoDB.extract_subdomain(request)
|
||
|
@viewed_user = ::User.where(username: username).first
|
||
|
|
||
|
if @viewed_user.nil?
|
||
|
username = username.strip.downcase
|
||
|
@viewed_org = get_organization_if_exists(username)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
|
||
|
def website_url(url)
|
||
|
if url.blank?
|
||
|
""
|
||
|
else
|
||
|
!url.blank? && url[/^https?:\/\//].nil? ? "http://#{url}" : url
|
||
|
end
|
||
|
end
|
||
|
|
||
|
end
|