From b79951601cf96df84356d2f1e480fd64d894a0b5 Mon Sep 17 00:00:00 2001 From: suphicakir Date: Thu, 5 Nov 2026 02:35:01 +0300 Subject: [PATCH] =?UTF-8?q?feat(db):=20migration=20mekanizmas=C4=B1=20ve?= =?UTF-8?q?=20ilk=20index=20migration'=C4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- package.json | 5 ++-- scripts/migrate.ts | 17 +++++++++++ scripts/migrations/.gitkeep | 0 scripts/migrations/001-initial-indexes.ts | 15 ++++++++++ scripts/migrations/index.ts | 35 +++++++++++++++++++++++ scripts/migrations/types.ts | 8 ++++++ scripts/seed/run.ts | 8 +++--- 7 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 scripts/migrate.ts delete mode 100644 scripts/migrations/.gitkeep create mode 100644 scripts/migrations/001-initial-indexes.ts create mode 100644 scripts/migrations/index.ts create mode 100644 scripts/migrations/types.ts diff --git a/package.json b/package.json index e229238..11ad446 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "butcem", - "version": "0.4.0", + "version": "0.5.0", "private": true, "description": "BÜTÇEM — aile bütçe, cari, birikim, ev stoğu ve akıllı alışveriş uygulaması", "engines": { @@ -21,7 +21,8 @@ "seed": "tsx --conditions=react-server --env-file=.env.local scripts/seed.ts", "seed:reset": "tsx --conditions=react-server --env-file=.env.local scripts/seed.ts --reset", "icons": "tsx scripts/generate-icons.ts", - "test:integration": "vitest run --config vitest.integration.config.mts" + "test:integration": "vitest run --config vitest.integration.config.mts", + "migrate": "tsx --conditions=react-server --env-file=.env.local scripts/migrate.ts" }, "dependencies": { "@hookform/resolvers": "^7.0.2", diff --git a/scripts/migrate.ts b/scripts/migrate.ts new file mode 100644 index 0000000..0edf58c --- /dev/null +++ b/scripts/migrate.ts @@ -0,0 +1,17 @@ +/** + * Migration'ları uygular: npm run migrate (canlıda deploy sonrası; bkz. docs/DEPLOY.md, Faz 15). + * `server-only` modülleri Next dışında yükleyebilmek için --conditions=react-server ile çalışır. + */ +import mongoose from "mongoose"; +import { getMongoClient } from "@/lib/db/mongo"; +import { runMigrations } from "./migrations"; + +runMigrations() + .catch((err: unknown) => { + console.error(err instanceof Error ? err.message : err); + process.exitCode = 1; + }) + .finally(async () => { + await mongoose.disconnect(); + await getMongoClient().close(); + }); diff --git a/scripts/migrations/.gitkeep b/scripts/migrations/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/scripts/migrations/001-initial-indexes.ts b/scripts/migrations/001-initial-indexes.ts new file mode 100644 index 0000000..60c6dec --- /dev/null +++ b/scripts/migrations/001-initial-indexes.ts @@ -0,0 +1,15 @@ +import { AuditLogModel } from "@/models/audit-log"; +import { HouseholdModel } from "@/models/household"; +import { HouseholdMemberModel } from "@/models/household-member"; +import type { Migration } from "./types"; + +/** Hane, üyelik ve denetim kaydı index'leri (Mongoose şemalarındaki tanımlarla). */ +export const migration: Migration = { + name: "001-initial-indexes", + description: "households, householdMembers ve auditLogs index'leri", + async up() { + await HouseholdModel.createIndexes(); + await HouseholdMemberModel.createIndexes(); + await AuditLogModel.createIndexes(); + }, +}; diff --git a/scripts/migrations/index.ts b/scripts/migrations/index.ts new file mode 100644 index 0000000..9c76e86 --- /dev/null +++ b/scripts/migrations/index.ts @@ -0,0 +1,35 @@ +import { getDb } from "@/lib/db/mongo"; +import { connectMongoose } from "@/lib/db/mongoose"; +import { migration as m001 } from "./001-initial-indexes"; +import type { Migration } from "./types"; + +/** Sıralı migration listesi. Yeni migration: dosyayı ekle ve buraya sırayla ekle. */ +export const MIGRATIONS: Migration[] = [m001]; + +const COLLECTION = "migrations"; + +/** + * Uygulanmamış migration'ları sırayla çalıştırır; her biri bir kez (`migrations` koleksiyonu). + * Aynı anda iki çalıştırma olursa benzersiz `name` index'i ikinci kaydı reddeder. + */ +export async function runMigrations(log: (line: string) => void = console.log): Promise { + const db = getDb(); + await connectMongoose(); + const col = db.collection<{ name: string; appliedAt: Date; durationMs: number }>(COLLECTION); + await col.createIndex({ name: 1 }, { unique: true }); + const applied = new Set( + (await col.find({}, { projection: { name: 1 } }).toArray()).map((d) => d.name), + ); + + const ran: string[] = []; + for (const m of MIGRATIONS) { + if (applied.has(m.name)) continue; + const started = Date.now(); + await m.up(db); + await col.insertOne({ name: m.name, appliedAt: new Date(), durationMs: Date.now() - started }); + ran.push(m.name); + log(`✓ Migration: ${m.name} — ${m.description}`); + } + if (!ran.length) log("• Bekleyen migration yok."); + return ran; +} diff --git a/scripts/migrations/types.ts b/scripts/migrations/types.ts new file mode 100644 index 0000000..595e5e8 --- /dev/null +++ b/scripts/migrations/types.ts @@ -0,0 +1,8 @@ +import type { Db } from "mongodb"; + +/** Bir migration: adı dosya adıyla aynı olmalı (ör. "001-initial-indexes"). Geri alma yok; ileri yönlü. */ +export type Migration = { + name: string; + description: string; + up: (db: Db) => Promise; +}; diff --git a/scripts/seed/run.ts b/scripts/seed/run.ts index 815964c..da38c25 100644 --- a/scripts/seed/run.ts +++ b/scripts/seed/run.ts @@ -6,6 +6,7 @@ import { env } from "@/lib/env"; import { HouseholdModel } from "@/models/household"; import { HouseholdMemberModel } from "@/models/household-member"; import { storedEmail } from "@/shared/account-email"; +import { runMigrations } from "../migrations"; import { ADMIN_USERNAME, SEED_HOUSEHOLDS, SEED_USERS, type SeedUser } from "./fixtures"; /** @@ -30,7 +31,7 @@ export type SeedSummary = { }; const AUTH_COLLECTIONS = ["user", "account", "session", "verification", "rateLimit"]; -const DOMAIN_COLLECTIONS = ["households", "householdMembers"]; +const DOMAIN_COLLECTIONS = ["households", "householdMembers", "auditLogs"]; type AuthApi = ReturnType["api"]; @@ -83,9 +84,8 @@ export async function runSeed({ log("• Kimlik doğrulama ve hane koleksiyonları temizlendi."); } - // Index'ler (production'da Mongoose autoIndex kapalı olduğu için açıkça oluşturulur). - await HouseholdModel.createIndexes(); - await HouseholdMemberModel.createIndexes(); + // Index'ler ve şema değişiklikleri (production'da Mongoose autoIndex kapalı). + await runMigrations(log); // 1) Admin const adminEmail = e.SEED_ADMIN_EMAIL.toLowerCase();