5
0
Fork 0
mirror of https://0xacab.org/sutty/sutty synced 2024-05-19 11:50:47 +00:00
panel/app/jobs/uri_collection_job.rb

173 lines
4.8 KiB
Ruby

# frozen_string_literal: true
# Procesar una lista de URIs para una lista de dominios. Esto nos
# permite procesar estadísticas a demanda.
#
# Hay varias cosas acá que van a convertirse en métodos propios, como la
# detección de URIs de un sitio (aunque la versión actual detecta todas
# las páginas y no solo las de posts como tenemos planeado, hay que
# resolver eso).
#
# Los hostnames de un sitio van a poder obtenerse a partir de
# Site#hostnames con la garantía de que son únicos.
class UriCollectionJob < PeriodicJob
# Ignoramos imágenes porque suelen ser demasiadas y no aportan a las
# estadísticas.
IMAGES = %w[.png .jpg .jpeg .gif .webp .jfif].freeze
STAT_NAME = 'uri_collection_job'
def perform(site:, once: true)
@site = site
# Obtener el principio del intervalo anterior
beginning_of_interval
# Recordar la última vez que se corrió la tarea
stat = site.stats.create! name: STAT_NAME
# Columnas a agrupar
columns = Stat::COLUMNS.zip([nil]).to_h
# Las URIs son la fuente de verdad de las visitas, porque son las
# que indican las páginas y recursos descargables, el resto son
# imágenes, CSS, JS y tipografías que no nos aportan números
# significativos.
uri_dimensions = { host: site.hostnames, uri: uris }
host_dimensions = { host: site.hostnames }
# Recorremos todos los hostnames y uris posibles y luego agrupamos
# recursivamente para no tener que recalcular, asumiendo que es más
# rápido buscar en los rollups indexados que en la tabla en bruto.
#
# Los referers solo se agrupan por host.
columns.each_key do |column|
columns[column] = AccessLog.where(**host_dimensions).distinct(column).pluck(column)
end
# Cantidad de visitas por host
rollup(name: 'host', dimensions: host_dimensions, filter: uri_dimensions)
reduce_rollup(name: 'host', dimensions: host_dimensions, filter: uri_dimensions)
# Cantidad de visitas por página/recurso
rollup(name: 'host|uri', dimensions: uri_dimensions)
reduce_rollup(name: 'host|uri', dimensions: uri_dimensions)
# Cantidad de visitas host y parámetro
columns.each_pair do |column, values|
column_name = "host|#{column}"
column_dimensions = { host: site.hostnames }
column_dimensions[column] = values
rollup(name: column_name, dimensions: column_dimensions, filter: uri_dimensions)
reduce_rollup(name: column_name, dimensions: column_dimensions)
end
stat.touch
run_again! unless once
end
private
# Generar un rollup de access logs
#
# @param :name [String]
# @param :beginning [Time]
# @param :dimensions [Hash]
# @param :filter [Hash]
# @return [nil]
def rollup(name:, dimensions:, interval: starting_interval, filter: nil)
AccessLog.where(**(filter || dimensions))
.where('created_at >= ?', beginning_of_interval)
.completed_requests
.non_robots
.group(*dimensions.keys)
.rollup(name, interval: interval, update: true)
end
# Generar rollups con el resto de la información
#
# @param :name [String]
# @param :dimensions [Hash]
# @param :filter [Hash]
# @return [nil]
def reduce_rollup(name:, dimensions:, filter: nil)
Stat::INTERVALS.reduce do |_previous, current|
rollup(name: name, dimensions: dimensions, filter: filter, interval: current)
current
end
nil
end
def stat_name
STAT_NAME
end
# Obtiene todas las ubicaciones de archivos
#
# @return [String]
#
# TODO: Cambiar al mergear origin-referer
def destinations
@destinations ||= site.deploys.map(&:destination).compact.select do |d|
File.directory?(d)
end.map do |d|
File.realpath(d)
end.uniq
end
# Recolecta todas las URIs menos imágenes
#
# TODO: Para los sitios con DeployLocalizedDomain estamos buscando
# URIs de más.
#
# @return [Array]
def uris
@uris ||=
destinations.map do |destination|
locales.map do |locale|
uri = "/#{locale}/".squeeze('/')
dir = File.join(destination, locale)
next unless File.directory? dir
files(dir).map do |f|
uri + f
end
end
end.flatten(3).compact
end
# @return [Array]
def locales
@locales ||= ['', site.locales.map(&:to_s)].flatten(1)
end
# @param :dir [String]
# @return [Array]
def files(dir)
Dir.chdir(dir) do
pages = Dir.glob('**/*.html')
files = Dir.glob('public/**/*')
files = remove_directories files
files = remove_images files
[pages, files].flatten(1)
end
end
# @param :files [Array]
# @return [Array]
def remove_directories(files)
files.reject do |f|
File.directory? f
end
end
def remove_images(files)
files.reject do |f|
IMAGES.include? File.extname(f).downcase
end
end
end