306 lines
12 KiB
TypeScript
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"`,
|
|
);
|
|
}
|
|
}
|