Switching the crystal functions to faaso

This commit is contained in:
Roberto Alsina 2024-08-17 19:28:10 -03:00
parent 8846ad3841
commit 33c95bca6b
23 changed files with 668 additions and 400 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
.faaso.yml
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]

11
busqueda/README.md Normal file
View File

@ -0,0 +1,11 @@
# Readme for Busqueda
This is a funko using the Crystal runtime for [FaaSO](https://git.ralsina.me/ralsina/faaso)
## What is Busqueda
Write here what it is
## How to use Busqueda
And so on.

View File

151
busqueda/funko.cr Normal file
View File

@ -0,0 +1,151 @@
require "json"
require "kemal"
require "pg"
require "pool/connection"
# get credentials from secrets
USER = File.read("/secrets/user").strip
PASS = File.read("/secrets/pass").strip
DBHOST = File.read("/secrets/dbhost").strip
DBURL = "postgres://#{USER}:#{PASS}@#{DBHOST}:5432/nombres"
puts "Connnecting to #{DBURL}"
# Create a connection pool to the database
pg = ConnectionPool.new(capacity: 5, timeout: 0.01.seconds) do
PG.connect(DBURL)
end
def normalize(s : String) : String
s.unicode_normalize(:nfkd)
.chars.reject! { |character|
!character.ascii_letter? && (character != ' ')
}.join("").downcase
end
# A basic hello world get endpoint
post "/" do |env|
prefijo = env.params.json["p"].as(String)
genero = env.params.json["g"].as(String)
year = env.params.json["a"].as(String)
p! prefijo, genero, year
prefijo = normalize(prefijo)
if !["f", "m"].includes?(genero)
genero = nil
end
datos = [] of Tuple(String, Int32 | String)
# Connect using credentials provided
pg.connection do |cursor|
if prefijo.empty? && year.empty?
result_set = cursor.query("
SELECT nombre, total::integer
FROM totales
ORDER BY total DESC
LIMIT 50")
elsif prefijo.empty? && !year.empty?
# Per-year totals
result_set = cursor.query("
SELECT nombre, contador::integer
FROM nombres
WHERE
anio = $1
ORDER BY contador DESC
LIMIT 50", year)
elsif !prefijo.empty? && year.empty?
# Filter only by prefix
result_set = cursor.query("
SELECT nombre, total::integer
FROM totales
WHERE
nombre LIKE $1
ORDER BY total DESC
LIMIT 50", prefijo + "%")
elsif !prefijo.empty? && !year.empty?
# We have both
result_set = cursor.query("
SELECT nombre, contador::integer
FROM nombres
WHERE
anio = $1 AND
nombre LIKE $2
ORDER BY contador DESC
LIMIT 50", year, prefijo + "%")
end
if !result_set.nil?
result_set.each do
nombre = result_set.read(String)
valor = result_set.read(Int32)
datos.push({nombre, valor})
end
result_set.close
end
if datos.empty?
raise "No data found"
end
end
# In this context, remove all composite names
datos.reject! { |row|
row[0].to_s.includes? " "
}
datos.insert(0, {"Nombre", "Cuantos?"})
if genero
pg.connection do |cursor|
datos.reject! { |row|
# How feminine is this name?
# Yes this database is upper case
puts "Checking #{row[1]} #{row[0]}"
feminidad = 0
sql = %(
SELECT COALESCE((SELECT frecuencia FROM mujeres WHERE nombre='#{row[0]?.to_s.upcase}'), 0) AS mujeres,
COALESCE((SELECT frecuencia FROM hombres WHERE nombre='#{row[0]?.to_s.upcase}'), 0) AS hombres
)
puts "SQL: #{sql}"
cursor.query sql do |result_set|
result_set.each do
mujeres = result_set.read(Int32)
hombres = result_set.read(Int32)
puts "frecuencias: #{mujeres} #{hombres}"
if hombres == mujeres == 0
feminidad = 0.5
else
feminidad = mujeres / (hombres + mujeres)
end
end
end
# El overlap en 0.5 es intencional!
if (feminidad >= 0.5 && genero == "f") ||
(feminidad <= 0.5 && genero == "m")
false
else
true
end
}
puts "Data split by gender"
end
end
datos = datos[..10].map { |row|
[row[0].capitalize, row[1]]
}
if datos.size > 1
title = "¿Puede ser ... #{datos[0][1].to_s.titleize}? ¿O capaz que #{datos[1][1].to_s.titleize}? ¡Contame más!"
elsif datos.size == 1
title = "Me parece que ... #{datos[0][1].to_s.titleize}!"
else
title = "No tengo idea!"
end
{
"title" => title,
"data" => datos,
}.to_json
end
get "/ping/" do
pg.connection.exec("SELECT 42")
"OK"
end

11
busqueda/funko.yml Normal file
View File

@ -0,0 +1,11 @@
name: busqueda
runtime: kemal
options:
shard_build_options: "--release"
ship_packages: []
devel_packages: []
healthcheck_options: "--interval=1m --timeout=2s --start-period=2s --retries=3"
healthcheck_command: "curl --fail http://localhost:3000/ping || exit 1"
copy_from_build:
- "public public"
- "bin/funko ."

View File

@ -1,164 +0,0 @@
import unicodedata
import urllib
from collections import namedtuple as nt
from dataclasses import dataclass
from json import loads
import logging
import pygal
import pyrqlite.dbapi2 as dbapi2
import requests
connection = dbapi2.connect(
host="10.61.0.1",
user="root",
port=4001,
password="",
)
def remove_accents(input_str):
nfkd_form = unicodedata.normalize("NFKD", input_str)
return "".join([c for c in nfkd_form if not unicodedata.combining(c)])
def femininidad(nombre):
sql1 = """
SELECT COALESCE(frecuencia,0)
FROM mujeres WHERE nombre=:nombre
"""
sql2 = """
SELECT COALESCE(frecuencia,0)
FROM hombres WHERE nombre=:nombre
"""
with connection.cursor() as cursor:
nombre = nombre.strip().split()[0].strip().upper()
mujeres = cursor.execute(sql1, {"nombre": nombre}).fetchone()
mujeres = 0 if mujeres is None else mujeres[0]
hombres = cursor.execute(sql2, {"nombre": nombre}).fetchone()
hombres = 0 if hombres is None else hombres[0]
if hombres == mujeres == 0:
return 0.5
return mujeres / (hombres + mujeres)
def split_por_genero(nombres):
femeninos = []
masculinos = []
for n in nombres:
fem = femininidad(n[1])
if fem is None:
femeninos.append(n)
masculinos.append(n)
elif fem >= 0.5:
femeninos.append(n)
else:
masculinos.append(n)
return {"f": femeninos, "m": masculinos}
def handle(req):
"""handle a request to the function
Args:
req (str): request body
{
p: prefijo del nombre,
g: genero del nombre,
y: año de nacimiento
}
"""
try:
data = loads(req)
except Exception as e:
data = {}
try:
prefijo = data.get("p") or None
genero = data.get("g") or None
try:
año = int(data.get("a"))
except Exception:
año = None
except Exception as e:
prefijo = genero = año = None
return f"{req} -- {e}", 400
if prefijo is not None:
prefijo = prefijo.strip().lower()
if genero not in ("f", "m"):
genero = None
if prefijo is None and año is None: # Totales globales
with connection.cursor() as cursor:
sql = """
SELECT total, nombre
FROM totales
ORDER BY total DESC
LIMIT 50
"""
cursor.execute(sql)
datos = [(r["total"], r["nombre"]) for r in cursor.fetchall()]
elif prefijo is None and año is not None: # Totales por año
with connection.cursor() as cursor:
sql = """
SELECT contador, nombre
FROM nombres
WHERE
anio = :anio
ORDER BY contador DESC
LIMIT 50
"""
cursor.execute(sql, {"anio": año})
datos = [(r["contador"], r["nombre"]) for r in cursor.fetchall()]
elif prefijo is not None and año is None:
with connection.cursor() as cursor:
sql = """
SELECT total, nombre
FROM totales
WHERE
nombre LIKE :nombre
ORDER BY total DESC
LIMIT 50
"""
cursor.execute(sql, {"nombre": f"{prefijo}%"})
datos = [(r["total"], r["nombre"]) for r in cursor.fetchall()]
else:
with connection.cursor() as cursor:
sql = """
SELECT contador, nombre
FROM nombres
WHERE
anio = :anio AND
nombre LIKE :nombre
ORDER BY contador DESC
LIMIT 50
"""
cursor.execute(sql, {"anio": año, "nombre": f"{prefijo}%"})
datos = [(r["contador"], r["nombre"]) for r in cursor.fetchall()]
if genero:
datos = split_por_genero(datos)[genero]
datos = datos[:10]
chart = pygal.HorizontalBar(height=400, show_legend=False, show_y_labels=True)
chart.x_labels = [nombre.title() for _, nombre in datos[::-1]]
if len(datos) > 1:
chart.title = f"¿Puede ser ... {datos[0][1].title()}? ¿O capaz que {datos[1][1].title()}? ¡Contáme más!"
elif len(datos) == 1:
chart.title = f"¡Hola {datos[0][1].title()}!"
elif len(datos) < 1:
chart.title = "¡No esssistís!"
chart.add("", [contador for contador, _ in datos[::-1]])
return (
chart.render(is_unicode=True),
200,
{"Content-Type": "image/svg+xml"},
)

240
busqueda/public/index.html Normal file
View File

@ -0,0 +1,240 @@
<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script
type="text/javascript"
src="https://www.gstatic.com/charts/loader.js"
></script>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<!-- Font -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Quicksand:wght@300..700&display=swap"
rel="stylesheet"
/>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"
/>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.colors.min.css"
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Kode+Mono&display=swap"
rel="stylesheet"
/>
<script type="text/javascript">
google.charts.load("current", { packages: ["corechart"] });
google.charts.setOnLoadCallback(drawChart);
async function drawChart() {
fetch("/", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
p: document.getElementById("p").value,
g: document.getElementById("g").value,
a: document.getElementById("a").value,
}),
})
.then((response) => response.json())
.then((json) => {
title = json["title"];
data = json["data"];
console.log(data);
data = google.visualization.arrayToDataTable(data);
// var options = {
// title: "",
// animation: {
// startup: true,
// duration: 1000,
// easing: "out",
// },
// backgroundColor: "#1c212c",
// vAxis: {
// minValue: 0,
// gridlines: { color: "#666" },
// minorGridlines: { color: "#1c212c" },
// textStyle: { color: "#aaa" },
// },
// hAxis: {
// gridlines: { color: "#666" },
// minorGridlines: { color: "#1c212c" },
// textStyle: { color: "#aaa" },
// },
// legend: { position: "bottom", textStyle: { color: "#aaa" } },
// };
var chart = new google.visualization.BarChart(
document.getElementById("chart")
);
chart.draw(data);
});
}
</script>
<style>
html * {
font-family: "Quicksand", sans-serif;
}
</style>
</head>
<body>
<main class="container" style="text-align: center">
<header>
<h1>Adivinar nombres por edad y género</h1>
</header>
<div id="chart" style="width: 80vw; height: 50vh; margin: auto"></div>
<form
onSubmit="return false;"
style="margin: auto; margin-top: 2em; width: 80%"
>
<fieldset class="grid">
<input type="text" name="p" id="p" placeholder="Como empieza tu nombre?" />
<select name="a" id="a">
<option selected value="">Año de nacimiento?</option>
<option value="1922">1922</option>
<option value="1923">1923</option>
<option value="1924">1924</option>
<option value="1925">1925</option>
<option value="1926">1926</option>
<option value="1927">1927</option>
<option value="1928">1928</option>
<option value="1929">1929</option>
<option value="1930">1930</option>
<option value="1931">1931</option>
<option value="1932">1932</option>
<option value="1933">1933</option>
<option value="1934">1934</option>
<option value="1935">1935</option>
<option value="1936">1936</option>
<option value="1937">1937</option>
<option value="1938">1938</option>
<option value="1939">1939</option>
<option value="1940">1940</option>
<option value="1941">1941</option>
<option value="1942">1942</option>
<option value="1943">1943</option>
<option value="1944">1944</option>
<option value="1945">1945</option>
<option value="1946">1946</option>
<option value="1947">1947</option>
<option value="1948">1948</option>
<option value="1949">1949</option>
<option value="1950">1950</option>
<option value="1951">1951</option>
<option value="1952">1952</option>
<option value="1953">1953</option>
<option value="1954">1954</option>
<option value="1955">1955</option>
<option value="1956">1956</option>
<option value="1957">1957</option>
<option value="1958">1958</option>
<option value="1959">1959</option>
<option value="1960">1960</option>
<option value="1961">1961</option>
<option value="1962">1962</option>
<option value="1963">1963</option>
<option value="1964">1964</option>
<option value="1965">1965</option>
<option value="1966">1966</option>
<option value="1967">1967</option>
<option value="1968">1968</option>
<option value="1969">1969</option>
<option value="1970">1970</option>
<option value="1971">1971</option>
<option value="1972">1972</option>
<option value="1973">1973</option>
<option value="1974">1974</option>
<option value="1975">1975</option>
<option value="1976">1976</option>
<option value="1977">1977</option>
<option value="1978">1978</option>
<option value="1979">1979</option>
<option value="1980">1980</option>
<option value="1981">1981</option>
<option value="1982">1982</option>
<option value="1983">1983</option>
<option value="1984">1984</option>
<option value="1985">1985</option>
<option value="1986">1986</option>
<option value="1987">1987</option>
<option value="1988">1988</option>
<option value="1989">1989</option>
<option value="1990">1990</option>
<option value="1991">1991</option>
<option value="1992">1992</option>
<option value="1993">1993</option>
<option value="1994">1994</option>
<option value="1995">1995</option>
<option value="1996">1996</option>
<option value="1997">1997</option>
<option value="1998">1998</option>
<option value="1999">1999</option>
<option value="2000">2000</option>
<option value="2001">2001</option>
<option value="2002">2002</option>
<option value="2003">2003</option>
<option value="2004">2004</option>
<option value="2005">2005</option>
<option value="1966">1966</option>
<option value="1967">1967</option>
<option value="1968">1968</option>
<option value="1969">1969</option>
<option value="1970">1970</option>
<option value="1971">1971</option>
<option value="1972">1972</option>
<option value="1973">1973</option>
<option value="1974">1974</option>
<option value="1975">1975</option>
<option value="1976">1976</option>
<option value="1977">1977</option>
<option value="1978">1978</option>
<option value="1979">1979</option>
<option value="1980">1980</option>
<option value="1981">1981</option>
<option value="1982">1982</option>
<option value="1983">1983</option>
<option value="1984">1984</option>
<option value="1985">1985</option>
<option value="1986">1986</option>
<option value="1987">1987</option>
<option value="1988">1988</option>
<option value="1989">1989</option>
<option value="1990">1990</option>
<option value="1991">1991</option>
<option value="1992">1992</option>
<option value="1993">1993</option>
<option value="1994">1994</option>
<option value="1995">1995</option>
<option value="1996">1996</option>
<option value="1997">1997</option>
<option value="1998">1998</option>
<option value="1999">1999</option>
<option value="2000">2000</option>
<option value="2001">2001</option>
<option value="2002">2002</option>
<option value="2003">2003</option>
<option value="2004">2004</option>
<option value="2005">2005</option>
</select>
<select name="g" id="g" />
<option selected value="">Género?</option>
<option value="f">Femenino</option>
<option value="m">Masculino</option>
</select>
<input type="submit" value="Buscar" onCLick="drawChart();" />
</fieldset>
</form>
</main>
</body>
</html>

View File

@ -1,3 +0,0 @@
pygal
requests
pyrqlite

18
busqueda/shard.yml Normal file
View File

@ -0,0 +1,18 @@
name: busqueda
version: 0.1.0
targets:
funko:
main: main.cr
dependencies:
kemal:
github: kemalcr/kemal
pg:
github: will/crystal-pg
pool:
github: ysbaddaden/pool
# development_dependencies:
# webmock:
# github: manastech/webmock.cr

View File

@ -1,127 +0,0 @@
require "http/client"
require "http/headers"
require "http/request"
require "ishi/html"
require "json"
require "uuid"
require "db"
require "pg"
USER = File.read("/var/openfaas/secrets/nombres-user").strip
PASS = File.read("/var/openfaas/secrets/nombres-pass").strip
DB_URL = "postgres://#{USER}:#{PASS}@10.61.0.1:5432/nombres"
class Handler
def format_buffer(buffer, canvas_name, title = "")
# Process the gnuplot output so it works in the page
#
# buffer is the Ishi output
# name is a string to replace for gnuplot_canvas so
# we can have multiple charts in a page
# title is added on top of the chart
html = buffer.to_s.split("\n")
html = html[html.index("<script type=\"text/javascript\">")..html.index("</script>")]
html = "<b>#{title}</b>" + html.join("\n") + %(
<div class="gnuplot">
<canvas id="Tile" width="32" height="32" hidden></canvas>
<table class="plot">
<tr><td>
<canvas id="gnuplot_canvas" width="800" height="300" tabindex="0">
Sorry, your browser seems not to support the HTML 5 canvas element
</canvas>
</td></tr>
</table>
<script type="text/javascript" defer>
gnuplot.init(); gnuplot_canvas();
</script>
</div>
)
# This ID needs to be unique in case
# we have 2 charts in the same page
html.gsub("gnuplot_canvas", canvas_name)
end
def query(sql, nombre)
# Runs a SQL query against the database.
#
# Returns an array of values [[Year,Count]...]
# Or nil if there are no results
DB.open(DB_URL) do |cursor|
cursor.query(sql, nombre) do |result_set|
result = [] of Tuple(Int32, Int32)
result_set.each do
year = result_set.read(Int32)
contador = result_set.read(Int32)
result.push({year, contador})
end
return result
end
end
# No result, return nil
nil
end
nombres = [] of String
def normalize_name(s)
# Remove diacritics, turn lowercase
normalized = s.unicode_normalize(:nfkd).chars
normalized.reject! { |character|
!character.ascii?
}.join("").downcase
end
def run(request : HTTP::Request)
unless (body = request.body).nil?
query = JSON.parse(body)
nombres = query["i"].as_s.split(",").map(&.strip)
nombres.reject! { |nombre| nombre.size == 0 }
end
if nombres.nil? || nombres.empty?
nombres = ["maria", "juan"]
end
# Remove all diacritics and whatnot
nombres = nombres.map { |nombre|
normalize_name nombre
}
puts "Processing #{nombres}"
buffer = IO::Memory.new
Ishi.new(buffer) do
canvas_size(800, 300)
show_key(true)
xrange(1922..2015)
nombres.map { |nombre|
x = Array(Int32).new
y = Array(Int32).new
results = query("SELECT anio::integer, contador::integer FROM nombres WHERE nombre = $1 ORDER BY anio", nombre)
if results.nil? # No results, all 0s
x = (1922..2015).to_a
y = x.map { |_| 0 }
else # We got results
values = Hash(Int32, Int32).new(default_value: 0)
results.map { |row|
values[row[0]] = row[1]
}
(1922..2015).map { |year|
x << year
y << values[year] # Defaults to 0, so we have the whole set
}
end
plot(x, y, title: nombre.titleize, style: :lines, linewidth: 3)
}
end
puts "After Ishi"
{
body: format_buffer(buffer, "historico"),
status_code: 200,
headers: HTTP::Headers{"Content-Type" => "text/html"},
}
end
end

View File

@ -1,8 +0,0 @@
name: historico
version: 0.1.0
dependencies:
ishi:
github: toddsundsted/ishi
pg:
github: will/crystal-pg

View File

@ -1,5 +1,27 @@
#!/bin/sh -x
set -e
export OPENFAAS_URL=http://pinky:8082
pass faas.ralsina.me | faas-cli login -u admin --password-stdin
faas-cli deploy -f functions.yml $*
#export OPENFAAS_URL=http://pinky:8082
#pass faas.ralsina.me | faas-cli login -u admin --password-stdin
#pass iol-pass | faas-cli secret create iol-pass
#pass iol-user | faas-cli secret create iol-user
#pass iol-api-secret | faas-cli secret create iol-api-secret
#pass nombres-user | faas-cli secret create nombres-user
#pass nombres-pass | faas-cli secret create nombres-pass
#faas-cli deploy -f functions.yml $*
export FAASO_SERVER=http://rocky:8888/admin
pass faaso-rocky | faaso login
pass nombres-user | faaso secret -a historico user
pass nombres-pass | faaso secret -a historico pass
echo "192.168.0.98" | faaso secret -a historico dbhost
faaso build busqueda
faaso scale busqueda 0
faaso scale busqueda 1
pass nombres-user | faaso secret -a busqueda user
pass nombres-pass | faaso secret -a busqueda pass
echo "192.168.0.98" | faaso secret -a busqueda dbhost
faaso build historico
faaso scale historico 0
faaso scale historico 1

View File

@ -3,36 +3,10 @@ provider:
name: openfaas
gateway: http://pinky:8082
functions:
# busqueda:
# lang: python3-flask
# handler: ./busqueda
# image: ralsina/nombres_busqueda:latest
# historico:
# lang: python3-flask
# handler: ./historico
# image: ralsina/nombres_historico:latest
tapas:
lang: python3-flask
handler: ./tapas
image: ralsina/tapas:latest
c-historico:
lang: crystal-http
handler: ./c-historico
image: ralsina/c-historico:latest
build_args:
ADDITIONAL_PACKAGE: gnuplot
secrets:
- nombres-pass
- nombres-user
c-busqueda:
lang: crystal-http
handler: ./c-busqueda
image: ralsina/c-busqueda:latest
build_args:
ADDITIONAL_PACKAGE: gnuplot
secrets:
- nombres-pass
- nombres-user
iol:
lang: python3-fastapi
handler: ./iol

11
historico/README.md Normal file
View File

@ -0,0 +1,11 @@
# Readme for Historico
This is a funko using the Crystal runtime for [FaaSO](https://git.ralsina.me/ralsina/faaso)
## What is Historico
Write here what it is
## How to use Historico
And so on.

View File

66
historico/funko.cr Normal file
View File

@ -0,0 +1,66 @@
require "json"
require "kemal"
require "pg"
require "pool/connection"
# get credentials from secrets
USER = File.read("/secrets/user").strip
PASS = File.read("/secrets/pass").strip
DBHOST = File.read("/secrets/dbhost").strip
DBURL = "postgres://#{USER}:#{PASS}@#{DBHOST}:5432/nombres"
puts "Connnecting to #{DBURL}"
# Create a connection pool to the database
pg = ConnectionPool.new(capacity: 5, timeout: 0.01.seconds) do
PG.connect(DBURL)
end
# Connect to the database and get information about
# the requested names
get "/" do |env|
# Names are query parameters
# Split by commas, capitalize and take the first 5
names = env.params.query["names"]
.split(",").map(&.strip.capitalize)[..4]
# Prepare results table
results = [] of Array(String)
results << ["Año"] + names
(1922..2015).each do |anio|
results << [anio.to_s]
end
# Connect using credentials provided
pg.connection do |cursor|
# Get the information for each name
names.map do |name|
# Normalize: remove diacritics etc.
name = name.unicode_normalize(:nfkd)
.chars.reject! { |character|
!character.ascii_letter? && (character != ' ')
}.join("").downcase
counter_per_year = {} of Int32 => Int32
cursor.query("
SELECT anio::integer, contador::integer
FROM nombres WHERE nombre = $1", name) do |result_set|
result_set.each do
counter_per_year[result_set.read(Int32)] = result_set.read(Int32)
end
end
(1922..2015).each do |anio|
results[anio - 1921] << counter_per_year.fetch(anio, 0).to_s
end
end
end
results.to_json
end
# The `/ping/` endpoint is configured in the container as a healthcheck
# You can make it better by checking that your database is responding
# or whatever checks you think are important
#
get "/ping/" do
pg.connection.exec("SELECT 42")
"OK"
end

11
historico/funko.yml Normal file
View File

@ -0,0 +1,11 @@
name: historico
runtime: kemal
options:
shard_build_options: ""
ship_packages: []
devel_packages: []
healthcheck_options: "--interval=1m --timeout=2s --start-period=2s --retries=3"
healthcheck_command: "curl --fail http://localhost:3000/ping || exit 1"
copy_from_build:
- "public public"
- "bin/funko ."

View File

@ -1,66 +0,0 @@
import unicodedata
import urllib
from collections import defaultdict as ddict
from dataclasses import dataclass
from json import loads
import pygal
import pyrqlite.dbapi2 as dbapi2
import requests
connection = dbapi2.connect(
host="10.61.0.1",
user="root",
port=4001,
password="",
)
def remove_accents(input_str):
nfkd_form = unicodedata.normalize("NFKD", input_str)
return "".join([c for c in nfkd_form if not unicodedata.combining(c)])
def handle(req):
"""handle a request to the function
Args:
req (str): request body
{"i": ["nombre1, nombre2"]}
"""
nombres = []
try:
nombres = loads(req)
nombres = nombres["i"].split(",")
nombres = [remove_accents(x.strip().lower()) for x in nombres]
nombres = [n for n in nombres if n]
except Exception:
pass
if not nombres:
nombres = ["maria", "juan"]
chart = pygal.Line(
height=200, fill=True, human_readable=True, show_minor_x_labels=False
)
chart.x_labels = [str(x) for x in range(1922, 2015)]
chart.x_labels_major = [str(x) if x % 10 == 0 else "" for x in range(1922, 2015)]
for nombre in nombres:
datos = ddict(int)
with connection.cursor() as cursor:
sql = """
SELECT anio, contador, nombre
FROM nombres
WHERE nombre = :nombre
ORDER BY anio
"""
cursor.execute(sql, {"nombre": nombre})
datos.update({r["anio"]: r["contador"] for r in cursor.fetchall()})
chart.add(nombre.title(), [datos[x] for x in range(1922, 2015)])
chart.x_labels = [str(n) for n in range(1922, 2015)]
chart.x_labels_major = [str(n) for n in range(1920, 2020, 10)]
# return Response(chart.render(is_unicode=True), mimetype="image/svg+xml")
return chart.render(is_unicode=True), 200, {"Content-Type": "image/svg+xml"}

109
historico/public/index.html Normal file
View File

@ -0,0 +1,109 @@
<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script
type="text/javascript"
src="https://www.gstatic.com/charts/loader.js"
></script>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<!-- Font -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Quicksand:wght@300..700&display=swap"
rel="stylesheet">
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"
/>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.colors.min.css"
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Kode+Mono&display=swap"
rel="stylesheet"
/>
<script type="text/javascript">
google.charts.load("current", { packages: ["corechart"] });
google.charts.setOnLoadCallback(drawChart);
async function drawChart() {
fetch(
"/faaso/historico/?" +
new URLSearchParams({
names: document.getElementById("nombres").value,
})
)
.then((response) => response.json())
.then((json) => {
var data = [json[0]];
data.push(
...json
.slice(1)
.map((item) => item.map((value) => parseInt(value)))
);
data = google.visualization.arrayToDataTable(data);
var options = {
title: "",
animation: {
startup: true,
duration: 1000,
easing: "out",
},
backgroundColor: "#1c212c",
vAxis: {
minValue: 0,
gridlines: { color: "#666" },
minorGridlines: { color: "#1c212c" },
textStyle: { color: "#aaa" }
},
hAxis: {
gridlines: { color: "#666" },
minorGridlines: { color: "#1c212c" },
textStyle: { color: "#aaa" }
},
legend: { position: "bottom", textStyle: { color: "#aaa" } },
};
var chart = new google.visualization.LineChart(
document.getElementById("chart")
);
chart.draw(data, options);
});
}
</script>
<style>
html * {
font-family: 'Quicksand', sans-serif;
}
</style>
</head>
<body>
<main class="container" style="text-align: center">
<header>
<h1>Popularidad de Nombres en Argentina</h1>
</header>
<div id="chart" style="width: 80vw; height: 50vh; margin: auto"></div>
<form
role="search"
onSubmit="return false;"
style="margin: auto; margin-top: 2em; width: 80%"
>
<input
type="search"
name="nombres"
id="nombres"
placeholder="Nombres separados con comas"
aria-label="Search"
/>
<input type="submit" value="Buscar" onCLick="drawChart();" />
</form>
</main>
</body>
</html>

View File

@ -1,3 +0,0 @@
pygal
requests
pyrqlite

14
historico/shard.yml Normal file
View File

@ -0,0 +1,14 @@
name: historico
version: 0.1.0
targets:
funko:
main: main.cr
dependencies:
kemal:
github: kemalcr/kemal
pg:
github: will/crystal-pg
pool:
github: ysbaddaden/pool