cs-prices: full ByMykel catalog, every config, daily multi-source prices, item pages with working charts
All checks were successful
Deploy production / deploy (push) Successful in 1m18s
All checks were successful
Deploy production / deploy (push) Successful in 1m18s
- cs.skins catalog (weapons, knives, gloves, stickers, cases, agents, charms, …) refreshed weekly - every market config: wears in float range, StatTrak, Souvenir, Doppler/Gamma phases - cs.daily_prices (one row per config/stream/day, idempotent) from Skinport, Steam, CSFloat, Buff163 - cs.price_windows for 24h/7d/30d/90d sale aggregates, cs.fx_rates (ECB) - scripts/cs-prices.ts daily|catalog|prices; legacy ingest fixed for the new unique index - skin browser + /projects/cs-prices/[slug] with variant/wear/phase switcher and real charts - charts show points for short histories and pad sparse time axes
This commit is contained in:
101
drizzle/20261026084751_cs_catalog/migration.sql
Normal file
101
drizzle/20261026084751_cs_catalog/migration.sql
Normal file
@@ -0,0 +1,101 @@
|
||||
CREATE TABLE "cs"."daily_prices" (
|
||||
"item_id" integer,
|
||||
"stream_id" smallint,
|
||||
"day" date,
|
||||
"price_cents" integer NOT NULL,
|
||||
"volume" integer,
|
||||
CONSTRAINT "daily_prices_pkey" PRIMARY KEY("item_id","stream_id","day")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "cs"."fx_rates" (
|
||||
"day" date PRIMARY KEY,
|
||||
"usd_eur" numeric(12,8) NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "cs"."price_windows" (
|
||||
"item_id" integer,
|
||||
"source" text,
|
||||
"currency" text NOT NULL,
|
||||
"span" text,
|
||||
"median_cents" integer,
|
||||
"avg_cents" integer,
|
||||
"min_cents" integer,
|
||||
"max_cents" integer,
|
||||
"volume" integer,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "price_windows_pkey" PRIMARY KEY("item_id","source","span")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "cs"."skins" (
|
||||
"id" text PRIMARY KEY,
|
||||
"slug" text NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"kind" text NOT NULL,
|
||||
"weapon" text,
|
||||
"pattern" text,
|
||||
"category" text,
|
||||
"rarity" text,
|
||||
"rarity_color" text,
|
||||
"image" text,
|
||||
"min_float" real,
|
||||
"max_float" real,
|
||||
"collections" text[],
|
||||
"source" text DEFAULT 'bymykel' NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
DROP INDEX "cs"."items_hash_idx";--> statement-breakpoint
|
||||
ALTER TABLE "cs"."items" ADD COLUMN "skin_id" text;--> statement-breakpoint
|
||||
ALTER TABLE "cs"."items" ADD COLUMN "phase" text;--> statement-breakpoint
|
||||
ALTER TABLE "cs"."items" ADD COLUMN "wear" text;--> statement-breakpoint
|
||||
ALTER TABLE "cs"."items" ADD COLUMN "stattrak" boolean DEFAULT false NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "cs"."items" ADD COLUMN "souvenir" boolean DEFAULT false NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "cs"."price_streams" ADD COLUMN "id" smallint GENERATED BY DEFAULT AS IDENTITY (sequence name "cs"."price_streams_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 32767 START WITH 1 CACHE 1);--> statement-breakpoint
|
||||
ALTER TABLE "cs"."price_streams" ADD COLUMN "label" text;--> statement-breakpoint
|
||||
ALTER TABLE "cs"."price_streams" ADD COLUMN "last_fetched_at" timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "cs"."price_streams" ADD COLUMN "last_rows" integer;--> statement-breakpoint
|
||||
ALTER TABLE "cs"."price_streams" ADD COLUMN "last_error" text;--> statement-breakpoint
|
||||
CREATE INDEX "daily_prices_day_idx" ON "cs"."daily_prices" ("day","stream_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "items_config_idx" ON "cs"."items" ("market_hash_name",coalesce("phase", ''));--> statement-breakpoint
|
||||
CREATE INDEX "items_skin_idx" ON "cs"."items" ("skin_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "price_streams_id_idx" ON "cs"."price_streams" ("id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "skins_slug_idx" ON "cs"."skins" ("slug");--> statement-breakpoint
|
||||
CREATE INDEX "skins_name_idx" ON "cs"."skins" ("name");--> statement-breakpoint
|
||||
CREATE INDEX "skins_kind_idx" ON "cs"."skins" ("kind");--> statement-breakpoint
|
||||
ALTER TABLE "cs"."daily_prices" ADD CONSTRAINT "daily_prices_item_id_items_id_fkey" FOREIGN KEY ("item_id") REFERENCES "cs"."items"("id") ON DELETE CASCADE;--> statement-breakpoint
|
||||
ALTER TABLE "cs"."items" ADD CONSTRAINT "items_skin_id_skins_id_fkey" FOREIGN KEY ("skin_id") REFERENCES "cs"."skins"("id") ON DELETE SET NULL;--> statement-breakpoint
|
||||
ALTER TABLE "cs"."price_windows" ADD CONSTRAINT "price_windows_item_id_items_id_fkey" FOREIGN KEY ("item_id") REFERENCES "cs"."items"("id") ON DELETE CASCADE;--> statement-breakpoint
|
||||
-- Facets for the listings that already exist (the daily job keeps these up to date).
|
||||
UPDATE cs.items SET
|
||||
stattrak = market_hash_name LIKE '%StatTrak™ %',
|
||||
souvenir = market_hash_name LIKE 'Souvenir %',
|
||||
wear = substring(market_hash_name from '\((Factory New|Minimal Wear|Field-Tested|Well-Worn|Battle-Scarred)\)$');
|
||||
--> statement-breakpoint
|
||||
-- Price streams the daily job writes (one row per config per stream per day).
|
||||
INSERT INTO cs.price_streams (source, currency, kind, label) VALUES
|
||||
('skinport', 'EUR', 'lowest-listing', 'Skinport lowest listing'),
|
||||
('skinport', 'EUR', 'sale-median-24h', 'Skinport sales, 24h median'),
|
||||
('steam-market', 'USD', 'sale-avg', 'Steam Market average sale (24h, else 7d)'),
|
||||
('csfloat', 'USD', 'lowest-listing', 'CSFloat lowest listing'),
|
||||
('buff163', 'USD', 'lowest-listing', 'Buff163 lowest listing')
|
||||
ON CONFLICT (source, currency, kind) DO UPDATE SET label = EXCLUDED.label;
|
||||
--> statement-breakpoint
|
||||
UPDATE cs.price_streams SET label = 'Steam Market lowest listing'
|
||||
WHERE source = 'steam-market' AND currency = 'USD' AND kind = 'lowest-listing' AND label IS NULL;
|
||||
--> statement-breakpoint
|
||||
-- Backfill: the one Steam lowest-listing sweep from Sep 2026 becomes the first daily point.
|
||||
INSERT INTO cs.daily_prices (item_id, stream_id, day, price_cents, volume)
|
||||
SELECT DISTINCT ON (p.item_id, (p.observed_at AT TIME ZONE 'UTC')::date)
|
||||
p.item_id, s.id, (p.observed_at AT TIME ZONE 'UTC')::date, p.price_cents, p.volume
|
||||
FROM cs.price_observations p
|
||||
JOIN cs.price_streams s ON s.source = p.source AND s.currency = p.currency AND s.kind = p.kind
|
||||
ORDER BY p.item_id, (p.observed_at AT TIME ZONE 'UTC')::date, p.observed_at DESC
|
||||
ON CONFLICT DO NOTHING;
|
||||
--> statement-breakpoint
|
||||
DO $$
|
||||
BEGIN
|
||||
IF current_user = 'personal' AND EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'personal_app') THEN
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON cs.skins, cs.daily_prices, cs.price_windows, cs.fx_rates TO personal_app;
|
||||
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA cs TO personal_app;
|
||||
END IF;
|
||||
END $$;
|
||||
42052
drizzle/20261026084751_cs_catalog/snapshot.json
Normal file
42052
drizzle/20261026084751_cs_catalog/snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user