From 01f0b82ac656083dfa26316f0a3ffced5895a1c8 Mon Sep 17 00:00:00 2001 From: Mahdi Date: Thu, 30 Jul 2026 19:14:06 +0330 Subject: [PATCH] Switch Prisma from SQLite to PostgreSQL for production deploy. Co-authored-by: Cursor --- .env.example | 10 +- .../migration.sql | 313 ++++++++++++++++++ prisma/migrations/migration_lock.toml | 4 +- .../20260722125821_init/migration.sql | 0 .../migration.sql | 0 .../migration.sql | 0 .../migration.sql | 0 .../migration.sql | 0 .../migration.sql | 0 .../migration.sql | 0 .../migration.sql | 0 .../migration.sql | 0 .../migration.sql | 0 prisma/migrations_sqlite_archive/README.md | 4 + .../migration_lock.toml | 3 + prisma/schema.prisma | 4 +- 16 files changed, 332 insertions(+), 6 deletions(-) create mode 100644 prisma/migrations/20260730180000_init_postgres/migration.sql rename prisma/{migrations => migrations_sqlite_archive}/20260722125821_init/migration.sql (100%) rename prisma/{migrations => migrations_sqlite_archive}/20260722152640_add_app_ui_config/migration.sql (100%) rename prisma/{migrations => migrations_sqlite_archive}/20260723073638_sms_settings_cache/migration.sql (100%) rename prisma/{migrations => migrations_sqlite_archive}/20260723143623_sms_accounts_owned/migration.sql (100%) rename prisma/{migrations => migrations_sqlite_archive}/20260727120000_product_releases/migration.sql (100%) rename prisma/{migrations => migrations_sqlite_archive}/20260727140000_tenant_app_version/migration.sql (100%) rename prisma/{migrations => migrations_sqlite_archive}/20260727150000_users_mobile_admins/migration.sql (100%) rename prisma/{migrations => migrations_sqlite_archive}/20260727160000_server_accounts/migration.sql (100%) rename prisma/{migrations => migrations_sqlite_archive}/20260728120000_plugin_subscriptions/migration.sql (100%) rename prisma/{migrations => migrations_sqlite_archive}/20260729150000_sms_panel_parity/migration.sql (100%) create mode 100644 prisma/migrations_sqlite_archive/README.md create mode 100644 prisma/migrations_sqlite_archive/migration_lock.toml diff --git a/.env.example b/.env.example index aa27b50..50bcf14 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,14 @@ PORT=3001 -DATABASE_URL="file:./dev.db" +NODE_ENV=development + +# PostgreSQL (local Docker or remote). SQLite دیگر پشتیبانی نمی‌شود. +# مثال لوکال: postgresql://postgres:postgres@127.0.0.1:5432/superadmin?schema=public +# مثال سرور: postgresql://montime:PASSWORD@127.0.0.1:5432/superadmin?schema=public +DATABASE_URL="postgresql://montime:CHANGE_ME@127.0.0.1:5432/superadmin?schema=public" + JWT_SECRET="change-me-in-production-use-long-random-string" JWT_EXPIRES_IN="7d" -CORS_ORIGIN="http://localhost:5173,http://localhost:3100" +CORS_ORIGIN="http://localhost:5173,http://localhost:3100,https://adm.montzar.ir" # آدرس عمومی API برای ساخت URL لوگو (در پروداکشن دامنه HTTPS سوپرادمین) PUBLIC_API_URL="http://localhost:3001" diff --git a/prisma/migrations/20260730180000_init_postgres/migration.sql b/prisma/migrations/20260730180000_init_postgres/migration.sql new file mode 100644 index 0000000..41bf2c3 --- /dev/null +++ b/prisma/migrations/20260730180000_init_postgres/migration.sql @@ -0,0 +1,313 @@ +-- CreateSchema +CREATE SCHEMA IF NOT EXISTS "public"; + +-- CreateEnum +CREATE TYPE "public"."ProductType" AS ENUM ('hamkar', 'khanegi'); + +-- CreateEnum +CREATE TYPE "public"."UserRole" AS ENUM ('super_admin', 'viewer'); + +-- CreateEnum +CREATE TYPE "public"."SmsLedgerType" AS ENUM ('charge', 'debit', 'adjust'); + +-- CreateTable +CREATE TABLE "public"."users" ( + "id" TEXT NOT NULL, + "mobile" TEXT NOT NULL, + "first_name" TEXT NOT NULL, + "last_name" TEXT NOT NULL, + "email" TEXT, + "password_hash" TEXT NOT NULL, + "role" "public"."UserRole" NOT NULL DEFAULT 'super_admin', + "is_active" BOOLEAN NOT NULL DEFAULT true, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "users_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "public"."tenants" ( + "id" TEXT NOT NULL, + "product" "public"."ProductType" NOT NULL, + "slug" TEXT NOT NULL, + "name_fa" TEXT NOT NULL, + "name_en" TEXT, + "domain" TEXT NOT NULL, + "domains" JSONB, + "avatar_url" TEXT, + "primary_color" TEXT, + "secondary_color" TEXT, + "background_color" TEXT, + "node_server" TEXT NOT NULL, + "node_ws" TEXT, + "app_url" TEXT, + "admin_url" TEXT, + "remote_api_token" TEXT, + "sms_settings_cache" JSONB, + "sms_synced_at" TIMESTAMP(3), + "app_ui_config" JSONB, + "app_version" TEXT NOT NULL DEFAULT '1', + "is_active" BOOLEAN NOT NULL DEFAULT true, + "notes" TEXT, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "tenants_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "public"."sms_accounts" ( + "id" TEXT NOT NULL, + "tenant_id" TEXT NOT NULL, + "balance_rials" INTEGER NOT NULL DEFAULT 0, + "price_per_part_rials" INTEGER NOT NULL DEFAULT 2500, + "is_blocked" BOOLEAN NOT NULL DEFAULT false, + "alert_critical_rials" INTEGER NOT NULL DEFAULT 10000, + "alert_warning_rials" INTEGER NOT NULL DEFAULT 100000, + "gateway_provider" TEXT, + "melipayamak_user" TEXT, + "melipayamak_password" TEXT, + "melipayamak_from" TEXT, + "panel_username" TEXT, + "panel_password" TEXT, + "panel_from" TEXT, + "charge_card" TEXT, + "charge_iban" TEXT, + "charge_holder" TEXT, + "charge_instruction" TEXT, + "extra_settings" JSONB, + "today_messages" INTEGER NOT NULL DEFAULT 0, + "today_parts" INTEGER NOT NULL DEFAULT 0, + "today_cost_rials" INTEGER NOT NULL DEFAULT 0, + "month_messages" INTEGER NOT NULL DEFAULT 0, + "month_parts" INTEGER NOT NULL DEFAULT 0, + "month_cost_rials" INTEGER NOT NULL DEFAULT 0, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "sms_accounts_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "public"."sms_ledgers" ( + "id" TEXT NOT NULL, + "sms_account_id" TEXT NOT NULL, + "type" "public"."SmsLedgerType" NOT NULL, + "amount_rials" INTEGER NOT NULL, + "balance_after_rials" INTEGER, + "parts" INTEGER, + "note" TEXT, + "created_by_id" TEXT, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "sms_ledgers_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "public"."sms_send_logs" ( + "id" TEXT NOT NULL, + "sms_account_id" TEXT NOT NULL, + "receiver" TEXT NOT NULL, + "body" TEXT NOT NULL, + "parts" INTEGER NOT NULL DEFAULT 1, + "price_per_part_rials" INTEGER NOT NULL, + "total_amount_rials" INTEGER NOT NULL, + "provider" TEXT, + "source" TEXT, + "is_alert" BOOLEAN NOT NULL DEFAULT false, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "sms_send_logs_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "public"."audit_logs" ( + "id" TEXT NOT NULL, + "action" TEXT NOT NULL, + "entity_type" TEXT NOT NULL, + "entity_id" TEXT, + "tenant_id" TEXT, + "user_id" TEXT, + "before" JSONB, + "after" JSONB, + "meta" JSONB, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "audit_logs_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "public"."server_accounts" ( + "id" TEXT NOT NULL, + "tenant_id" TEXT NOT NULL, + "is_dedicated" BOOLEAN NOT NULL DEFAULT false, + "server_name" TEXT, + "specs" TEXT, + "price_rials" INTEGER, + "charged_at" TIMESTAMP(3), + "plan_months" INTEGER, + "expires_at" TIMESTAMP(3), + "description" TEXT, + "alert_days_before" INTEGER NOT NULL DEFAULT 3, + "last_alert_date" TIMESTAMP(3), + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "server_accounts_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "public"."server_charges" ( + "id" TEXT NOT NULL, + "server_account_id" TEXT NOT NULL, + "charged_at" TIMESTAMP(3) NOT NULL, + "plan_months" INTEGER NOT NULL, + "price_rials" INTEGER NOT NULL, + "expires_at" TIMESTAMP(3) NOT NULL, + "note" TEXT, + "created_by_id" TEXT, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "server_charges_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "public"."plugin_subscriptions" ( + "id" TEXT NOT NULL, + "tenant_id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "specs" TEXT, + "price_rials" INTEGER, + "charged_at" TIMESTAMP(3), + "plan_months" INTEGER, + "expires_at" TIMESTAMP(3), + "description" TEXT, + "alert_days_before" INTEGER NOT NULL DEFAULT 3, + "last_alert_date" TIMESTAMP(3), + "is_active" BOOLEAN NOT NULL DEFAULT true, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "plugin_subscriptions_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "public"."plugin_charges" ( + "id" TEXT NOT NULL, + "plugin_id" TEXT NOT NULL, + "charged_at" TIMESTAMP(3) NOT NULL, + "plan_months" INTEGER NOT NULL, + "price_rials" INTEGER NOT NULL, + "expires_at" TIMESTAMP(3) NOT NULL, + "note" TEXT, + "created_by_id" TEXT, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "plugin_charges_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "users_mobile_key" ON "public"."users"("mobile"); + +-- CreateIndex +CREATE UNIQUE INDEX "users_email_key" ON "public"."users"("email"); + +-- CreateIndex +CREATE UNIQUE INDEX "tenants_slug_key" ON "public"."tenants"("slug"); + +-- CreateIndex +CREATE UNIQUE INDEX "tenants_domain_key" ON "public"."tenants"("domain"); + +-- CreateIndex +CREATE INDEX "tenants_product_idx" ON "public"."tenants"("product"); + +-- CreateIndex +CREATE INDEX "tenants_is_active_idx" ON "public"."tenants"("is_active"); + +-- CreateIndex +CREATE UNIQUE INDEX "sms_accounts_tenant_id_key" ON "public"."sms_accounts"("tenant_id"); + +-- CreateIndex +CREATE INDEX "sms_ledgers_sms_account_id_idx" ON "public"."sms_ledgers"("sms_account_id"); + +-- CreateIndex +CREATE INDEX "sms_ledgers_created_at_idx" ON "public"."sms_ledgers"("created_at"); + +-- CreateIndex +CREATE INDEX "sms_ledgers_type_idx" ON "public"."sms_ledgers"("type"); + +-- CreateIndex +CREATE INDEX "sms_send_logs_sms_account_id_idx" ON "public"."sms_send_logs"("sms_account_id"); + +-- CreateIndex +CREATE INDEX "sms_send_logs_created_at_idx" ON "public"."sms_send_logs"("created_at"); + +-- CreateIndex +CREATE INDEX "sms_send_logs_provider_idx" ON "public"."sms_send_logs"("provider"); + +-- CreateIndex +CREATE INDEX "audit_logs_tenant_id_idx" ON "public"."audit_logs"("tenant_id"); + +-- CreateIndex +CREATE INDEX "audit_logs_user_id_idx" ON "public"."audit_logs"("user_id"); + +-- CreateIndex +CREATE INDEX "audit_logs_created_at_idx" ON "public"."audit_logs"("created_at"); + +-- CreateIndex +CREATE UNIQUE INDEX "server_accounts_tenant_id_key" ON "public"."server_accounts"("tenant_id"); + +-- CreateIndex +CREATE INDEX "server_accounts_expires_at_idx" ON "public"."server_accounts"("expires_at"); + +-- CreateIndex +CREATE INDEX "server_accounts_is_dedicated_idx" ON "public"."server_accounts"("is_dedicated"); + +-- CreateIndex +CREATE INDEX "server_charges_server_account_id_idx" ON "public"."server_charges"("server_account_id"); + +-- CreateIndex +CREATE INDEX "server_charges_created_at_idx" ON "public"."server_charges"("created_at"); + +-- CreateIndex +CREATE INDEX "plugin_subscriptions_tenant_id_idx" ON "public"."plugin_subscriptions"("tenant_id"); + +-- CreateIndex +CREATE INDEX "plugin_subscriptions_expires_at_idx" ON "public"."plugin_subscriptions"("expires_at"); + +-- CreateIndex +CREATE INDEX "plugin_subscriptions_is_active_idx" ON "public"."plugin_subscriptions"("is_active"); + +-- CreateIndex +CREATE INDEX "plugin_charges_plugin_id_idx" ON "public"."plugin_charges"("plugin_id"); + +-- CreateIndex +CREATE INDEX "plugin_charges_created_at_idx" ON "public"."plugin_charges"("created_at"); + +-- AddForeignKey +ALTER TABLE "public"."sms_accounts" ADD CONSTRAINT "sms_accounts_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "public"."tenants"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "public"."sms_ledgers" ADD CONSTRAINT "sms_ledgers_sms_account_id_fkey" FOREIGN KEY ("sms_account_id") REFERENCES "public"."sms_accounts"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "public"."sms_send_logs" ADD CONSTRAINT "sms_send_logs_sms_account_id_fkey" FOREIGN KEY ("sms_account_id") REFERENCES "public"."sms_accounts"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "public"."audit_logs" ADD CONSTRAINT "audit_logs_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "public"."tenants"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "public"."audit_logs" ADD CONSTRAINT "audit_logs_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "public"."server_accounts" ADD CONSTRAINT "server_accounts_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "public"."tenants"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "public"."server_charges" ADD CONSTRAINT "server_charges_server_account_id_fkey" FOREIGN KEY ("server_account_id") REFERENCES "public"."server_accounts"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "public"."plugin_subscriptions" ADD CONSTRAINT "plugin_subscriptions_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "public"."tenants"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "public"."plugin_charges" ADD CONSTRAINT "plugin_charges_plugin_id_fkey" FOREIGN KEY ("plugin_id") REFERENCES "public"."plugin_subscriptions"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml index 2a5a444..99e4f20 100644 --- a/prisma/migrations/migration_lock.toml +++ b/prisma/migrations/migration_lock.toml @@ -1,3 +1,3 @@ # Please do not edit this file manually -# It should be added in your version-control system (e.g., Git) -provider = "sqlite" +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" diff --git a/prisma/migrations/20260722125821_init/migration.sql b/prisma/migrations_sqlite_archive/20260722125821_init/migration.sql similarity index 100% rename from prisma/migrations/20260722125821_init/migration.sql rename to prisma/migrations_sqlite_archive/20260722125821_init/migration.sql diff --git a/prisma/migrations/20260722152640_add_app_ui_config/migration.sql b/prisma/migrations_sqlite_archive/20260722152640_add_app_ui_config/migration.sql similarity index 100% rename from prisma/migrations/20260722152640_add_app_ui_config/migration.sql rename to prisma/migrations_sqlite_archive/20260722152640_add_app_ui_config/migration.sql diff --git a/prisma/migrations/20260723073638_sms_settings_cache/migration.sql b/prisma/migrations_sqlite_archive/20260723073638_sms_settings_cache/migration.sql similarity index 100% rename from prisma/migrations/20260723073638_sms_settings_cache/migration.sql rename to prisma/migrations_sqlite_archive/20260723073638_sms_settings_cache/migration.sql diff --git a/prisma/migrations/20260723143623_sms_accounts_owned/migration.sql b/prisma/migrations_sqlite_archive/20260723143623_sms_accounts_owned/migration.sql similarity index 100% rename from prisma/migrations/20260723143623_sms_accounts_owned/migration.sql rename to prisma/migrations_sqlite_archive/20260723143623_sms_accounts_owned/migration.sql diff --git a/prisma/migrations/20260727120000_product_releases/migration.sql b/prisma/migrations_sqlite_archive/20260727120000_product_releases/migration.sql similarity index 100% rename from prisma/migrations/20260727120000_product_releases/migration.sql rename to prisma/migrations_sqlite_archive/20260727120000_product_releases/migration.sql diff --git a/prisma/migrations/20260727140000_tenant_app_version/migration.sql b/prisma/migrations_sqlite_archive/20260727140000_tenant_app_version/migration.sql similarity index 100% rename from prisma/migrations/20260727140000_tenant_app_version/migration.sql rename to prisma/migrations_sqlite_archive/20260727140000_tenant_app_version/migration.sql diff --git a/prisma/migrations/20260727150000_users_mobile_admins/migration.sql b/prisma/migrations_sqlite_archive/20260727150000_users_mobile_admins/migration.sql similarity index 100% rename from prisma/migrations/20260727150000_users_mobile_admins/migration.sql rename to prisma/migrations_sqlite_archive/20260727150000_users_mobile_admins/migration.sql diff --git a/prisma/migrations/20260727160000_server_accounts/migration.sql b/prisma/migrations_sqlite_archive/20260727160000_server_accounts/migration.sql similarity index 100% rename from prisma/migrations/20260727160000_server_accounts/migration.sql rename to prisma/migrations_sqlite_archive/20260727160000_server_accounts/migration.sql diff --git a/prisma/migrations/20260728120000_plugin_subscriptions/migration.sql b/prisma/migrations_sqlite_archive/20260728120000_plugin_subscriptions/migration.sql similarity index 100% rename from prisma/migrations/20260728120000_plugin_subscriptions/migration.sql rename to prisma/migrations_sqlite_archive/20260728120000_plugin_subscriptions/migration.sql diff --git a/prisma/migrations/20260729150000_sms_panel_parity/migration.sql b/prisma/migrations_sqlite_archive/20260729150000_sms_panel_parity/migration.sql similarity index 100% rename from prisma/migrations/20260729150000_sms_panel_parity/migration.sql rename to prisma/migrations_sqlite_archive/20260729150000_sms_panel_parity/migration.sql diff --git a/prisma/migrations_sqlite_archive/README.md b/prisma/migrations_sqlite_archive/README.md new file mode 100644 index 0000000..83d30e9 --- /dev/null +++ b/prisma/migrations_sqlite_archive/README.md @@ -0,0 +1,4 @@ +# Archived SQLite migrations + +These were the local SQLite migration history before switching to PostgreSQL +(`20260730180000_init_postgres`). Do not run them against Postgres. diff --git a/prisma/migrations_sqlite_archive/migration_lock.toml b/prisma/migrations_sqlite_archive/migration_lock.toml new file mode 100644 index 0000000..2a5a444 --- /dev/null +++ b/prisma/migrations_sqlite_archive/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "sqlite" diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 0975216..27e3560 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -3,8 +3,8 @@ generator client { } datasource db { - // Local scaffold default: SQLite. For MySQL set provider = "mysql" and DATABASE_URL accordingly. - provider = "sqlite" + // Production + shared host: PostgreSQL. Local: Docker Postgres or same server DB. + provider = "postgresql" url = env("DATABASE_URL") }