diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8e59ff2..238d95c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -42,6 +42,9 @@ importers: drizzle-orm: specifier: ^0.29.1 version: 0.29.3(@types/better-sqlite3@7.6.8)(better-sqlite3@9.2.2) + zod: + specifier: ^3.22.4 + version: 3.22.4 devDependencies: '@sveltejs/adapter-node': specifier: ^2.0.2 @@ -2859,4 +2862,3 @@ packages: /zod@3.22.4: resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} - dev: true diff --git a/sitio/package.json b/sitio/package.json index 242f07b..67b073f 100644 --- a/sitio/package.json +++ b/sitio/package.json @@ -14,8 +14,11 @@ "format": "prettier --write ." }, "devDependencies": { + "@sveltejs/adapter-node": "^2.0.2", "@sveltejs/kit": "^2.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0", + "@types/better-sqlite3": "^7.6.8", + "@types/node": "^20.10.6", "autoprefixer": "^10.4.16", "db-datos": "workspace:^", "postcss": "^8.4.32", @@ -28,10 +31,7 @@ "tailwindcss": "^3.3.6", "tslib": "^2.4.1", "typescript": "^5.0.0", - "vite": "^5.0.3", - "@sveltejs/adapter-node": "^2.0.2", - "@types/better-sqlite3": "^7.6.8", - "@types/node": "^20.10.6" + "vite": "^5.0.3" }, "type": "module", "dependencies": { @@ -39,6 +39,7 @@ "chart.js": "^4.4.1", "chartjs-adapter-dayjs-4": "^1.0.4", "dayjs": "^1.11.10", - "drizzle-orm": "^0.29.1" + "drizzle-orm": "^0.29.1", + "zod": "^3.22.4" } } diff --git a/sitio/src/lib/ProductPreview.svelte b/sitio/src/lib/ProductPreview.svelte index 17fa6cc..f2b3bae 100644 --- a/sitio/src/lib/ProductPreview.svelte +++ b/sitio/src/lib/ProductPreview.svelte @@ -1,10 +1,19 @@ - - + + + {#if product.imageUrl} - {product.name} + {product.name} {/if}

{product.name}

diff --git a/sitio/src/routes/+page.server.ts b/sitio/src/routes/+page.server.ts index ad906c7..7b0873a 100644 --- a/sitio/src/routes/+page.server.ts +++ b/sitio/src/routes/+page.server.ts @@ -1,64 +1,60 @@ import type { PageData, PageServerLoad } from "./$types"; import { getDb, schema } from "$lib/server/db"; -const { precios } = schema; -import { desc, sql } from "drizzle-orm"; +const { precios, bestSelling } = schema; +import { desc, max, sql } from "drizzle-orm"; import { Supermercado, hostBySupermercado, supermercados, } from "db-datos/supermercado"; +import z from "zod"; +import type { Product } from "$lib/ProductPreview.svelte"; -let cache: Promise<{ key: Date; data: { precios: Precios } }> = doQuery(); +type Data = { + category: string; + products: Product[]; +}[]; +let cache: Promise<{ key: Date; data: Data }> = doQuery(); async function doQuery() { const db = await getDb(); - console.time("ean"); - const eans = await db + + const categories = await db .select({ - ean: precios.ean, + fetchedAt: bestSelling.fetchedAt, + category: bestSelling.category, + eansJson: bestSelling.eansJson, }) - .from(precios) - .groupBy(precios.ean) - .orderBy(sql`random()`) - .limit(50); - console.timeEnd("ean"); + .from(bestSelling) + .groupBy(bestSelling.category) + .having(max(bestSelling.fetchedAt)); - return; + const categoriesWithProducts = await Promise.all( + categories.map(async (category) => { + const eans = z.array(z.string()).parse(JSON.parse(category.eansJson)); - const precioss = await Promise.all( - supermercados.map( - async ( - supermercado, - ): Promise< - [ - Supermercado, - { ean: string; name: string | null; imageUrl: string | null }[], - ] - > => { - const host = hostBySupermercado[supermercado]; - console.time(supermercado); - const q = db - .select({ - ean: precios.ean, - name: precios.name, - imageUrl: precios.imageUrl, - }) - .from(precios) - .groupBy(precios.ean) - .having(sql`max(fetched_at)`) - .where( - sql`ean in ${eans.map((x) => x.ean)} and in_stock and url like ${`%${host}%`}`, - ); - // console.debug(q.toSQL()); - const res = await q; - console.timeEnd(supermercado); - return [supermercado, res]; - }, - ), + const products = await db + .select({ + ean: precios.ean, + name: precios.name, + imageUrl: precios.imageUrl, + }) + .from(precios) + .where(sql`${precios.ean} in ${eans}`) + .groupBy(precios.ean) + .having(max(precios.fetchedAt)); + + return { + category: category.category, + products: eans + .map((ean) => products.find((p) => p.ean === ean)) + .filter((x): x is Product => !!x && !!x.name), + }; + }), ); - const data = { precios: precioss.flatMap(([_, r]) => r) }; - return { key: new Date(), data }; + + return { key: new Date(), data: categoriesWithProducts }; } setInterval( @@ -69,14 +65,8 @@ setInterval( 4 * 60 * 60 * 1000, ); -type Precios = { - ean: string; - name: string | null; - imageUrl: string | null; -}[]; - export const load: PageServerLoad = async ({ params, -}): Promise<{ precios: Precios }> => { - return (await cache).data; +}): Promise<{ data: Data }> => { + return { data: (await cache).data }; }; diff --git a/sitio/src/routes/+page.svelte b/sitio/src/routes/+page.svelte index 6ce7518..87f988c 100644 --- a/sitio/src/routes/+page.svelte +++ b/sitio/src/routes/+page.svelte @@ -3,53 +3,27 @@ import type { PageData } from "./$types"; export let data: PageData; - $: precios = data.precios.filter( - (d): d is { ean: string; name: string; imageUrl: string | null } => - !!d.name, - ); - $: productos = precios.reduce( - (prev, curr) => [ - ...prev, - ...(prev.find((p) => p.ean === curr.ean) ? [] : [curr]), - ], - [] as { ean: string; name: string; imageUrl: string | null }[], - ); + + const categoryLabels: { [key in string]: string } = { + almacen: "Almacen", + bebidas: "Bebidas", + "frutas-y-verduras": "Frutas y Verduras", + }; -

WIP

- -
-

Ejemplos

- -
- -
-

Random

- -
+{#each data.data as { category, products }} +
+

+ {categoryLabels[category] ?? category} +

+ +
+{/each}