diff --git a/backend/Dockerfile b/backend/Dockerfile index cd1c9a7..1e78b54 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -12,5 +12,8 @@ COPY --from=builder /app/dist ./dist COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/package.json ./ COPY --from=builder /app/prisma ./prisma +COPY --from=builder /app/prisma.config.js ./ +COPY --from=builder /app/entrypoint.sh ./ +RUN chmod +x entrypoint.sh EXPOSE 3001 -CMD ["node", "dist/main.js"] +CMD ["sh", "entrypoint.sh"] diff --git a/backend/entrypoint.sh b/backend/entrypoint.sh new file mode 100644 index 0000000..a14004a --- /dev/null +++ b/backend/entrypoint.sh @@ -0,0 +1,7 @@ +#!/bin/sh +set -e + +echo "Running database migrations..." +node_modules/.bin/prisma migrate deploy + +exec node dist/main.js diff --git a/backend/package.json b/backend/package.json index 0c54b7f..c6441f7 100644 --- a/backend/package.json +++ b/backend/package.json @@ -11,6 +11,7 @@ "start:prod": "node dist/main", "prisma:generate": "prisma generate", "prisma:pull": "prisma db pull", + "prisma:migrate": "prisma migrate deploy", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix" }, "keywords": [], diff --git a/backend/prisma/migrations/0000_init/migration.sql b/backend/prisma/migrations/0000_init/migration.sql index 3c5a1d0..4fdd1bc 100644 --- a/backend/prisma/migrations/0000_init/migration.sql +++ b/backend/prisma/migrations/0000_init/migration.sql @@ -1,16 +1,19 @@ -Loaded Prisma config from prisma.config.js. - -- CreateSchema CREATE SCHEMA IF NOT EXISTS "public"; -- CreateEnum -CREATE TYPE "service_location" AS ENUM ('office', 'delivery'); +DO $$ BEGIN + CREATE TYPE "service_location" AS ENUM ('office', 'delivery'); +EXCEPTION WHEN duplicate_object THEN NULL; +END $$; --- CreateEnum -CREATE TYPE "service_status" AS ENUM ('pending', 'accepted', 'denied', 'active', 'cancelled', 'completed', 'self_booked'); +DO $$ BEGIN + CREATE TYPE "service_status" AS ENUM ('pending', 'accepted', 'denied', 'active', 'cancelled', 'completed', 'self_booked'); +EXCEPTION WHEN duplicate_object THEN NULL; +END $$; -- CreateTable -CREATE TABLE "chats" ( +CREATE TABLE IF NOT EXISTS "chats" ( "id" UUID NOT NULL DEFAULT uuid_generate_v4(), "user_id" UUID NOT NULL, "professional_id" UUID NOT NULL, @@ -19,8 +22,7 @@ CREATE TABLE "chats" ( CONSTRAINT "chats_pkey" PRIMARY KEY ("id") ); --- CreateTable -CREATE TABLE "cities" ( +CREATE TABLE IF NOT EXISTS "cities" ( "id" UUID NOT NULL DEFAULT uuid_generate_v4(), "region_id" UUID NOT NULL, "name" VARCHAR(255) NOT NULL, @@ -30,8 +32,7 @@ CREATE TABLE "cities" ( CONSTRAINT "cities_pkey" PRIMARY KEY ("id") ); --- CreateTable -CREATE TABLE "comments" ( +CREATE TABLE IF NOT EXISTS "comments" ( "id" UUID NOT NULL DEFAULT uuid_generate_v4(), "author_id" UUID NOT NULL, "destination_id" UUID NOT NULL, @@ -44,16 +45,14 @@ CREATE TABLE "comments" ( CONSTRAINT "comments_pkey" PRIMARY KEY ("id") ); --- CreateTable -CREATE TABLE "countries" ( +CREATE TABLE IF NOT EXISTS "countries" ( "id" UUID NOT NULL DEFAULT uuid_generate_v4(), "name" VARCHAR(255) NOT NULL, CONSTRAINT "countries_pkey" PRIMARY KEY ("id") ); --- CreateTable -CREATE TABLE "messages" ( +CREATE TABLE IF NOT EXISTS "messages" ( "id" UUID NOT NULL DEFAULT uuid_generate_v4(), "chat_id" UUID NOT NULL, "sender_id" UUID NOT NULL, @@ -63,8 +62,7 @@ CREATE TABLE "messages" ( CONSTRAINT "messages_pkey" PRIMARY KEY ("id") ); --- CreateTable -CREATE TABLE "payment_methods" ( +CREATE TABLE IF NOT EXISTS "payment_methods" ( "id" UUID NOT NULL DEFAULT uuid_generate_v4(), "professional_id" UUID NOT NULL, "nequi" BOOLEAN DEFAULT false, @@ -74,8 +72,7 @@ CREATE TABLE "payment_methods" ( CONSTRAINT "payment_methods_pkey" PRIMARY KEY ("id") ); --- CreateTable -CREATE TABLE "professionals" ( +CREATE TABLE IF NOT EXISTS "professionals" ( "id" UUID NOT NULL DEFAULT uuid_generate_v4(), "user_id" UUID NOT NULL, "identification" VARCHAR(50), @@ -98,16 +95,14 @@ CREATE TABLE "professionals" ( CONSTRAINT "professionals_pkey" PRIMARY KEY ("id") ); --- CreateTable -CREATE TABLE "professions" ( +CREATE TABLE IF NOT EXISTS "professions" ( "id" UUID NOT NULL DEFAULT uuid_generate_v4(), "name" VARCHAR(255) NOT NULL, CONSTRAINT "professions_pkey" PRIMARY KEY ("id") ); --- CreateTable -CREATE TABLE "regions" ( +CREATE TABLE IF NOT EXISTS "regions" ( "id" UUID NOT NULL DEFAULT uuid_generate_v4(), "country_id" UUID NOT NULL, "name" VARCHAR(255) NOT NULL, @@ -115,8 +110,7 @@ CREATE TABLE "regions" ( CONSTRAINT "regions_pkey" PRIMARY KEY ("id") ); --- CreateTable -CREATE TABLE "reputations" ( +CREATE TABLE IF NOT EXISTS "reputations" ( "user_id" UUID NOT NULL, "total" INTEGER NOT NULL DEFAULT 0, "average" DECIMAL(3,2) NOT NULL DEFAULT 0, @@ -127,8 +121,7 @@ CREATE TABLE "reputations" ( CONSTRAINT "reputations_pkey" PRIMARY KEY ("user_id") ); --- CreateTable -CREATE TABLE "schedules" ( +CREATE TABLE IF NOT EXISTS "schedules" ( "id" UUID NOT NULL DEFAULT uuid_generate_v4(), "professional_id" UUID NOT NULL, "day_of_week" SMALLINT NOT NULL, @@ -142,8 +135,7 @@ CREATE TABLE "schedules" ( CONSTRAINT "schedules_pkey" PRIMARY KEY ("id") ); --- CreateTable -CREATE TABLE "services" ( +CREATE TABLE IF NOT EXISTS "services" ( "id" UUID NOT NULL DEFAULT uuid_generate_v4(), "professional_id" UUID NOT NULL, "user_id" UUID NOT NULL, @@ -166,8 +158,7 @@ CREATE TABLE "services" ( CONSTRAINT "services_pkey" PRIMARY KEY ("id") ); --- CreateTable -CREATE TABLE "settings" ( +CREATE TABLE IF NOT EXISTS "settings" ( "key" VARCHAR(100) NOT NULL, "value" JSONB NOT NULL, "updated_at" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, @@ -175,8 +166,7 @@ CREATE TABLE "settings" ( CONSTRAINT "settings_pkey" PRIMARY KEY ("key") ); --- CreateTable -CREATE TABLE "specializations" ( +CREATE TABLE IF NOT EXISTS "specializations" ( "id" UUID NOT NULL DEFAULT uuid_generate_v4(), "professional_id" UUID NOT NULL, "name" VARCHAR(255) NOT NULL, @@ -185,8 +175,16 @@ CREATE TABLE "specializations" ( CONSTRAINT "specializations_pkey" PRIMARY KEY ("id") ); --- CreateTable -CREATE TABLE "users" ( +CREATE TABLE IF NOT EXISTS "suggestions" ( + "id" UUID NOT NULL DEFAULT uuid_generate_v4(), + "name" VARCHAR(255), + "message" TEXT NOT NULL, + "created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "suggestions_pkey" PRIMARY KEY ("id") +); + +CREATE TABLE IF NOT EXISTS "users" ( "id" UUID NOT NULL DEFAULT uuid_generate_v4(), "email" VARCHAR(255), "phone" VARCHAR(20), @@ -208,126 +206,113 @@ CREATE TABLE "users" ( ); -- CreateIndex -CREATE INDEX "idx_chats_professional_id" ON "chats"("professional_id"); +CREATE INDEX IF NOT EXISTS "idx_chats_professional_id" ON "chats"("professional_id"); +CREATE INDEX IF NOT EXISTS "idx_chats_user_id" ON "chats"("user_id"); +CREATE UNIQUE INDEX IF NOT EXISTS "chats_user_id_professional_id_key" ON "chats"("user_id", "professional_id"); +CREATE INDEX IF NOT EXISTS "idx_cities_region_id" ON "cities"("region_id"); +CREATE INDEX IF NOT EXISTS "idx_comments_author_id" ON "comments"("author_id"); +CREATE INDEX IF NOT EXISTS "idx_comments_destination_id" ON "comments"("destination_id"); +CREATE INDEX IF NOT EXISTS "idx_comments_service_id" ON "comments"("service_id"); +CREATE UNIQUE INDEX IF NOT EXISTS "countries_name_key" ON "countries"("name"); +CREATE INDEX IF NOT EXISTS "idx_messages_chat_id" ON "messages"("chat_id"); +CREATE INDEX IF NOT EXISTS "idx_messages_created_at" ON "messages"("created_at"); +CREATE UNIQUE INDEX IF NOT EXISTS "payment_methods_professional_id_key" ON "payment_methods"("professional_id"); +CREATE UNIQUE INDEX IF NOT EXISTS "professionals_user_id_key" ON "professionals"("user_id"); +CREATE INDEX IF NOT EXISTS "idx_professionals_user_id" ON "professionals"("user_id"); +CREATE UNIQUE INDEX IF NOT EXISTS "professions_name_key" ON "professions"("name"); +CREATE INDEX IF NOT EXISTS "idx_regions_country_id" ON "regions"("country_id"); +CREATE INDEX IF NOT EXISTS "idx_schedules_professional_id" ON "schedules"("professional_id"); +CREATE UNIQUE INDEX IF NOT EXISTS "schedules_professional_id_day_of_week_key" ON "schedules"("professional_id", "day_of_week"); +CREATE INDEX IF NOT EXISTS "idx_services_day" ON "services"("day"); +CREATE INDEX IF NOT EXISTS "idx_services_professional_id" ON "services"("professional_id"); +CREATE INDEX IF NOT EXISTS "idx_services_status" ON "services"("status"); +CREATE INDEX IF NOT EXISTS "idx_services_user_id" ON "services"("user_id"); +CREATE INDEX IF NOT EXISTS "idx_specializations_professional_id" ON "specializations"("professional_id"); +CREATE UNIQUE INDEX IF NOT EXISTS "users_email_key" ON "users"("email"); +CREATE UNIQUE INDEX IF NOT EXISTS "users_phone_key" ON "users"("phone"); --- CreateIndex -CREATE INDEX "idx_chats_user_id" ON "chats"("user_id"); +-- AddForeignKey (idempotent via DO blocks) +DO $$ BEGIN + ALTER TABLE "chats" ADD CONSTRAINT "chats_professional_id_fkey" FOREIGN KEY ("professional_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION; +EXCEPTION WHEN duplicate_object THEN NULL; +END $$; --- CreateIndex -CREATE UNIQUE INDEX "chats_user_id_professional_id_key" ON "chats"("user_id", "professional_id"); +DO $$ BEGIN + ALTER TABLE "chats" ADD CONSTRAINT "chats_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION; +EXCEPTION WHEN duplicate_object THEN NULL; +END $$; --- CreateIndex -CREATE INDEX "idx_cities_region_id" ON "cities"("region_id"); +DO $$ BEGIN + ALTER TABLE "cities" ADD CONSTRAINT "cities_region_id_fkey" FOREIGN KEY ("region_id") REFERENCES "regions"("id") ON DELETE CASCADE ON UPDATE NO ACTION; +EXCEPTION WHEN duplicate_object THEN NULL; +END $$; --- CreateIndex -CREATE INDEX "idx_comments_author_id" ON "comments"("author_id"); +DO $$ BEGIN + ALTER TABLE "comments" ADD CONSTRAINT "comments_author_id_fkey" FOREIGN KEY ("author_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION; +EXCEPTION WHEN duplicate_object THEN NULL; +END $$; --- CreateIndex -CREATE INDEX "idx_comments_destination_id" ON "comments"("destination_id"); +DO $$ BEGIN + ALTER TABLE "comments" ADD CONSTRAINT "comments_destination_id_fkey" FOREIGN KEY ("destination_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION; +EXCEPTION WHEN duplicate_object THEN NULL; +END $$; --- CreateIndex -CREATE INDEX "idx_comments_service_id" ON "comments"("service_id"); +DO $$ BEGIN + ALTER TABLE "comments" ADD CONSTRAINT "comments_service_id_fkey" FOREIGN KEY ("service_id") REFERENCES "services"("id") ON DELETE NO ACTION ON UPDATE NO ACTION; +EXCEPTION WHEN duplicate_object THEN NULL; +END $$; --- CreateIndex -CREATE UNIQUE INDEX "countries_name_key" ON "countries"("name"); +DO $$ BEGIN + ALTER TABLE "messages" ADD CONSTRAINT "messages_chat_id_fkey" FOREIGN KEY ("chat_id") REFERENCES "chats"("id") ON DELETE CASCADE ON UPDATE NO ACTION; +EXCEPTION WHEN duplicate_object THEN NULL; +END $$; --- CreateIndex -CREATE INDEX "idx_messages_chat_id" ON "messages"("chat_id"); +DO $$ BEGIN + ALTER TABLE "messages" ADD CONSTRAINT "messages_sender_id_fkey" FOREIGN KEY ("sender_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION; +EXCEPTION WHEN duplicate_object THEN NULL; +END $$; --- CreateIndex -CREATE INDEX "idx_messages_created_at" ON "messages"("created_at"); +DO $$ BEGIN + ALTER TABLE "payment_methods" ADD CONSTRAINT "payment_methods_professional_id_fkey" FOREIGN KEY ("professional_id") REFERENCES "professionals"("id") ON DELETE CASCADE ON UPDATE NO ACTION; +EXCEPTION WHEN duplicate_object THEN NULL; +END $$; --- CreateIndex -CREATE UNIQUE INDEX "payment_methods_professional_id_key" ON "payment_methods"("professional_id"); +DO $$ BEGIN + ALTER TABLE "professionals" ADD CONSTRAINT "professionals_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION; +EXCEPTION WHEN duplicate_object THEN NULL; +END $$; --- CreateIndex -CREATE UNIQUE INDEX "professionals_user_id_key" ON "professionals"("user_id"); +DO $$ BEGIN + ALTER TABLE "regions" ADD CONSTRAINT "regions_country_id_fkey" FOREIGN KEY ("country_id") REFERENCES "countries"("id") ON DELETE CASCADE ON UPDATE NO ACTION; +EXCEPTION WHEN duplicate_object THEN NULL; +END $$; --- CreateIndex -CREATE INDEX "idx_professionals_user_id" ON "professionals"("user_id"); +DO $$ BEGIN + ALTER TABLE "reputations" ADD CONSTRAINT "reputations_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION; +EXCEPTION WHEN duplicate_object THEN NULL; +END $$; --- CreateIndex -CREATE UNIQUE INDEX "professions_name_key" ON "professions"("name"); +DO $$ BEGIN + ALTER TABLE "schedules" ADD CONSTRAINT "schedules_professional_id_fkey" FOREIGN KEY ("professional_id") REFERENCES "professionals"("id") ON DELETE CASCADE ON UPDATE NO ACTION; +EXCEPTION WHEN duplicate_object THEN NULL; +END $$; --- CreateIndex -CREATE INDEX "idx_regions_country_id" ON "regions"("country_id"); +DO $$ BEGIN + ALTER TABLE "services" ADD CONSTRAINT "services_professional_id_fkey" FOREIGN KEY ("professional_id") REFERENCES "professionals"("id") ON DELETE NO ACTION ON UPDATE NO ACTION; +EXCEPTION WHEN duplicate_object THEN NULL; +END $$; --- CreateIndex -CREATE INDEX "idx_schedules_professional_id" ON "schedules"("professional_id"); +DO $$ BEGIN + ALTER TABLE "services" ADD CONSTRAINT "services_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION; +EXCEPTION WHEN duplicate_object THEN NULL; +END $$; --- CreateIndex -CREATE UNIQUE INDEX "schedules_professional_id_day_of_week_key" ON "schedules"("professional_id", "day_of_week"); +DO $$ BEGIN + ALTER TABLE "specializations" ADD CONSTRAINT "specializations_professional_id_fkey" FOREIGN KEY ("professional_id") REFERENCES "professionals"("id") ON DELETE CASCADE ON UPDATE NO ACTION; +EXCEPTION WHEN duplicate_object THEN NULL; +END $$; --- CreateIndex -CREATE INDEX "idx_services_day" ON "services"("day"); - --- CreateIndex -CREATE INDEX "idx_services_professional_id" ON "services"("professional_id"); - --- CreateIndex -CREATE INDEX "idx_services_status" ON "services"("status"); - --- CreateIndex -CREATE INDEX "idx_services_user_id" ON "services"("user_id"); - --- CreateIndex -CREATE INDEX "idx_specializations_professional_id" ON "specializations"("professional_id"); - --- CreateIndex -CREATE UNIQUE INDEX "users_email_key" ON "users"("email"); - --- CreateIndex -CREATE UNIQUE INDEX "users_phone_key" ON "users"("phone"); - --- AddForeignKey -ALTER TABLE "chats" ADD CONSTRAINT "chats_professional_id_fkey" FOREIGN KEY ("professional_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION; - --- AddForeignKey -ALTER TABLE "chats" ADD CONSTRAINT "chats_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION; - --- AddForeignKey -ALTER TABLE "cities" ADD CONSTRAINT "cities_region_id_fkey" FOREIGN KEY ("region_id") REFERENCES "regions"("id") ON DELETE CASCADE ON UPDATE NO ACTION; - --- AddForeignKey -ALTER TABLE "comments" ADD CONSTRAINT "comments_author_id_fkey" FOREIGN KEY ("author_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION; - --- AddForeignKey -ALTER TABLE "comments" ADD CONSTRAINT "comments_destination_id_fkey" FOREIGN KEY ("destination_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION; - --- AddForeignKey -ALTER TABLE "comments" ADD CONSTRAINT "comments_service_id_fkey" FOREIGN KEY ("service_id") REFERENCES "services"("id") ON DELETE NO ACTION ON UPDATE NO ACTION; - --- AddForeignKey -ALTER TABLE "messages" ADD CONSTRAINT "messages_chat_id_fkey" FOREIGN KEY ("chat_id") REFERENCES "chats"("id") ON DELETE CASCADE ON UPDATE NO ACTION; - --- AddForeignKey -ALTER TABLE "messages" ADD CONSTRAINT "messages_sender_id_fkey" FOREIGN KEY ("sender_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION; - --- AddForeignKey -ALTER TABLE "payment_methods" ADD CONSTRAINT "payment_methods_professional_id_fkey" FOREIGN KEY ("professional_id") REFERENCES "professionals"("id") ON DELETE CASCADE ON UPDATE NO ACTION; - --- AddForeignKey -ALTER TABLE "professionals" ADD CONSTRAINT "professionals_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION; - --- AddForeignKey -ALTER TABLE "regions" ADD CONSTRAINT "regions_country_id_fkey" FOREIGN KEY ("country_id") REFERENCES "countries"("id") ON DELETE CASCADE ON UPDATE NO ACTION; - --- AddForeignKey -ALTER TABLE "reputations" ADD CONSTRAINT "reputations_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION; - --- AddForeignKey -ALTER TABLE "schedules" ADD CONSTRAINT "schedules_professional_id_fkey" FOREIGN KEY ("professional_id") REFERENCES "professionals"("id") ON DELETE CASCADE ON UPDATE NO ACTION; - --- AddForeignKey -ALTER TABLE "services" ADD CONSTRAINT "services_professional_id_fkey" FOREIGN KEY ("professional_id") REFERENCES "professionals"("id") ON DELETE NO ACTION ON UPDATE NO ACTION; - --- AddForeignKey -ALTER TABLE "services" ADD CONSTRAINT "services_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION; - --- AddForeignKey -ALTER TABLE "specializations" ADD CONSTRAINT "specializations_professional_id_fkey" FOREIGN KEY ("professional_id") REFERENCES "professionals"("id") ON DELETE CASCADE ON UPDATE NO ACTION; - --- CreateTrigger: update reputation on comment insert/update +-- Trigger: update reputation on comment insert/update CREATE OR REPLACE FUNCTION update_reputation() RETURNS TRIGGER AS $$ BEGIN @@ -360,4 +345,3 @@ CREATE OR REPLACE TRIGGER trg_update_reputation AFTER INSERT OR UPDATE ON comments FOR EACH ROW EXECUTE FUNCTION update_reputation(); - diff --git a/backend/prisma/migrations/0002_add_message_logs/migration.sql b/backend/prisma/migrations/0002_add_message_logs/migration.sql new file mode 100644 index 0000000..9296ab4 --- /dev/null +++ b/backend/prisma/migrations/0002_add_message_logs/migration.sql @@ -0,0 +1,12 @@ +-- CreateTable +CREATE TABLE IF NOT EXISTS "message_logs" ( + "id" UUID NOT NULL DEFAULT uuid_generate_v4(), + "channel" VARCHAR(10) NOT NULL, + "recipient" VARCHAR(255) NOT NULL, + "body" TEXT NOT NULL, + "status" VARCHAR(20) NOT NULL, + "error" TEXT, + "created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "message_logs_pkey" PRIMARY KEY ("id") +); diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index f40d83b..6bdefbd 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -194,10 +194,10 @@ model suggestions { model message_logs { id String @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid - channel String @db.VarChar(10) // 'sms' | 'email' + channel String @db.VarChar(10) recipient String @db.VarChar(255) body String - status String @db.VarChar(20) // 'sent' | 'error' + status String @db.VarChar(20) error String? created_at DateTime @default(now()) @db.Timestamptz(6) }