Mueve la base de datos de SQLite a PostgreSQL

El usuario ha puesto un servidor con PostgreSQL 16, así que la base pasa allí.
El catálogo no se toca: sigue siendo Markdown del repositorio; lo que se muda es
lo que generan las personas, que es lo único que estaba en la base.

Dos diferencias entre motores que habrían pasado desapercibidas y se cazaron
comprobando con datos de verdad, no compilando:

- `integer` en Postgres son cuatro bytes y en SQLite ocho. El limitador de
  envíos guarda su vencimiento en milisegundos desde 1970 —trece dígitos, del
  orden del billón— y en `integer` desbordaría, dejando de limitar. Va en
  `bigint`.
- `avg()` y `count()` los entrega el driver como cadena para no perder
  precisión. Sin convertirlas, `cuantas === 0` nunca se cumpliría y «1 voto»
  saldría siempre en plural. Se convierten en la consulta.

Verificado contra el servidor: once tablas creadas, las 113 pruebas de extremo a
extremo y las 224 unitarias en verde, el servidor de producción arrancando, el
limitador escribiendo sus trece dígitos y el borrado en cascada llevándose
valoraciones y favoritos al borrar una cuenta.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
master
dev 1 month ago
parent ef7f1e0b04
commit 3aebf91085

@ -1,6 +1,6 @@
# --- Base de datos -----------------------------------------------------------
# En local basta un archivo SQLite. En producción, la URL de Turso (libsql://…).
DATABASE_URL=file:local.db
DATABASE_URL=postgresql://senza_paura_web:CLAVE@192.168.18.171:5432/senzapaura
# --- Audio -------------------------------------------------------------------
# Directorio privado con los archivos completos. NO debe servirse como estático:

@ -904,6 +904,25 @@ enlace a nuestra propia pantalla de acceso con `?volverA=` apuntando fuera para
que alguien escriba su correo aquí y acabe en una página ajena creyendo que
sigue en esta.
## La base de datos
**PostgreSQL 16**, con Drizzle por delante. Aquí no vive el catálogo —estilos,
discos, temas y capítulos siguen siendo Markdown del repositorio—: la base
guarda solo lo que generan las personas que visitan la web, que es lo que no
puede estar en el código. Cuentas, códigos de acceso, sesiones, límites de
envío, carritos, pedidos, compras, favoritos y valoraciones.
Dos diferencias con SQLite que costaron un fallo cada una, por si aparecen otra
vez:
- **`integer` en Postgres son cuatro bytes**, no ocho. El limitador guarda su
vencimiento en milisegundos desde 1970 —trece dígitos— y en `integer` no
cabe: va en `bigint`.
- **`avg()` y `count()` llegan como cadena.** El driver no las convierte para
no perder precisión, así que `cuantas === 0` nunca se cumpliría y «1 voto»
saldría siempre en plural. Se convierten en la propia consulta con
`::float8` y `::int`.
## Variables de entorno
Copia `.env.example` a `.env`. Todas son opcionales en desarrollo:

@ -4,7 +4,7 @@ if (!process.env.DATABASE_URL) throw new Error('DATABASE_URL is not set');
export default defineConfig({
schema: './src/lib/server/db/schema.ts',
dialect: 'sqlite',
dialect: 'postgresql',
dbCredentials: { url: process.env.DATABASE_URL },
verbose: true,
strict: true

@ -0,0 +1,106 @@
CREATE TABLE "carrito" (
"id" text PRIMARY KEY NOT NULL,
"usuario_id" text,
"creado_en" timestamp with time zone NOT NULL,
"actualizado_en" timestamp with time zone NOT NULL
);
--> statement-breakpoint
CREATE TABLE "carrito_item" (
"carrito_id" text NOT NULL,
"cancion_slug" text NOT NULL,
"anadido_en" timestamp with time zone NOT NULL,
CONSTRAINT "carrito_item_carrito_id_cancion_slug_pk" PRIMARY KEY("carrito_id","cancion_slug")
);
--> statement-breakpoint
CREATE TABLE "codigo_acceso" (
"id" text PRIMARY KEY NOT NULL,
"email" text NOT NULL,
"hash" text NOT NULL,
"expira_en" timestamp with time zone NOT NULL,
"intentos" integer DEFAULT 0 NOT NULL,
"usado_en" timestamp with time zone,
"creado_en" timestamp with time zone NOT NULL
);
--> statement-breakpoint
CREATE TABLE "compra" (
"usuario_id" text NOT NULL,
"cancion_slug" text NOT NULL,
"pedido_id" text NOT NULL,
"comprado_en" timestamp with time zone NOT NULL,
CONSTRAINT "compra_usuario_id_cancion_slug_pk" PRIMARY KEY("usuario_id","cancion_slug")
);
--> statement-breakpoint
CREATE TABLE "favorito" (
"usuario_id" text NOT NULL,
"cancion_slug" text NOT NULL,
"marcado_en" timestamp with time zone NOT NULL,
CONSTRAINT "favorito_usuario_id_cancion_slug_pk" PRIMARY KEY("usuario_id","cancion_slug")
);
--> statement-breakpoint
CREATE TABLE "limite" (
"clave" text PRIMARY KEY NOT NULL,
"intentos" integer NOT NULL,
"expira_en" bigint NOT NULL
);
--> statement-breakpoint
CREATE TABLE "pedido" (
"id" text PRIMARY KEY NOT NULL,
"usuario_id" text NOT NULL,
"sesion_pago_id" text NOT NULL,
"estado" text DEFAULT 'pendiente' NOT NULL,
"packs" integer NOT NULL,
"total_centimos" integer NOT NULL,
"moneda" text DEFAULT 'eur' NOT NULL,
"creado_en" timestamp with time zone NOT NULL,
"pagado_en" timestamp with time zone,
CONSTRAINT "pedido_sesion_pago_id_unique" UNIQUE("sesion_pago_id")
);
--> statement-breakpoint
CREATE TABLE "pedido_item" (
"pedido_id" text NOT NULL,
"cancion_slug" text NOT NULL,
"titulo" text NOT NULL,
CONSTRAINT "pedido_item_pedido_id_cancion_slug_pk" PRIMARY KEY("pedido_id","cancion_slug")
);
--> statement-breakpoint
CREATE TABLE "sesion" (
"id" text PRIMARY KEY NOT NULL,
"usuario_id" text NOT NULL,
"expira_en" timestamp with time zone NOT NULL,
"creada_en" timestamp with time zone NOT NULL
);
--> statement-breakpoint
CREATE TABLE "usuario" (
"id" text PRIMARY KEY NOT NULL,
"email" text NOT NULL,
"nombre" text,
"creado_en" timestamp with time zone NOT NULL,
"ultimo_acceso_en" timestamp with time zone,
CONSTRAINT "usuario_email_unique" UNIQUE("email")
);
--> statement-breakpoint
CREATE TABLE "valoracion" (
"usuario_id" text NOT NULL,
"cancion_slug" text NOT NULL,
"puntuacion" integer NOT NULL,
"creada_en" timestamp with time zone NOT NULL,
"actualizada_en" timestamp with time zone NOT NULL,
CONSTRAINT "valoracion_usuario_id_cancion_slug_pk" PRIMARY KEY("usuario_id","cancion_slug")
);
--> statement-breakpoint
ALTER TABLE "carrito" ADD CONSTRAINT "carrito_usuario_id_usuario_id_fk" FOREIGN KEY ("usuario_id") REFERENCES "public"."usuario"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "carrito_item" ADD CONSTRAINT "carrito_item_carrito_id_carrito_id_fk" FOREIGN KEY ("carrito_id") REFERENCES "public"."carrito"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "compra" ADD CONSTRAINT "compra_usuario_id_usuario_id_fk" FOREIGN KEY ("usuario_id") REFERENCES "public"."usuario"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "compra" ADD CONSTRAINT "compra_pedido_id_pedido_id_fk" FOREIGN KEY ("pedido_id") REFERENCES "public"."pedido"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "favorito" ADD CONSTRAINT "favorito_usuario_id_usuario_id_fk" FOREIGN KEY ("usuario_id") REFERENCES "public"."usuario"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "pedido" ADD CONSTRAINT "pedido_usuario_id_usuario_id_fk" FOREIGN KEY ("usuario_id") REFERENCES "public"."usuario"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "pedido_item" ADD CONSTRAINT "pedido_item_pedido_id_pedido_id_fk" FOREIGN KEY ("pedido_id") REFERENCES "public"."pedido"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "sesion" ADD CONSTRAINT "sesion_usuario_id_usuario_id_fk" FOREIGN KEY ("usuario_id") REFERENCES "public"."usuario"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "valoracion" ADD CONSTRAINT "valoracion_usuario_id_usuario_id_fk" FOREIGN KEY ("usuario_id") REFERENCES "public"."usuario"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "carrito_usuario" ON "carrito" USING btree ("usuario_id");--> statement-breakpoint
CREATE INDEX "codigo_acceso_email" ON "codigo_acceso" USING btree ("email");--> statement-breakpoint
CREATE INDEX "idx_favorito_usuario" ON "favorito" USING btree ("usuario_id");--> statement-breakpoint
CREATE INDEX "limite_expira" ON "limite" USING btree ("expira_en");--> statement-breakpoint
CREATE INDEX "pedido_usuario" ON "pedido" USING btree ("usuario_id");--> statement-breakpoint
CREATE INDEX "sesion_usuario" ON "sesion" USING btree ("usuario_id");--> statement-breakpoint
CREATE INDEX "idx_valoracion_cancion" ON "valoracion" USING btree ("cancion_slug");

@ -1,79 +0,0 @@
CREATE TABLE `carrito` (
`id` text PRIMARY KEY NOT NULL,
`usuario_id` text,
`creado_en` integer NOT NULL,
`actualizado_en` integer NOT NULL,
FOREIGN KEY (`usuario_id`) REFERENCES `usuario`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE INDEX `carrito_usuario` ON `carrito` (`usuario_id`);--> statement-breakpoint
CREATE TABLE `carrito_item` (
`carrito_id` text NOT NULL,
`cancion_slug` text NOT NULL,
`anadido_en` integer NOT NULL,
PRIMARY KEY(`carrito_id`, `cancion_slug`),
FOREIGN KEY (`carrito_id`) REFERENCES `carrito`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `codigo_acceso` (
`id` text PRIMARY KEY NOT NULL,
`email` text NOT NULL,
`hash` text NOT NULL,
`expira_en` integer NOT NULL,
`intentos` integer DEFAULT 0 NOT NULL,
`usado_en` integer,
`creado_en` integer NOT NULL
);
--> statement-breakpoint
CREATE INDEX `codigo_acceso_email` ON `codigo_acceso` (`email`);--> statement-breakpoint
CREATE TABLE `compra` (
`usuario_id` text NOT NULL,
`cancion_slug` text NOT NULL,
`pedido_id` text NOT NULL,
`comprado_en` integer NOT NULL,
PRIMARY KEY(`usuario_id`, `cancion_slug`),
FOREIGN KEY (`usuario_id`) REFERENCES `usuario`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`pedido_id`) REFERENCES `pedido`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `pedido` (
`id` text PRIMARY KEY NOT NULL,
`usuario_id` text NOT NULL,
`sesion_pago_id` text NOT NULL,
`estado` text DEFAULT 'pendiente' NOT NULL,
`packs` integer NOT NULL,
`total_centimos` integer NOT NULL,
`moneda` text DEFAULT 'eur' NOT NULL,
`creado_en` integer NOT NULL,
`pagado_en` integer,
FOREIGN KEY (`usuario_id`) REFERENCES `usuario`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE UNIQUE INDEX `pedido_sesion_pago_id_unique` ON `pedido` (`sesion_pago_id`);--> statement-breakpoint
CREATE INDEX `pedido_usuario` ON `pedido` (`usuario_id`);--> statement-breakpoint
CREATE TABLE `pedido_item` (
`pedido_id` text NOT NULL,
`cancion_slug` text NOT NULL,
`titulo` text NOT NULL,
PRIMARY KEY(`pedido_id`, `cancion_slug`),
FOREIGN KEY (`pedido_id`) REFERENCES `pedido`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `sesion` (
`id` text PRIMARY KEY NOT NULL,
`usuario_id` text NOT NULL,
`expira_en` integer NOT NULL,
`creada_en` integer NOT NULL,
FOREIGN KEY (`usuario_id`) REFERENCES `usuario`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE INDEX `sesion_usuario` ON `sesion` (`usuario_id`);--> statement-breakpoint
CREATE TABLE `usuario` (
`id` text PRIMARY KEY NOT NULL,
`email` text NOT NULL,
`nombre` text,
`creado_en` integer NOT NULL,
`ultimo_acceso_en` integer
);
--> statement-breakpoint
CREATE UNIQUE INDEX `usuario_email_unique` ON `usuario` (`email`);

@ -1,7 +0,0 @@
CREATE TABLE `limite` (
`clave` text PRIMARY KEY NOT NULL,
`intentos` integer NOT NULL,
`expira_en` integer NOT NULL
);
--> statement-breakpoint
CREATE INDEX `limite_expira` ON `limite` (`expira_en`);

@ -1,11 +0,0 @@
CREATE TABLE `valoracion` (
`usuario_id` text NOT NULL,
`cancion_slug` text NOT NULL,
`puntuacion` integer NOT NULL,
`creada_en` integer NOT NULL,
`actualizada_en` integer NOT NULL,
PRIMARY KEY(`usuario_id`, `cancion_slug`),
FOREIGN KEY (`usuario_id`) REFERENCES `usuario`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE INDEX `idx_valoracion_cancion` ON `valoracion` (`cancion_slug`);

@ -1,9 +0,0 @@
CREATE TABLE `favorito` (
`usuario_id` text NOT NULL,
`cancion_slug` text NOT NULL,
`marcado_en` integer NOT NULL,
PRIMARY KEY(`usuario_id`, `cancion_slug`),
FOREIGN KEY (`usuario_id`) REFERENCES `usuario`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE INDEX `idx_favorito_usuario` ON `favorito` (`usuario_id`);

@ -1,48 +1,53 @@
{
"version": "6",
"dialect": "sqlite",
"id": "375db306-e08d-48ac-910e-912f1c38c7e5",
"id": "de6718aa-a69c-4f5b-8cfa-5a4a7b3a470b",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
"tables": {
"carrito": {
"public.carrito": {
"name": "carrito",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
"notNull": true
},
"usuario_id": {
"name": "usuario_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"creado_en": {
"name": "creado_en",
"type": "integer",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"actualizado_en": {
"name": "actualizado_en",
"type": "integer",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
}
},
"indexes": {
"carrito_usuario": {
"name": "carrito_usuario",
"columns": [
"usuario_id"
{
"expression": "usuario_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
@ -62,31 +67,31 @@
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"carrito_item": {
"public.carrito_item": {
"name": "carrito_item",
"schema": "",
"columns": {
"carrito_id": {
"name": "carrito_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"cancion_slug": {
"name": "cancion_slug",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"anadido_en": {
"name": "anadido_en",
"type": "integer",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
}
},
"indexes": {},
@ -107,114 +112,117 @@
},
"compositePrimaryKeys": {
"carrito_item_carrito_id_cancion_slug_pk": {
"name": "carrito_item_carrito_id_cancion_slug_pk",
"columns": [
"carrito_id",
"cancion_slug"
],
"name": "carrito_item_carrito_id_cancion_slug_pk"
]
}
},
"uniqueConstraints": {},
"checkConstraints": {}
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"codigo_acceso": {
"public.codigo_acceso": {
"name": "codigo_acceso",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
"notNull": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"hash": {
"name": "hash",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"expira_en": {
"name": "expira_en",
"type": "integer",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"intentos": {
"name": "intentos",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
},
"usado_en": {
"name": "usado_en",
"type": "integer",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"creado_en": {
"name": "creado_en",
"type": "integer",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
}
},
"indexes": {
"codigo_acceso_email": {
"name": "codigo_acceso_email",
"columns": [
"email"
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"compra": {
"public.compra": {
"name": "compra",
"schema": "",
"columns": {
"usuario_id": {
"name": "usuario_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"cancion_slug": {
"name": "cancion_slug",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"pedido_id": {
"name": "pedido_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"comprado_en": {
"name": "comprado_en",
"type": "integer",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
}
},
"indexes": {},
@ -248,99 +256,210 @@
},
"compositePrimaryKeys": {
"compra_usuario_id_cancion_slug_pk": {
"name": "compra_usuario_id_cancion_slug_pk",
"columns": [
"usuario_id",
"cancion_slug"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.favorito": {
"name": "favorito",
"schema": "",
"columns": {
"usuario_id": {
"name": "usuario_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"cancion_slug": {
"name": "cancion_slug",
"type": "text",
"primaryKey": false,
"notNull": true
},
"marcado_en": {
"name": "marcado_en",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"idx_favorito_usuario": {
"name": "idx_favorito_usuario",
"columns": [
{
"expression": "usuario_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"favorito_usuario_id_usuario_id_fk": {
"name": "favorito_usuario_id_usuario_id_fk",
"tableFrom": "favorito",
"tableTo": "usuario",
"columnsFrom": [
"usuario_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"favorito_usuario_id_cancion_slug_pk": {
"name": "favorito_usuario_id_cancion_slug_pk",
"columns": [
"usuario_id",
"cancion_slug"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.limite": {
"name": "limite",
"schema": "",
"columns": {
"clave": {
"name": "clave",
"type": "text",
"primaryKey": true,
"notNull": true
},
"intentos": {
"name": "intentos",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"expira_en": {
"name": "expira_en",
"type": "bigint",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"limite_expira": {
"name": "limite_expira",
"columns": [
{
"expression": "expira_en",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"name": "compra_usuario_id_cancion_slug_pk"
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"pedido": {
"public.pedido": {
"name": "pedido",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
"notNull": true
},
"usuario_id": {
"name": "usuario_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"sesion_pago_id": {
"name": "sesion_pago_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"estado": {
"name": "estado",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'pendiente'"
},
"packs": {
"name": "packs",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"total_centimos": {
"name": "total_centimos",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"moneda": {
"name": "moneda",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'eur'"
},
"creado_en": {
"name": "creado_en",
"type": "integer",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"pagado_en": {
"name": "pagado_en",
"type": "integer",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
}
},
"indexes": {
"pedido_sesion_pago_id_unique": {
"name": "pedido_sesion_pago_id_unique",
"columns": [
"sesion_pago_id"
],
"isUnique": true
},
"pedido_usuario": {
"name": "pedido_usuario",
"columns": [
"usuario_id"
{
"expression": "usuario_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
@ -359,32 +478,40 @@
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
"uniqueConstraints": {
"pedido_sesion_pago_id_unique": {
"name": "pedido_sesion_pago_id_unique",
"nullsNotDistinct": false,
"columns": [
"sesion_pago_id"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"pedido_item": {
"public.pedido_item": {
"name": "pedido_item",
"schema": "",
"columns": {
"pedido_id": {
"name": "pedido_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"cancion_slug": {
"name": "cancion_slug",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"titulo": {
"name": "titulo",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
}
},
"indexes": {},
@ -405,55 +532,62 @@
},
"compositePrimaryKeys": {
"pedido_item_pedido_id_cancion_slug_pk": {
"name": "pedido_item_pedido_id_cancion_slug_pk",
"columns": [
"pedido_id",
"cancion_slug"
],
"name": "pedido_item_pedido_id_cancion_slug_pk"
]
}
},
"uniqueConstraints": {},
"checkConstraints": {}
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"sesion": {
"public.sesion": {
"name": "sesion",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
"notNull": true
},
"usuario_id": {
"name": "usuario_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"expira_en": {
"name": "expira_en",
"type": "integer",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"creada_en": {
"name": "creada_en",
"type": "integer",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
}
},
"indexes": {
"sesion_usuario": {
"name": "sesion_usuario",
"columns": [
"usuario_id"
{
"expression": "usuario_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
@ -473,70 +607,152 @@
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"usuario": {
"public.usuario": {
"name": "usuario",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
"notNull": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"nombre": {
"name": "nombre",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
},
"creado_en": {
"name": "creado_en",
"type": "integer",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"autoincrement": false
"notNull": true
},
"ultimo_acceso_en": {
"name": "ultimo_acceso_en",
"type": "integer",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
}
},
"indexes": {
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"usuario_email_unique": {
"name": "usuario_email_unique",
"nullsNotDistinct": false,
"columns": [
"email"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.valoracion": {
"name": "valoracion",
"schema": "",
"columns": {
"usuario_id": {
"name": "usuario_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"cancion_slug": {
"name": "cancion_slug",
"type": "text",
"primaryKey": false,
"notNull": true
},
"puntuacion": {
"name": "puntuacion",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"creada_en": {
"name": "creada_en",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
},
"actualizada_en": {
"name": "actualizada_en",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"idx_valoracion_cancion": {
"name": "idx_valoracion_cancion",
"columns": [
{
"expression": "cancion_slug",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"valoracion_usuario_id_usuario_id_fk": {
"name": "valoracion_usuario_id_usuario_id_fk",
"tableFrom": "valoracion",
"tableTo": "usuario",
"columnsFrom": [
"usuario_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"valoracion_usuario_id_cancion_slug_pk": {
"name": "valoracion_usuario_id_cancion_slug_pk",
"columns": [
"usuario_id",
"cancion_slug"
]
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"views": {},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"indexes": {}
"tables": {}
}
}

@ -1,581 +0,0 @@
{
"version": "6",
"dialect": "sqlite",
"id": "7662184c-1b15-413a-8519-dc445f9e1d9d",
"prevId": "375db306-e08d-48ac-910e-912f1c38c7e5",
"tables": {
"carrito": {
"name": "carrito",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"usuario_id": {
"name": "usuario_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"creado_en": {
"name": "creado_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"actualizado_en": {
"name": "actualizado_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"carrito_usuario": {
"name": "carrito_usuario",
"columns": [
"usuario_id"
],
"isUnique": false
}
},
"foreignKeys": {
"carrito_usuario_id_usuario_id_fk": {
"name": "carrito_usuario_id_usuario_id_fk",
"tableFrom": "carrito",
"tableTo": "usuario",
"columnsFrom": [
"usuario_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"carrito_item": {
"name": "carrito_item",
"columns": {
"carrito_id": {
"name": "carrito_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"cancion_slug": {
"name": "cancion_slug",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"anadido_en": {
"name": "anadido_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"carrito_item_carrito_id_carrito_id_fk": {
"name": "carrito_item_carrito_id_carrito_id_fk",
"tableFrom": "carrito_item",
"tableTo": "carrito",
"columnsFrom": [
"carrito_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"carrito_item_carrito_id_cancion_slug_pk": {
"columns": [
"carrito_id",
"cancion_slug"
],
"name": "carrito_item_carrito_id_cancion_slug_pk"
}
},
"uniqueConstraints": {},
"checkConstraints": {}
},
"codigo_acceso": {
"name": "codigo_acceso",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"hash": {
"name": "hash",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"expira_en": {
"name": "expira_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"intentos": {
"name": "intentos",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
},
"usado_en": {
"name": "usado_en",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"creado_en": {
"name": "creado_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"codigo_acceso_email": {
"name": "codigo_acceso_email",
"columns": [
"email"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"compra": {
"name": "compra",
"columns": {
"usuario_id": {
"name": "usuario_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"cancion_slug": {
"name": "cancion_slug",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"pedido_id": {
"name": "pedido_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"comprado_en": {
"name": "comprado_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"compra_usuario_id_usuario_id_fk": {
"name": "compra_usuario_id_usuario_id_fk",
"tableFrom": "compra",
"tableTo": "usuario",
"columnsFrom": [
"usuario_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"compra_pedido_id_pedido_id_fk": {
"name": "compra_pedido_id_pedido_id_fk",
"tableFrom": "compra",
"tableTo": "pedido",
"columnsFrom": [
"pedido_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"compra_usuario_id_cancion_slug_pk": {
"columns": [
"usuario_id",
"cancion_slug"
],
"name": "compra_usuario_id_cancion_slug_pk"
}
},
"uniqueConstraints": {},
"checkConstraints": {}
},
"limite": {
"name": "limite",
"columns": {
"clave": {
"name": "clave",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"intentos": {
"name": "intentos",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"expira_en": {
"name": "expira_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"limite_expira": {
"name": "limite_expira",
"columns": [
"expira_en"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"pedido": {
"name": "pedido",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"usuario_id": {
"name": "usuario_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"sesion_pago_id": {
"name": "sesion_pago_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"estado": {
"name": "estado",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'pendiente'"
},
"packs": {
"name": "packs",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"total_centimos": {
"name": "total_centimos",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"moneda": {
"name": "moneda",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'eur'"
},
"creado_en": {
"name": "creado_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"pagado_en": {
"name": "pagado_en",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {
"pedido_sesion_pago_id_unique": {
"name": "pedido_sesion_pago_id_unique",
"columns": [
"sesion_pago_id"
],
"isUnique": true
},
"pedido_usuario": {
"name": "pedido_usuario",
"columns": [
"usuario_id"
],
"isUnique": false
}
},
"foreignKeys": {
"pedido_usuario_id_usuario_id_fk": {
"name": "pedido_usuario_id_usuario_id_fk",
"tableFrom": "pedido",
"tableTo": "usuario",
"columnsFrom": [
"usuario_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"pedido_item": {
"name": "pedido_item",
"columns": {
"pedido_id": {
"name": "pedido_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"cancion_slug": {
"name": "cancion_slug",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"titulo": {
"name": "titulo",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"pedido_item_pedido_id_pedido_id_fk": {
"name": "pedido_item_pedido_id_pedido_id_fk",
"tableFrom": "pedido_item",
"tableTo": "pedido",
"columnsFrom": [
"pedido_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"pedido_item_pedido_id_cancion_slug_pk": {
"columns": [
"pedido_id",
"cancion_slug"
],
"name": "pedido_item_pedido_id_cancion_slug_pk"
}
},
"uniqueConstraints": {},
"checkConstraints": {}
},
"sesion": {
"name": "sesion",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"usuario_id": {
"name": "usuario_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"expira_en": {
"name": "expira_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"creada_en": {
"name": "creada_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"sesion_usuario": {
"name": "sesion_usuario",
"columns": [
"usuario_id"
],
"isUnique": false
}
},
"foreignKeys": {
"sesion_usuario_id_usuario_id_fk": {
"name": "sesion_usuario_id_usuario_id_fk",
"tableFrom": "sesion",
"tableTo": "usuario",
"columnsFrom": [
"usuario_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"usuario": {
"name": "usuario",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"nombre": {
"name": "nombre",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"creado_en": {
"name": "creado_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"ultimo_acceso_en": {
"name": "ultimo_acceso_en",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {
"usuario_email_unique": {
"name": "usuario_email_unique",
"columns": [
"email"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
}
},
"views": {},
"enums": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"indexes": {}
}
}

@ -1,656 +0,0 @@
{
"version": "6",
"dialect": "sqlite",
"id": "451fba36-e77b-45fd-894c-a02c708cdcbf",
"prevId": "7662184c-1b15-413a-8519-dc445f9e1d9d",
"tables": {
"carrito": {
"name": "carrito",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"usuario_id": {
"name": "usuario_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"creado_en": {
"name": "creado_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"actualizado_en": {
"name": "actualizado_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"carrito_usuario": {
"name": "carrito_usuario",
"columns": [
"usuario_id"
],
"isUnique": false
}
},
"foreignKeys": {
"carrito_usuario_id_usuario_id_fk": {
"name": "carrito_usuario_id_usuario_id_fk",
"tableFrom": "carrito",
"tableTo": "usuario",
"columnsFrom": [
"usuario_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"carrito_item": {
"name": "carrito_item",
"columns": {
"carrito_id": {
"name": "carrito_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"cancion_slug": {
"name": "cancion_slug",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"anadido_en": {
"name": "anadido_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"carrito_item_carrito_id_carrito_id_fk": {
"name": "carrito_item_carrito_id_carrito_id_fk",
"tableFrom": "carrito_item",
"tableTo": "carrito",
"columnsFrom": [
"carrito_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"carrito_item_carrito_id_cancion_slug_pk": {
"columns": [
"carrito_id",
"cancion_slug"
],
"name": "carrito_item_carrito_id_cancion_slug_pk"
}
},
"uniqueConstraints": {},
"checkConstraints": {}
},
"codigo_acceso": {
"name": "codigo_acceso",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"hash": {
"name": "hash",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"expira_en": {
"name": "expira_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"intentos": {
"name": "intentos",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
},
"usado_en": {
"name": "usado_en",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"creado_en": {
"name": "creado_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"codigo_acceso_email": {
"name": "codigo_acceso_email",
"columns": [
"email"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"compra": {
"name": "compra",
"columns": {
"usuario_id": {
"name": "usuario_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"cancion_slug": {
"name": "cancion_slug",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"pedido_id": {
"name": "pedido_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"comprado_en": {
"name": "comprado_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"compra_usuario_id_usuario_id_fk": {
"name": "compra_usuario_id_usuario_id_fk",
"tableFrom": "compra",
"tableTo": "usuario",
"columnsFrom": [
"usuario_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"compra_pedido_id_pedido_id_fk": {
"name": "compra_pedido_id_pedido_id_fk",
"tableFrom": "compra",
"tableTo": "pedido",
"columnsFrom": [
"pedido_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"compra_usuario_id_cancion_slug_pk": {
"columns": [
"usuario_id",
"cancion_slug"
],
"name": "compra_usuario_id_cancion_slug_pk"
}
},
"uniqueConstraints": {},
"checkConstraints": {}
},
"limite": {
"name": "limite",
"columns": {
"clave": {
"name": "clave",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"intentos": {
"name": "intentos",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"expira_en": {
"name": "expira_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"limite_expira": {
"name": "limite_expira",
"columns": [
"expira_en"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"pedido": {
"name": "pedido",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"usuario_id": {
"name": "usuario_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"sesion_pago_id": {
"name": "sesion_pago_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"estado": {
"name": "estado",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'pendiente'"
},
"packs": {
"name": "packs",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"total_centimos": {
"name": "total_centimos",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"moneda": {
"name": "moneda",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'eur'"
},
"creado_en": {
"name": "creado_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"pagado_en": {
"name": "pagado_en",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {
"pedido_sesion_pago_id_unique": {
"name": "pedido_sesion_pago_id_unique",
"columns": [
"sesion_pago_id"
],
"isUnique": true
},
"pedido_usuario": {
"name": "pedido_usuario",
"columns": [
"usuario_id"
],
"isUnique": false
}
},
"foreignKeys": {
"pedido_usuario_id_usuario_id_fk": {
"name": "pedido_usuario_id_usuario_id_fk",
"tableFrom": "pedido",
"tableTo": "usuario",
"columnsFrom": [
"usuario_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"pedido_item": {
"name": "pedido_item",
"columns": {
"pedido_id": {
"name": "pedido_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"cancion_slug": {
"name": "cancion_slug",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"titulo": {
"name": "titulo",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"pedido_item_pedido_id_pedido_id_fk": {
"name": "pedido_item_pedido_id_pedido_id_fk",
"tableFrom": "pedido_item",
"tableTo": "pedido",
"columnsFrom": [
"pedido_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"pedido_item_pedido_id_cancion_slug_pk": {
"columns": [
"pedido_id",
"cancion_slug"
],
"name": "pedido_item_pedido_id_cancion_slug_pk"
}
},
"uniqueConstraints": {},
"checkConstraints": {}
},
"sesion": {
"name": "sesion",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"usuario_id": {
"name": "usuario_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"expira_en": {
"name": "expira_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"creada_en": {
"name": "creada_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"sesion_usuario": {
"name": "sesion_usuario",
"columns": [
"usuario_id"
],
"isUnique": false
}
},
"foreignKeys": {
"sesion_usuario_id_usuario_id_fk": {
"name": "sesion_usuario_id_usuario_id_fk",
"tableFrom": "sesion",
"tableTo": "usuario",
"columnsFrom": [
"usuario_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"usuario": {
"name": "usuario",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"nombre": {
"name": "nombre",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"creado_en": {
"name": "creado_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"ultimo_acceso_en": {
"name": "ultimo_acceso_en",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {
"usuario_email_unique": {
"name": "usuario_email_unique",
"columns": [
"email"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"valoracion": {
"name": "valoracion",
"columns": {
"usuario_id": {
"name": "usuario_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"cancion_slug": {
"name": "cancion_slug",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"puntuacion": {
"name": "puntuacion",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"creada_en": {
"name": "creada_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"actualizada_en": {
"name": "actualizada_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"idx_valoracion_cancion": {
"name": "idx_valoracion_cancion",
"columns": [
"cancion_slug"
],
"isUnique": false
}
},
"foreignKeys": {
"valoracion_usuario_id_usuario_id_fk": {
"name": "valoracion_usuario_id_usuario_id_fk",
"tableFrom": "valoracion",
"tableTo": "usuario",
"columnsFrom": [
"usuario_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"valoracion_usuario_id_cancion_slug_pk": {
"columns": [
"usuario_id",
"cancion_slug"
],
"name": "valoracion_usuario_id_cancion_slug_pk"
}
},
"uniqueConstraints": {},
"checkConstraints": {}
}
},
"views": {},
"enums": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"indexes": {}
}
}

@ -1,717 +0,0 @@
{
"version": "6",
"dialect": "sqlite",
"id": "49a154bc-fef2-4fec-9138-9a9d3ae97f42",
"prevId": "451fba36-e77b-45fd-894c-a02c708cdcbf",
"tables": {
"carrito": {
"name": "carrito",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"usuario_id": {
"name": "usuario_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"creado_en": {
"name": "creado_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"actualizado_en": {
"name": "actualizado_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"carrito_usuario": {
"name": "carrito_usuario",
"columns": [
"usuario_id"
],
"isUnique": false
}
},
"foreignKeys": {
"carrito_usuario_id_usuario_id_fk": {
"name": "carrito_usuario_id_usuario_id_fk",
"tableFrom": "carrito",
"tableTo": "usuario",
"columnsFrom": [
"usuario_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"carrito_item": {
"name": "carrito_item",
"columns": {
"carrito_id": {
"name": "carrito_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"cancion_slug": {
"name": "cancion_slug",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"anadido_en": {
"name": "anadido_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"carrito_item_carrito_id_carrito_id_fk": {
"name": "carrito_item_carrito_id_carrito_id_fk",
"tableFrom": "carrito_item",
"tableTo": "carrito",
"columnsFrom": [
"carrito_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"carrito_item_carrito_id_cancion_slug_pk": {
"columns": [
"carrito_id",
"cancion_slug"
],
"name": "carrito_item_carrito_id_cancion_slug_pk"
}
},
"uniqueConstraints": {},
"checkConstraints": {}
},
"codigo_acceso": {
"name": "codigo_acceso",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"hash": {
"name": "hash",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"expira_en": {
"name": "expira_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"intentos": {
"name": "intentos",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
},
"usado_en": {
"name": "usado_en",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"creado_en": {
"name": "creado_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"codigo_acceso_email": {
"name": "codigo_acceso_email",
"columns": [
"email"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"compra": {
"name": "compra",
"columns": {
"usuario_id": {
"name": "usuario_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"cancion_slug": {
"name": "cancion_slug",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"pedido_id": {
"name": "pedido_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"comprado_en": {
"name": "comprado_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"compra_usuario_id_usuario_id_fk": {
"name": "compra_usuario_id_usuario_id_fk",
"tableFrom": "compra",
"tableTo": "usuario",
"columnsFrom": [
"usuario_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"compra_pedido_id_pedido_id_fk": {
"name": "compra_pedido_id_pedido_id_fk",
"tableFrom": "compra",
"tableTo": "pedido",
"columnsFrom": [
"pedido_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"compra_usuario_id_cancion_slug_pk": {
"columns": [
"usuario_id",
"cancion_slug"
],
"name": "compra_usuario_id_cancion_slug_pk"
}
},
"uniqueConstraints": {},
"checkConstraints": {}
},
"favorito": {
"name": "favorito",
"columns": {
"usuario_id": {
"name": "usuario_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"cancion_slug": {
"name": "cancion_slug",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"marcado_en": {
"name": "marcado_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"idx_favorito_usuario": {
"name": "idx_favorito_usuario",
"columns": [
"usuario_id"
],
"isUnique": false
}
},
"foreignKeys": {
"favorito_usuario_id_usuario_id_fk": {
"name": "favorito_usuario_id_usuario_id_fk",
"tableFrom": "favorito",
"tableTo": "usuario",
"columnsFrom": [
"usuario_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"favorito_usuario_id_cancion_slug_pk": {
"columns": [
"usuario_id",
"cancion_slug"
],
"name": "favorito_usuario_id_cancion_slug_pk"
}
},
"uniqueConstraints": {},
"checkConstraints": {}
},
"limite": {
"name": "limite",
"columns": {
"clave": {
"name": "clave",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"intentos": {
"name": "intentos",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"expira_en": {
"name": "expira_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"limite_expira": {
"name": "limite_expira",
"columns": [
"expira_en"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"pedido": {
"name": "pedido",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"usuario_id": {
"name": "usuario_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"sesion_pago_id": {
"name": "sesion_pago_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"estado": {
"name": "estado",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'pendiente'"
},
"packs": {
"name": "packs",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"total_centimos": {
"name": "total_centimos",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"moneda": {
"name": "moneda",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'eur'"
},
"creado_en": {
"name": "creado_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"pagado_en": {
"name": "pagado_en",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {
"pedido_sesion_pago_id_unique": {
"name": "pedido_sesion_pago_id_unique",
"columns": [
"sesion_pago_id"
],
"isUnique": true
},
"pedido_usuario": {
"name": "pedido_usuario",
"columns": [
"usuario_id"
],
"isUnique": false
}
},
"foreignKeys": {
"pedido_usuario_id_usuario_id_fk": {
"name": "pedido_usuario_id_usuario_id_fk",
"tableFrom": "pedido",
"tableTo": "usuario",
"columnsFrom": [
"usuario_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"pedido_item": {
"name": "pedido_item",
"columns": {
"pedido_id": {
"name": "pedido_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"cancion_slug": {
"name": "cancion_slug",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"titulo": {
"name": "titulo",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"pedido_item_pedido_id_pedido_id_fk": {
"name": "pedido_item_pedido_id_pedido_id_fk",
"tableFrom": "pedido_item",
"tableTo": "pedido",
"columnsFrom": [
"pedido_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"pedido_item_pedido_id_cancion_slug_pk": {
"columns": [
"pedido_id",
"cancion_slug"
],
"name": "pedido_item_pedido_id_cancion_slug_pk"
}
},
"uniqueConstraints": {},
"checkConstraints": {}
},
"sesion": {
"name": "sesion",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"usuario_id": {
"name": "usuario_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"expira_en": {
"name": "expira_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"creada_en": {
"name": "creada_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"sesion_usuario": {
"name": "sesion_usuario",
"columns": [
"usuario_id"
],
"isUnique": false
}
},
"foreignKeys": {
"sesion_usuario_id_usuario_id_fk": {
"name": "sesion_usuario_id_usuario_id_fk",
"tableFrom": "sesion",
"tableTo": "usuario",
"columnsFrom": [
"usuario_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"usuario": {
"name": "usuario",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"nombre": {
"name": "nombre",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"creado_en": {
"name": "creado_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"ultimo_acceso_en": {
"name": "ultimo_acceso_en",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {
"usuario_email_unique": {
"name": "usuario_email_unique",
"columns": [
"email"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"valoracion": {
"name": "valoracion",
"columns": {
"usuario_id": {
"name": "usuario_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"cancion_slug": {
"name": "cancion_slug",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"puntuacion": {
"name": "puntuacion",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"creada_en": {
"name": "creada_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"actualizada_en": {
"name": "actualizada_en",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"idx_valoracion_cancion": {
"name": "idx_valoracion_cancion",
"columns": [
"cancion_slug"
],
"isUnique": false
}
},
"foreignKeys": {
"valoracion_usuario_id_usuario_id_fk": {
"name": "valoracion_usuario_id_usuario_id_fk",
"tableFrom": "valoracion",
"tableTo": "usuario",
"columnsFrom": [
"usuario_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"valoracion_usuario_id_cancion_slug_pk": {
"columns": [
"usuario_id",
"cancion_slug"
],
"name": "valoracion_usuario_id_cancion_slug_pk"
}
},
"uniqueConstraints": {},
"checkConstraints": {}
}
},
"views": {},
"enums": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"indexes": {}
}
}

@ -1,33 +1,12 @@
{
"version": "7",
"dialect": "sqlite",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "6",
"when": 1788645291369,
"tag": "0000_good_magma",
"breakpoints": true
},
{
"idx": 1,
"version": "6",
"when": 1788708006540,
"tag": "0001_ancient_goblin_queen",
"breakpoints": true
},
{
"idx": 2,
"version": "6",
"when": 1788727806327,
"tag": "0002_common_jean_grey",
"breakpoints": true
},
{
"idx": 3,
"version": "6",
"when": 1788730784535,
"tag": "0003_dark_mad_thinker",
"version": "7",
"when": 1788780351154,
"tag": "0000_aromatic_bug",
"breakpoints": true
}
]

439
package-lock.json generated

@ -8,23 +8,25 @@
"name": "senzapaura",
"version": "0.0.1",
"dependencies": {
"drizzle-orm": "^0.45.2",
"pg": "^8.23.0",
"stripe": "^22.6.1"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@libsql/client": "^0.17.3",
"@playwright/test": "^1.60.0",
"@sveltejs/adapter-node": "^5.5.4",
"@sveltejs/kit": "^2.63.0",
"@sveltejs/vite-plugin-svelte": "^7.1.2",
"@types/node": "^22",
"@types/pg": "^8.23.1",
"@vitest/browser-playwright": "^4.1.8",
"drizzle-kit": "^0.31.10",
"drizzle-orm": "^0.45.2",
"eslint": "^10.4.1",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-svelte": "^3.19.0",
"globals": "^17.6.0",
"js-yaml": "^5.4.1",
"mdsvex": "^0.12.7",
"playwright": "^1.60.0",
"prettier": "^3.8.3",
@ -1223,178 +1225,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/@libsql/client": {
"version": "0.17.4",
"resolved": "https://registry.npmjs.org/@libsql/client/-/client-0.17.4.tgz",
"integrity": "sha512-lYayFWasDV78A+TjlEhr6ubb3odBV6OHjb+wdp8VQcyWWAEIjuwbCHaraEUS4m4yWoo0BvZo96It4VdzZRmRWw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@libsql/core": "^0.17.4",
"@libsql/hrana-client": "^0.10.0",
"js-base64": "^3.7.5",
"libsql": "^0.5.28",
"promise-limit": "^2.7.0"
}
},
"node_modules/@libsql/core": {
"version": "0.17.4",
"resolved": "https://registry.npmjs.org/@libsql/core/-/core-0.17.4.tgz",
"integrity": "sha512-LqF9gIvnJ38nmAH1y/ChizHqDO/MO1wLgA96XrraulEEbqXxLjleSH92YWTolbuJKgPUmGu4aJk9W3UnAcxLOQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"js-base64": "^3.7.5"
}
},
"node_modules/@libsql/darwin-arm64": {
"version": "0.5.29",
"resolved": "https://registry.npmjs.org/@libsql/darwin-arm64/-/darwin-arm64-0.5.29.tgz",
"integrity": "sha512-K+2RIB1OGFPYQbfay48GakLhqf3ArcbHqPFu7EZiaUcRgFcdw8RoltsMyvbj5ix2fY0HV3Q3Ioa/ByvQdaSM0A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@libsql/darwin-x64": {
"version": "0.5.29",
"resolved": "https://registry.npmjs.org/@libsql/darwin-x64/-/darwin-x64-0.5.29.tgz",
"integrity": "sha512-OtT+KFHsKFy1R5FVadr8FJ2Bb1mghtXTyJkxv0trocq7NuHntSki1eUbxpO5ezJesDvBlqFjnWaYYY516QNLhQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@libsql/hrana-client": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/@libsql/hrana-client/-/hrana-client-0.10.0.tgz",
"integrity": "sha512-OoA4EMqRAC7kn7V2P6EQqRcpZf2W+AjsNIyCizBg339Tq/aMC7sRnzs3SklderhmQWAqEzvv8A2vhxVmWpkVvw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@libsql/isomorphic-ws": "^0.1.5",
"js-base64": "^3.7.5"
}
},
"node_modules/@libsql/isomorphic-ws": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/@libsql/isomorphic-ws/-/isomorphic-ws-0.1.5.tgz",
"integrity": "sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/ws": "^8.5.4",
"ws": "^8.13.0"
}
},
"node_modules/@libsql/linux-arm-gnueabihf": {
"version": "0.5.29",
"resolved": "https://registry.npmjs.org/@libsql/linux-arm-gnueabihf/-/linux-arm-gnueabihf-0.5.29.tgz",
"integrity": "sha512-CD4n4zj7SJTHso4nf5cuMoWoMSS7asn5hHygsDuhRl8jjjCTT3yE+xdUvI4J7zsyb53VO5ISh4cwwOtf6k2UhQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@libsql/linux-arm-musleabihf": {
"version": "0.5.29",
"resolved": "https://registry.npmjs.org/@libsql/linux-arm-musleabihf/-/linux-arm-musleabihf-0.5.29.tgz",
"integrity": "sha512-2Z9qBVpEJV7OeflzIR3+l5yAd4uTOLxklScYTwpZnkm2vDSGlC1PRlueLaufc4EFITkLKXK2MWBpexuNJfMVcg==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@libsql/linux-arm64-gnu": {
"version": "0.5.29",
"resolved": "https://registry.npmjs.org/@libsql/linux-arm64-gnu/-/linux-arm64-gnu-0.5.29.tgz",
"integrity": "sha512-gURBqaiXIGGwFNEaUj8Ldk7Hps4STtG+31aEidCk5evMMdtsdfL3HPCpvys+ZF/tkOs2MWlRWoSq7SOuCE9k3w==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@libsql/linux-arm64-musl": {
"version": "0.5.29",
"resolved": "https://registry.npmjs.org/@libsql/linux-arm64-musl/-/linux-arm64-musl-0.5.29.tgz",
"integrity": "sha512-fwgYZ0H8mUkyVqXZHF3mT/92iIh1N94Owi/f66cPVNsk9BdGKq5gVpoKO+7UxaNzuEH1roJp2QEwsCZMvBLpqg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@libsql/linux-x64-gnu": {
"version": "0.5.29",
"resolved": "https://registry.npmjs.org/@libsql/linux-x64-gnu/-/linux-x64-gnu-0.5.29.tgz",
"integrity": "sha512-y14V0vY0nmMC6G0pHeJcEarcnGU2H6cm21ZceRkacWHvQAEhAG0latQkCtoS2njFOXiYIg+JYPfAoWKbi82rkg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@libsql/linux-x64-musl": {
"version": "0.5.29",
"resolved": "https://registry.npmjs.org/@libsql/linux-x64-musl/-/linux-x64-musl-0.5.29.tgz",
"integrity": "sha512-gquqwA/39tH4pFl+J9n3SOMSymjX+6kZ3kWgY3b94nXFTwac9bnFNMffIomgvlFaC4ArVqMnOZD3nuJ3H3VO1w==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@libsql/win32-x64-msvc": {
"version": "0.5.29",
"resolved": "https://registry.npmjs.org/@libsql/win32-x64-msvc/-/win32-x64-msvc-0.5.29.tgz",
"integrity": "sha512-4/0CvEdhi6+KjMxMaVbFM2n2Z44escBRoEYpR+gZg64DdetzGnYm8mcNLcoySaDJZNaBd6wz5DNdgRmcI4hXcg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@napi-rs/lzma-linux-x64-gnu": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz",
@ -1412,13 +1242,6 @@
"node": "^22.20 || ^24.12 || >=25"
}
},
"node_modules/@neon-rs/load": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/@neon-rs/load/-/load-0.0.4.tgz",
"integrity": "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==",
"dev": true,
"license": "MIT"
},
"node_modules/@oxc-project/types": {
"version": "0.148.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.148.0.tgz",
@ -2401,6 +2224,18 @@
"undici-types": "~6.21.0"
}
},
"node_modules/@types/pg": {
"version": "8.23.1",
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.23.1.tgz",
"integrity": "sha512-fKVHpikPdg4GKks3JuLEhvwSyvwzF23hnabPy6DD8ljVbC7+6J5dQzdv4arV6jqq57djnMgs1HKBxX4P8aBI3A==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"@types/node": "*",
"pg-protocol": "*",
"pg-types": "^2.2.0"
}
},
"node_modules/@types/resolve": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
@ -2415,16 +2250,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.69.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.69.0.tgz",
@ -2855,6 +2680,13 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true,
"license": "Python-2.0"
},
"node_modules/aria-query": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz",
@ -3608,7 +3440,6 @@
"version": "0.45.2",
"resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.45.2.tgz",
"integrity": "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q==",
"dev": true,
"license": "Apache-2.0",
"peerDependencies": {
"@aws-sdk/client-rds-data": ">=3",
@ -4350,12 +4181,28 @@
"dev": true,
"license": "ISC"
},
"node_modules/js-base64": {
"version": "3.9.3",
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.9.3.tgz",
"integrity": "sha512-uwYQp+VJ38FVvtim6qNbit6e9uT6dwWQ4Y1+H9TxhW5hcHjpHwoxlR0nMpqUmIFOmu4VqMxwdJA88gIVuZJQ/g==",
"node_modules/js-yaml": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.4.1.tgz",
"integrity": "sha512-28R/k+NAjeuf7+CKlTxWZVExJGwVVLwY06DgEnOMz2gEpfNkDcD7QvyiVPT0xy0XXhU8vHsd4Ot42OOPdJG7dQ==",
"dev": true,
"license": "BSD-3-Clause"
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/puzrin"
},
{
"type": "github",
"url": "https://github.com/sponsors/nodeca"
}
],
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.mjs"
}
},
"node_modules/json-schema-traverse": {
"version": "0.4.1",
@ -4412,49 +4259,6 @@
"node": ">= 0.8.0"
}
},
"node_modules/libsql": {
"version": "0.5.29",
"resolved": "https://registry.npmjs.org/libsql/-/libsql-0.5.29.tgz",
"integrity": "sha512-8lMP8iMgiBzzoNbAPQ59qdVcj6UaE/Vnm+fiwX4doX4Narook0a4GPKWBEv+CR8a1OwbfkgL18uBfBjWdF0Fzg==",
"cpu": [
"x64",
"arm64",
"wasm32",
"arm"
],
"dev": true,
"license": "MIT",
"os": [
"darwin",
"linux",
"win32"
],
"dependencies": {
"@neon-rs/load": "^0.0.4",
"detect-libc": "2.0.2"
},
"optionalDependencies": {
"@libsql/darwin-arm64": "0.5.29",
"@libsql/darwin-x64": "0.5.29",
"@libsql/linux-arm-gnueabihf": "0.5.29",
"@libsql/linux-arm-musleabihf": "0.5.29",
"@libsql/linux-arm64-gnu": "0.5.29",
"@libsql/linux-arm64-musl": "0.5.29",
"@libsql/linux-x64-gnu": "0.5.29",
"@libsql/linux-x64-musl": "0.5.29",
"@libsql/win32-x64-msvc": "0.5.29"
}
},
"node_modules/libsql/node_modules/detect-libc": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz",
"integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=8"
}
},
"node_modules/lightningcss": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
@ -5025,6 +4829,95 @@
"dev": true,
"license": "MIT"
},
"node_modules/pg": {
"version": "8.23.0",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.23.0.tgz",
"integrity": "sha512-Ip2EQCngowJLGOfCwkFhPXU7/ljlhn6Rxlmy4XYfL2Y+vyRM59+8uR2xqRWKdYmbXmxCFOAmKxBuSUCdF34qLg==",
"license": "MIT",
"dependencies": {
"pg-connection-string": "^2.14.0",
"pg-pool": "^3.14.0",
"pg-protocol": "^1.16.0",
"pg-types": "2.2.0",
"pgpass": "1.0.5"
},
"engines": {
"node": ">= 16.0.0"
},
"optionalDependencies": {
"pg-cloudflare": "^1.4.0"
},
"peerDependencies": {
"pg-native": ">=3.0.1"
},
"peerDependenciesMeta": {
"pg-native": {
"optional": true
}
}
},
"node_modules/pg-cloudflare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz",
"integrity": "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==",
"license": "MIT",
"optional": true
},
"node_modules/pg-connection-string": {
"version": "2.14.0",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.14.0.tgz",
"integrity": "sha512-XwWDGcLRGCXAR8F/AM5bG7Q+A3Wm2s6QeEjlOKZLlH3UYcguiqCWKyWXVag5TLTIjR7oOJUY8kcADaZgWPyLeg==",
"license": "MIT"
},
"node_modules/pg-int8": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
"license": "ISC",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/pg-pool": {
"version": "3.14.0",
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz",
"integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==",
"license": "MIT",
"peerDependencies": {
"pg": ">=8.0"
}
},
"node_modules/pg-protocol": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.16.0.tgz",
"integrity": "sha512-sILXutLVjCLjcDuOmvhX5e2Z4cS5qG/6Bu3VkpFwdf/633ElGLpEh9bgmuI5I4sqKqkifQiGyiCcx1HdtrK7tg==",
"license": "MIT"
},
"node_modules/pg-types": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
"license": "MIT",
"dependencies": {
"pg-int8": "1.0.1",
"postgres-array": "~2.0.0",
"postgres-bytea": "~1.0.0",
"postgres-date": "~1.0.4",
"postgres-interval": "^1.1.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/pgpass": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
"license": "MIT",
"dependencies": {
"split2": "^4.1.0"
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@ -5221,6 +5114,45 @@
"node": ">=4"
}
},
"node_modules/postgres-array": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/postgres-bytea": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz",
"integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/postgres-date": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/postgres-interval": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
"license": "MIT",
"dependencies": {
"xtend": "^4.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@ -5278,13 +5210,6 @@
"node": ">=6"
}
},
"node_modules/promise-limit": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/promise-limit/-/promise-limit-2.7.0.tgz",
"integrity": "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==",
"dev": true,
"license": "ISC"
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@ -5566,6 +5491,15 @@
"source-map": "^0.6.0"
}
},
"node_modules/split2": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
"license": "ISC",
"engines": {
"node": ">= 10.x"
}
},
"node_modules/stackback": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
@ -6314,7 +6248,7 @@
"version": "8.21.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz",
"integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==",
"dev": true,
"devOptional": true,
"license": "MIT",
"engines": {
"node": ">=10.0.0"
@ -6332,6 +6266,15 @@
}
}
},
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"license": "MIT",
"engines": {
"node": ">=0.4"
}
},
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",

@ -30,6 +30,7 @@
"@sveltejs/kit": "^2.63.0",
"@sveltejs/vite-plugin-svelte": "^7.1.2",
"@types/node": "^22",
"@types/pg": "^8.23.1",
"@vitest/browser-playwright": "^4.1.8",
"drizzle-kit": "^0.31.10",
"eslint": "^10.4.1",
@ -51,8 +52,8 @@
"vitest-browser-svelte": "^2.1.1"
},
"dependencies": {
"@libsql/client": "^0.17.3",
"drizzle-orm": "^0.45.2",
"pg": "^8.23.0",
"stripe": "^22.6.1"
}
}

@ -1,10 +1,25 @@
import { drizzle } from 'drizzle-orm/libsql';
import { createClient } from '@libsql/client';
import { drizzle } from 'drizzle-orm/node-postgres';
import pg from 'pg';
import * as schema from './schema';
import { env } from '$env/dynamic/private';
if (!env.DATABASE_URL) throw new Error('DATABASE_URL is not set');
const client = createClient({ url: env.DATABASE_URL });
/**
* Un pool, no una conexion suelta.
*
* El servidor atiende varias peticiones a la vez y cada una consulta lo suyo;
* con una sola conexion se pondrian en fila. El pool las reparte y reutiliza,
* que es lo que hace que abrir una pagina no espere a la anterior.
*
* `max` deliberadamente bajo: Postgres cobra caro cada conexion —cada una es un
* proceso— y este sitio no necesita mas. Subirlo sin medir suele empeorar.
*/
const pool = new pg.Pool({
connectionString: env.DATABASE_URL,
max: 10,
idleTimeoutMillis: 30_000,
connectionTimeoutMillis: 10_000
});
export const db = drizzle(client, { schema });
export const db = drizzle(pool, { schema });

@ -12,30 +12,40 @@
*/
import { relations } from 'drizzle-orm';
import { index, integer, primaryKey, sqliteTable, text } from 'drizzle-orm/sqlite-core';
import { bigint, index, integer, pgTable, primaryKey, text, timestamp } from 'drizzle-orm/pg-core';
const id = () =>
text('id')
.primaryKey()
.$defaultFn(() => crypto.randomUUID());
/** Marca de tiempo obligatoria que se rellena sola al insertar. */
/**
* Marca de tiempo obligatoria que se rellena sola al insertar.
*
* Con zona horaria siempre: el servidor puede estar en una y quien mira en
* otra, y un instante sin zona es un instante que no se sabe cual es. Y en
* modo `date` porque el codigo trabaja con `Date`; el modo por defecto de
* Postgres en Drizzle devuelve cadenas.
*/
const ahora = (columna: string) =>
integer(columna, { mode: 'timestamp' })
timestamp(columna, { withTimezone: true, mode: 'date' })
.notNull()
.$defaultFn(() => new Date());
/** Instante opcional, con el mismo criterio. */
const instante = (columna: string) => timestamp(columna, { withTimezone: true, mode: 'date' });
/* -------------------------------------------------------------------------
* Cuentas
* ---------------------------------------------------------------------- */
export const usuario = sqliteTable('usuario', {
export const usuario = pgTable('usuario', {
id: id(),
/** Siempre normalizado en minusculas y sin espacios. */
email: text('email').notNull().unique(),
nombre: text('nombre'),
creadoEn: ahora('creado_en'),
ultimoAccesoEn: integer('ultimo_acceso_en', { mode: 'timestamp' })
ultimoAccesoEn: instante('ultimo_acceso_en')
});
/**
@ -45,21 +55,21 @@ export const usuario = sqliteTable('usuario', {
* entrar en una cuenta ajena. Es la misma razon por la que las sesiones
* guardan el hash del token.
*/
export const codigoAcceso = sqliteTable(
export const codigoAcceso = pgTable(
'codigo_acceso',
{
id: id(),
email: text('email').notNull(),
hash: text('hash').notNull(),
expiraEn: integer('expira_en', { mode: 'timestamp' }).notNull(),
expiraEn: instante('expira_en').notNull(),
intentos: integer('intentos').notNull().default(0),
usadoEn: integer('usado_en', { mode: 'timestamp' }),
usadoEn: instante('usado_en'),
creadoEn: ahora('creado_en')
},
(t) => [index('codigo_acceso_email').on(t.email)]
);
export const sesion = sqliteTable(
export const sesion = pgTable(
'sesion',
{
/** Hash del token que viaja en la cookie. */
@ -67,7 +77,7 @@ export const sesion = sqliteTable(
usuarioId: text('usuario_id')
.notNull()
.references(() => usuario.id, { onDelete: 'cascade' }),
expiraEn: integer('expira_en', { mode: 'timestamp' }).notNull(),
expiraEn: instante('expira_en').notNull(),
creadaEn: ahora('creada_en')
},
(t) => [index('sesion_usuario').on(t.usuarioId)]
@ -83,14 +93,19 @@ export const sesion = sqliteTable(
*
* `expiraEn` va en milisegundos y no como fecha porque las ventanas son
* aritmetica pura, y algunas duran menos de un minuto.
*
* Y va en `bigint`, no en `integer`: en SQLite un entero son ocho bytes, pero
* en Postgres son cuatro y el maximo se queda en 2.147.483.647. Un instante en
* milisegundos ronda el billon y siete, asi que en `integer` desbordaria y el
* limitador dejaria de limitar.
*/
export const limite = sqliteTable(
export const limite = pgTable(
'limite',
{
/** Que se esta limitando: "acceso:ip:1.2.3.4", "acceso:correo:..." */
clave: text('clave').primaryKey(),
intentos: integer('intentos').notNull(),
expiraEn: integer('expira_en').notNull()
expiraEn: bigint('expira_en', { mode: 'number' }).notNull()
},
(t) => [index('limite_expira').on(t.expiraEn)]
);
@ -104,7 +119,7 @@ export const limite = sqliteTable(
* entrado, identificado por una cookie, y al iniciar sesion se fusiona con el
* del usuario.
*/
export const carrito = sqliteTable(
export const carrito = pgTable(
'carrito',
{
id: id(),
@ -115,7 +130,7 @@ export const carrito = sqliteTable(
(t) => [index('carrito_usuario').on(t.usuarioId)]
);
export const carritoItem = sqliteTable(
export const carritoItem = pgTable(
'carrito_item',
{
carritoId: text('carrito_id')
@ -131,7 +146,7 @@ export const carritoItem = sqliteTable(
* Pedidos y compras
* ---------------------------------------------------------------------- */
export const pedido = sqliteTable(
export const pedido = pgTable(
'pedido',
{
id: id(),
@ -149,12 +164,12 @@ export const pedido = sqliteTable(
totalCentimos: integer('total_centimos').notNull(),
moneda: text('moneda').notNull().default('eur'),
creadoEn: ahora('creado_en'),
pagadoEn: integer('pagado_en', { mode: 'timestamp' })
pagadoEn: instante('pagado_en')
},
(t) => [index('pedido_usuario').on(t.usuarioId)]
);
export const pedidoItem = sqliteTable(
export const pedidoItem = pgTable(
'pedido_item',
{
pedidoId: text('pedido_id')
@ -172,7 +187,7 @@ export const pedidoItem = sqliteTable(
* proposito: responde a "puede esta persona descargar este tema" en una sola
* consulta, sin recorrer pedidos.
*/
export const compra = sqliteTable(
export const compra = pgTable(
'compra',
{
usuarioId: text('usuario_id')
@ -202,7 +217,7 @@ export const compra = sqliteTable(
* duplica nada. El indice por cuenta es para pintar la lista, que es la
* consulta que se hace de verdad.
*/
export const favorito = sqliteTable(
export const favorito = pgTable(
'favorito',
{
usuarioId: text('usuario_id')
@ -231,7 +246,7 @@ export const favorito = sqliteTable(
* El indice por `cancion_slug` es para la media: se calcula por tema, y sin el
* habria que recorrer la tabla entera en cada ficha.
*/
export const valoracion = sqliteTable(
export const valoracion = pgTable(
'valoracion',
{
usuarioId: text('usuario_id')

@ -50,10 +50,17 @@ export function aPuntuacion(valor: FormDataEntryValue | null): number | undefine
/** Media y número de votos de un tema. */
export async function resumenDeCancion(cancionSlug: string): Promise<ResumenDeValoracion> {
/*
* Los moldes no son decoración. En Postgres `avg()` devuelve `numeric` y
* `count()` devuelve `bigint`, y el driver entrega las dos como **cadena**
* para no perder precisión. Sin convertirlas, `cuantas === 0` nunca se
* cumpliría —es `"0"`— y el singular de «1 voto» tampoco, porque `"1" === 1`
* es falso. Se convierten aquí, que es donde se sabe qué son.
*/
const [fila] = await db
.select({
media: sql<number | null>`avg(${valoracion.puntuacion})`,
cuantas: sql<number>`count(*)`
media: sql<number | null>`avg(${valoracion.puntuacion})::float8`,
cuantas: sql<number>`count(*)::int`
})
.from(valoracion)
.where(eq(valoracion.cancionSlug, cancionSlug));

Loading…
Cancel
Save

Powered by TurnKey Linux.