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}
-
+
{/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
-
-
-
-
- Random
-
- {#each productos as product}
- -
-
-
- {/each}
-
-
+{#each data.data as { category, products }}
+
+
+ {categoryLabels[category] ?? category}
+
+
+ {#each products as product}
+ -
+
+
+ {/each}
+
+
+{/each}