finance-api/src/database/migrations/1734256800000-InitialSchema.ts

306 lines
12 KiB
TypeScript

import { MigrationInterface, QueryRunner } from 'typeorm';
export class InitialSchema1734256800000 implements MigrationInterface {
name = 'InitialSchema1734256800000';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`);
await queryRunner.query(`
CREATE TYPE "public"."transaction_type_enum" AS ENUM('INCOME', 'EXPENSE')
`);
await queryRunner.query(`
CREATE TYPE "public"."budget_group_type_enum" AS ENUM('ESSENTIAL', 'PERSONAL', 'SAVINGS')
`);
await queryRunner.query(`
CREATE TYPE "public"."payment_method_enum" AS ENUM('CASH', 'CARD', 'BANK_TRANSFER')
`);
await queryRunner.query(`
CREATE TYPE "public"."goal_status_enum" AS ENUM('ACTIVE', 'COMPLETED', 'PAUSED', 'CANCELLED')
`);
await queryRunner.query(`
CREATE TYPE "public"."goal_priority_enum" AS ENUM('LOW', 'MEDIUM', 'HIGH')
`);
await queryRunner.query(`
CREATE TYPE "public"."goal_auto_save_frequency_enum" AS ENUM('DAILY', 'WEEKLY', 'MONTHLY')
`);
await queryRunner.query(`
CREATE TYPE "public"."recommendation_type_enum" AS ENUM('SAVING', 'SPENDING', 'INVESTMENT', 'TAX', 'DEBT', 'BUDGET', 'GOAL')
`);
await queryRunner.query(`
CREATE TYPE "public"."recommendation_status_enum" AS ENUM('NEW', 'VIEWED', 'APPLIED', 'DISMISSED')
`);
await queryRunner.query(`
CREATE TABLE "users" (
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
"email" character varying(255) NOT NULL,
"phone" character varying(20),
"password_hash" character varying(255) NOT NULL,
"first_name" character varying(100),
"last_name" character varying(100),
"currency" character varying(3) NOT NULL DEFAULT 'RUB',
"language" character varying(10) NOT NULL DEFAULT 'ru',
"timezone" character varying(50) NOT NULL DEFAULT 'Europe/Moscow',
"is_email_verified" boolean NOT NULL DEFAULT false,
"is_phone_verified" boolean NOT NULL DEFAULT false,
"is_active" boolean NOT NULL DEFAULT true,
"failed_login_attempts" integer NOT NULL DEFAULT 0,
"locked_until" TIMESTAMP,
"last_login_at" TIMESTAMP,
"monthly_income" numeric(15,2) NOT NULL DEFAULT 0,
"financial_goals" jsonb,
"created_at" TIMESTAMP NOT NULL DEFAULT now(),
"updated_at" TIMESTAMP NOT NULL DEFAULT now(),
"deleted_at" TIMESTAMP,
CONSTRAINT "UQ_users_email" UNIQUE ("email"),
CONSTRAINT "UQ_users_phone" UNIQUE ("phone"),
CONSTRAINT "PK_users" PRIMARY KEY ("id")
)
`);
await queryRunner.query(
`CREATE INDEX "IDX_users_email" ON "users" ("email")`,
);
await queryRunner.query(
`CREATE INDEX "IDX_users_phone" ON "users" ("phone")`,
);
await queryRunner.query(`
CREATE TABLE "refresh_tokens" (
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
"user_id" uuid NOT NULL,
"token_hash" character varying(255) NOT NULL,
"user_agent" text,
"ip_address" inet,
"expires_at" TIMESTAMP NOT NULL,
"is_revoked" boolean NOT NULL DEFAULT false,
"replaced_by_token_hash" character varying(255),
"created_at" TIMESTAMP NOT NULL DEFAULT now(),
CONSTRAINT "PK_refresh_tokens" PRIMARY KEY ("id"),
CONSTRAINT "FK_refresh_tokens_user" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE
)
`);
await queryRunner.query(
`CREATE INDEX "IDX_refresh_tokens_user_id" ON "refresh_tokens" ("user_id")`,
);
await queryRunner.query(
`CREATE INDEX "IDX_refresh_tokens_expires_at" ON "refresh_tokens" ("expires_at")`,
);
await queryRunner.query(`
CREATE TABLE "audit_logs" (
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
"user_id" uuid,
"action" character varying(50) NOT NULL,
"entity_type" character varying(50) NOT NULL,
"entity_id" uuid,
"old_values" jsonb,
"new_values" jsonb,
"ip_address" inet,
"user_agent" text,
"created_at" TIMESTAMP NOT NULL DEFAULT now(),
CONSTRAINT "PK_audit_logs" PRIMARY KEY ("id"),
CONSTRAINT "FK_audit_logs_user" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE SET NULL
)
`);
await queryRunner.query(
`CREATE INDEX "IDX_audit_logs_user_id" ON "audit_logs" ("user_id")`,
);
await queryRunner.query(
`CREATE INDEX "IDX_audit_logs_created_at" ON "audit_logs" ("created_at")`,
);
await queryRunner.query(`
CREATE TABLE "categories" (
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
"name_ru" character varying(100) NOT NULL,
"name_en" character varying(100),
"type" "public"."transaction_type_enum" NOT NULL,
"group_type" "public"."budget_group_type_enum",
"icon" character varying(50),
"color" character varying(7),
"is_default" boolean NOT NULL DEFAULT true,
"user_id" uuid,
"parent_id" uuid,
CONSTRAINT "PK_categories" PRIMARY KEY ("id"),
CONSTRAINT "FK_categories_user" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE,
CONSTRAINT "FK_categories_parent" FOREIGN KEY ("parent_id") REFERENCES "categories"("id") ON DELETE CASCADE
)
`);
await queryRunner.query(
`CREATE INDEX "IDX_categories_type" ON "categories" ("type")`,
);
await queryRunner.query(
`CREATE INDEX "IDX_categories_user_id" ON "categories" ("user_id")`,
);
await queryRunner.query(`
CREATE TABLE "transactions" (
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
"user_id" uuid NOT NULL,
"amount" numeric(15,2) NOT NULL,
"currency" character varying(3) NOT NULL DEFAULT 'RUB',
"type" "public"."transaction_type_enum" NOT NULL,
"category_id" uuid,
"description" text,
"transaction_date" date NOT NULL,
"payment_method" "public"."payment_method_enum",
"receipt_url" character varying(500),
"receipt_processed" boolean NOT NULL DEFAULT false,
"created_by" uuid,
"updated_by" uuid,
"is_recurring" boolean NOT NULL DEFAULT false,
"recurring_pattern" jsonb,
"is_planned" boolean NOT NULL DEFAULT false,
"created_at" TIMESTAMP NOT NULL DEFAULT now(),
"updated_at" TIMESTAMP NOT NULL DEFAULT now(),
"deleted_at" TIMESTAMP,
CONSTRAINT "CHK_transactions_amount" CHECK ("amount" > 0),
CONSTRAINT "PK_transactions" PRIMARY KEY ("id"),
CONSTRAINT "FK_transactions_user" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE,
CONSTRAINT "FK_transactions_category" FOREIGN KEY ("category_id") REFERENCES "categories"("id") ON DELETE SET NULL
)
`);
await queryRunner.query(
`CREATE INDEX "IDX_transactions_user_id" ON "transactions" ("user_id")`,
);
await queryRunner.query(
`CREATE INDEX "IDX_transactions_type" ON "transactions" ("type")`,
);
await queryRunner.query(
`CREATE INDEX "IDX_transactions_category_id" ON "transactions" ("category_id")`,
);
await queryRunner.query(
`CREATE INDEX "IDX_transactions_transaction_date" ON "transactions" ("transaction_date")`,
);
await queryRunner.query(`
CREATE TABLE "budgets" (
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
"user_id" uuid NOT NULL,
"month" date NOT NULL,
"total_income" numeric(15,2) NOT NULL DEFAULT 0,
"essentials_limit" numeric(15,2) NOT NULL DEFAULT 0,
"essentials_spent" numeric(15,2) NOT NULL DEFAULT 0,
"personal_limit" numeric(15,2) NOT NULL DEFAULT 0,
"personal_spent" numeric(15,2) NOT NULL DEFAULT 0,
"savings_limit" numeric(15,2) NOT NULL DEFAULT 0,
"savings_spent" numeric(15,2) NOT NULL DEFAULT 0,
"custom_allocations" jsonb,
"is_active" boolean NOT NULL DEFAULT true,
CONSTRAINT "UQ_budgets_user_month" UNIQUE ("user_id", "month"),
CONSTRAINT "PK_budgets" PRIMARY KEY ("id"),
CONSTRAINT "FK_budgets_user" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE
)
`);
await queryRunner.query(
`CREATE INDEX "IDX_budgets_user_id" ON "budgets" ("user_id")`,
);
await queryRunner.query(
`CREATE INDEX "IDX_budgets_month" ON "budgets" ("month")`,
);
await queryRunner.query(`
CREATE TABLE "goals" (
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
"user_id" uuid NOT NULL,
"title_ru" character varying(200) NOT NULL,
"description_ru" text,
"target_amount" numeric(15,2) NOT NULL,
"current_amount" numeric(15,2) NOT NULL DEFAULT 0,
"currency" character varying(3) NOT NULL DEFAULT 'RUB',
"target_date" date,
"status" "public"."goal_status_enum" NOT NULL DEFAULT 'ACTIVE',
"priority" "public"."goal_priority_enum" NOT NULL DEFAULT 'MEDIUM',
"icon" character varying(50),
"color" character varying(7),
"auto_save_enabled" boolean NOT NULL DEFAULT false,
"auto_save_amount" numeric(15,2),
"auto_save_frequency" "public"."goal_auto_save_frequency_enum",
"created_at" TIMESTAMP NOT NULL DEFAULT now(),
"updated_at" TIMESTAMP NOT NULL DEFAULT now(),
"completed_at" TIMESTAMP,
CONSTRAINT "PK_goals" PRIMARY KEY ("id"),
CONSTRAINT "FK_goals_user" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE
)
`);
await queryRunner.query(
`CREATE INDEX "IDX_goals_user_id" ON "goals" ("user_id")`,
);
await queryRunner.query(`
CREATE TABLE "recommendations" (
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
"user_id" uuid NOT NULL,
"type" "public"."recommendation_type_enum" NOT NULL,
"title_ru" character varying(200) NOT NULL,
"description_ru" text NOT NULL,
"action_text_ru" character varying(200),
"priority_score" numeric(3,2) NOT NULL DEFAULT 0.5,
"confidence_score" numeric(3,2) NOT NULL DEFAULT 0.5,
"potential_savings" numeric(15,2),
"status" "public"."recommendation_status_enum" NOT NULL DEFAULT 'NEW',
"action_data" jsonb,
"expires_at" TIMESTAMP,
"created_at" TIMESTAMP NOT NULL DEFAULT now(),
"viewed_at" TIMESTAMP,
"applied_at" TIMESTAMP,
CONSTRAINT "PK_recommendations" PRIMARY KEY ("id"),
CONSTRAINT "FK_recommendations_user" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE
)
`);
await queryRunner.query(
`CREATE INDEX "IDX_recommendations_user_id" ON "recommendations" ("user_id")`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE IF EXISTS "recommendations"`);
await queryRunner.query(`DROP TABLE IF EXISTS "goals"`);
await queryRunner.query(`DROP TABLE IF EXISTS "budgets"`);
await queryRunner.query(`DROP TABLE IF EXISTS "transactions"`);
await queryRunner.query(`DROP TABLE IF EXISTS "categories"`);
await queryRunner.query(`DROP TABLE IF EXISTS "audit_logs"`);
await queryRunner.query(`DROP TABLE IF EXISTS "refresh_tokens"`);
await queryRunner.query(`DROP TABLE IF EXISTS "users"`);
await queryRunner.query(
`DROP TYPE IF EXISTS "public"."recommendation_status_enum"`,
);
await queryRunner.query(
`DROP TYPE IF EXISTS "public"."recommendation_type_enum"`,
);
await queryRunner.query(
`DROP TYPE IF EXISTS "public"."goal_auto_save_frequency_enum"`,
);
await queryRunner.query(
`DROP TYPE IF EXISTS "public"."goal_priority_enum"`,
);
await queryRunner.query(`DROP TYPE IF EXISTS "public"."goal_status_enum"`);
await queryRunner.query(
`DROP TYPE IF EXISTS "public"."payment_method_enum"`,
);
await queryRunner.query(
`DROP TYPE IF EXISTS "public"."budget_group_type_enum"`,
);
await queryRunner.query(
`DROP TYPE IF EXISTS "public"."transaction_type_enum"`,
);
}
}