import { MigrationInterface, QueryRunner } from 'typeorm'; export class InitSchema1700000000000 implements MigrationInterface { name = 'InitSchema1700000000000'; public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(`PRAGMA foreign_keys = ON;`); await queryRunner.query(`PRAGMA journal_mode = WAL;`); await queryRunner.query(` CREATE TABLE IF NOT EXISTS "user" ( "id" TEXT PRIMARY KEY, "username" TEXT NOT NULL UNIQUE, "password_hash" TEXT NOT NULL, "role" TEXT NOT NULL DEFAULT 'player', "status" TEXT NOT NULL DEFAULT 'enabled', "created_at" TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')) ); `); await queryRunner.query(` CREATE TABLE IF NOT EXISTS "setting" ( "key" TEXT PRIMARY KEY, "value" TEXT NOT NULL DEFAULT '' ); `); await queryRunner.query(` CREATE TABLE IF NOT EXISTS "category" ( "id" TEXT PRIMARY KEY, "system_key" TEXT, "name" TEXT NOT NULL, "abbreviation" TEXT NOT NULL, "description" TEXT NOT NULL DEFAULT '', "icon_path" TEXT NOT NULL DEFAULT '' ); `); await queryRunner.query(`CREATE UNIQUE INDEX IF NOT EXISTS "uq_category_system_key" ON "category"("system_key") WHERE "system_key" IS NOT NULL;`); await queryRunner.query(` CREATE TABLE IF NOT EXISTS "challenge" ( "id" TEXT PRIMARY KEY, "name" TEXT NOT NULL, "description_md" TEXT NOT NULL DEFAULT '', "category_id" TEXT NOT NULL, "difficulty" TEXT NOT NULL DEFAULT 'low', "initial_points" INTEGER NOT NULL DEFAULT 0, "minimum_points" INTEGER NOT NULL DEFAULT 0, "decay_solves" INTEGER NOT NULL DEFAULT 0, "flag" TEXT NOT NULL DEFAULT '', "protocol" TEXT NOT NULL DEFAULT 'nc', "port" INTEGER, "ip_address" TEXT NOT NULL DEFAULT '', "created_at" TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')), CONSTRAINT "fk_challenge_category" FOREIGN KEY ("category_id") REFERENCES "category"("id") ON DELETE RESTRICT ); `); await queryRunner.query(`CREATE INDEX IF NOT EXISTS "idx_challenge_category" ON "challenge"("category_id");`); await queryRunner.query(` CREATE TABLE IF NOT EXISTS "challenge_file" ( "id" TEXT PRIMARY KEY, "challenge_id" TEXT NOT NULL, "original_filename" TEXT NOT NULL, "stored_path" TEXT NOT NULL, CONSTRAINT "fk_challenge_file_challenge" FOREIGN KEY ("challenge_id") REFERENCES "challenge"("id") ON DELETE RESTRICT ); `); await queryRunner.query(`CREATE INDEX IF NOT EXISTS "idx_challenge_file_challenge" ON "challenge_file"("challenge_id");`); await queryRunner.query(` CREATE TABLE IF NOT EXISTS "solve" ( "id" TEXT PRIMARY KEY, "challenge_id" TEXT NOT NULL, "user_id" TEXT NOT NULL, "solved_at" TEXT NOT NULL, "points_awarded" INTEGER NOT NULL DEFAULT 0, "base_points" INTEGER NOT NULL DEFAULT 0, "rank_bonus" INTEGER NOT NULL DEFAULT 0, CONSTRAINT "fk_solve_challenge" FOREIGN KEY ("challenge_id") REFERENCES "challenge"("id") ON DELETE RESTRICT, CONSTRAINT "fk_solve_user" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE RESTRICT ); `); await queryRunner.query(`CREATE UNIQUE INDEX IF NOT EXISTS "uq_solve_challenge_user" ON "solve"("challenge_id","user_id");`); await queryRunner.query(`CREATE INDEX IF NOT EXISTS "idx_solve_user" ON "solve"("user_id");`); await queryRunner.query(`CREATE INDEX IF NOT EXISTS "idx_solve_challenge" ON "solve"("challenge_id");`); await queryRunner.query(` CREATE TABLE IF NOT EXISTS "refresh_token" ( "id" TEXT PRIMARY KEY, "user_id" TEXT NOT NULL, "token_hash" TEXT NOT NULL UNIQUE, "issued_at" TEXT NOT NULL, "expires_at" TEXT NOT NULL, "revoked_at" TEXT ); `); await queryRunner.query(`CREATE INDEX IF NOT EXISTS "idx_refresh_token_user" ON "refresh_token"("user_id");`); await queryRunner.query(` CREATE TABLE IF NOT EXISTS "blog_post" ( "id" TEXT PRIMARY KEY, "title" TEXT NOT NULL, "body_md" TEXT NOT NULL DEFAULT '', "status" TEXT NOT NULL DEFAULT 'draft', "created_at" TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')), "updated_at" TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')), "published_at" TEXT ); `); await queryRunner.query(`CREATE INDEX IF NOT EXISTS "idx_blog_status_published" ON "blog_post"("status","published_at");`); } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(`DROP TABLE IF EXISTS "blog_post";`); await queryRunner.query(`DROP TABLE IF EXISTS "refresh_token";`); await queryRunner.query(`DROP TABLE IF EXISTS "solve";`); await queryRunner.query(`DROP TABLE IF EXISTS "challenge_file";`); await queryRunner.query(`DROP TABLE IF EXISTS "challenge";`); await queryRunner.query(`DROP TABLE IF EXISTS "category";`); await queryRunner.query(`DROP TABLE IF EXISTS "setting";`); await queryRunner.query(`DROP TABLE IF EXISTS "user";`); } }