feat(seed): örnek aile kullanıcıları, haneler ve roller (idempotent)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,8 @@ BETTER_AUTH_URL="http://localhost:3000"
|
|||||||
# Faz 4 seed'i için ilk admin hesabı
|
# Faz 4 seed'i için ilk admin hesabı
|
||||||
SEED_ADMIN_EMAIL="admin@butcem.local"
|
SEED_ADMIN_EMAIL="admin@butcem.local"
|
||||||
SEED_ADMIN_PASSWORD=""
|
SEED_ADMIN_PASSWORD=""
|
||||||
|
# Örnek aile kullanıcılarının ortak şifresi (yalnızca geliştirme; production'da örnek kullanıcı oluşturulmaz)
|
||||||
|
SEED_USER_PASSWORD=""
|
||||||
|
|
||||||
# Ek dosyalar (canlıda Docker volume: /app/uploads)
|
# Ek dosyalar (canlıda Docker volume: /app/uploads)
|
||||||
UPLOAD_DIR="./uploads"
|
UPLOAD_DIR="./uploads"
|
||||||
|
|||||||
+18
-52
@@ -1,62 +1,25 @@
|
|||||||
/**
|
/**
|
||||||
* Seed betiği: npm run seed | Sıfırlayıp doldurma: npm run seed:reset
|
* Seed: npm run seed | Sıfırlayıp doldurma: npm run seed:reset (production'da reddedilir)
|
||||||
*
|
*
|
||||||
* Faz 3: yalnızca ilk admin (SEED_ADMIN_EMAIL / SEED_ADMIN_PASSWORD, kullanıcı adı "admin").
|
* Oluşturulanlar: admin (SEED_ADMIN_*), 7 örnek kullanıcı (biri askıda, biri ikinci admin),
|
||||||
* Faz 4: örnek kullanıcılar, hane ve aile üyeleri eklenecek.
|
* 2 hane ve hane içi rolleri. Ayrıntı: scripts/seed/fixtures.ts
|
||||||
*
|
|
||||||
* İdempotent: kullanıcı e-postasına göre kontrol eder, tekrar çalıştırınca kopya oluşturmaz.
|
|
||||||
* Kullanıcılar Better Auth API'siyle oluşturulur (şifre doğru hashlenir; koleksiyona elle yazılmaz).
|
|
||||||
* `server-only` modülleri Next dışında yükleyebilmek için --conditions=react-server ile çalışır.
|
* `server-only` modülleri Next dışında yükleyebilmek için --conditions=react-server ile çalışır.
|
||||||
*/
|
*/
|
||||||
import { getAuth } from "@/lib/auth";
|
import { getMongoClient } from "@/lib/db/mongo";
|
||||||
import { getDb, getMongoClient } from "@/lib/db/mongo";
|
|
||||||
import { env } from "@/lib/env";
|
import { env } from "@/lib/env";
|
||||||
|
import { closeSeedConnections, runSeed } from "./seed/run";
|
||||||
const reset = process.argv.includes("--reset");
|
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const e = env();
|
const summary = await runSeed({ reset: process.argv.includes("--reset") });
|
||||||
if (reset && e.NODE_ENV === "production") {
|
const { users, households, memberships } = summary.created;
|
||||||
throw new Error("seed:reset production ortamında çalıştırılamaz.");
|
console.log(`\nOluşturulan: ${users} kullanıcı, ${households} hane, ${memberships} üyelik.`);
|
||||||
}
|
|
||||||
if (!e.SEED_ADMIN_EMAIL || !e.SEED_ADMIN_PASSWORD) {
|
|
||||||
throw new Error("SEED_ADMIN_EMAIL ve SEED_ADMIN_PASSWORD tanımlı olmalı (.env.local).");
|
|
||||||
}
|
|
||||||
|
|
||||||
const db = getDb();
|
// Hesap tablosu yalnızca geliştirme ortamında gösterilir.
|
||||||
if (reset) {
|
if (env().NODE_ENV !== "production" && summary.accounts.length) {
|
||||||
for (const name of ["user", "account", "session", "verification", "rateLimit"]) {
|
console.log(
|
||||||
await db.collection(name).deleteMany({});
|
"\nGeliştirme hesapları (şifreler: admin → SEED_ADMIN_PASSWORD, diğerleri → SEED_USER_PASSWORD):",
|
||||||
}
|
);
|
||||||
console.log("• Kimlik doğrulama koleksiyonları temizlendi.");
|
console.table(summary.accounts);
|
||||||
}
|
|
||||||
|
|
||||||
const email = e.SEED_ADMIN_EMAIL.toLowerCase();
|
|
||||||
const existing = await db.collection("user").findOne({ email });
|
|
||||||
if (existing) {
|
|
||||||
console.log(`• Admin zaten var: ${email}`);
|
|
||||||
} else {
|
|
||||||
await getAuth().api.createUser({
|
|
||||||
body: {
|
|
||||||
email,
|
|
||||||
password: e.SEED_ADMIN_PASSWORD,
|
|
||||||
name: "Yönetici",
|
|
||||||
role: "admin",
|
|
||||||
data: { username: "admin" },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
console.log(`✓ Admin oluşturuldu: ${email} (kullanıcı adı: admin)`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.NODE_ENV !== "production") {
|
|
||||||
console.table([
|
|
||||||
{
|
|
||||||
rol: "admin",
|
|
||||||
kullaniciAdi: "admin",
|
|
||||||
eposta: email,
|
|
||||||
sifre: "SEED_ADMIN_PASSWORD (.env.local)",
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,4 +28,7 @@ main()
|
|||||||
console.error(err instanceof Error ? err.message : err);
|
console.error(err instanceof Error ? err.message : err);
|
||||||
process.exitCode = 1;
|
process.exitCode = 1;
|
||||||
})
|
})
|
||||||
.finally(() => getMongoClient().close());
|
.finally(async () => {
|
||||||
|
await closeSeedConnections();
|
||||||
|
await getMongoClient().close();
|
||||||
|
});
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import type { HouseholdRole } from "@/features/households/types";
|
||||||
|
import type { SystemRole } from "@/shared/schemas/common";
|
||||||
|
|
||||||
|
/** Örnek (geliştirme) kullanıcıları. E-postası olmayanlar kullanıcı adıyla giriş yapar. */
|
||||||
|
export type SeedUser = {
|
||||||
|
username: string;
|
||||||
|
name: string;
|
||||||
|
email?: string;
|
||||||
|
role: SystemRole;
|
||||||
|
banReason?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SEED_USERS: SeedUser[] = [
|
||||||
|
{ username: "ayse", name: "Ayşe Çakır", email: "ayse@butcem.local", role: "user" },
|
||||||
|
{ username: "emre", name: "Emre Çakır", role: "user" },
|
||||||
|
{ username: "zeynep", name: "Zeynep Çakır", role: "user" },
|
||||||
|
{ username: "fatma", name: "Fatma Demir", email: "fatma@butcem.local", role: "user" },
|
||||||
|
{ username: "mehmet", name: "Mehmet Demir", email: "mehmet@butcem.local", role: "user" },
|
||||||
|
// İkinci admin: hiçbir haneye üye değil → hane verisini göremez (Faz 0 kararı).
|
||||||
|
{ username: "burak", name: "Burak Öztürk", email: "burak@butcem.local", role: "admin" },
|
||||||
|
{
|
||||||
|
username: "ozan",
|
||||||
|
name: "Ozan Kaya",
|
||||||
|
email: "ozan@butcem.local",
|
||||||
|
role: "user",
|
||||||
|
banReason: "Ev arkadaşlığı sona erdi; erişim kaldırıldı.",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/** Seed admin'in kullanıcı adı (SEED_ADMIN_EMAIL ile oluşturulur). */
|
||||||
|
export const ADMIN_USERNAME = "admin";
|
||||||
|
|
||||||
|
export type SeedHousehold = {
|
||||||
|
name: string;
|
||||||
|
members: { username: string; role: HouseholdRole }[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SEED_HOUSEHOLDS: SeedHousehold[] = [
|
||||||
|
{
|
||||||
|
name: "Çakır Ailesi",
|
||||||
|
members: [
|
||||||
|
{ username: ADMIN_USERNAME, role: "owner" },
|
||||||
|
{ username: "ayse", role: "owner" },
|
||||||
|
{ username: "emre", role: "member" },
|
||||||
|
{ username: "zeynep", role: "viewer" },
|
||||||
|
{ username: "ozan", role: "member" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Annemler",
|
||||||
|
members: [
|
||||||
|
{ username: "fatma", role: "owner" },
|
||||||
|
{ username: "mehmet", role: "member" },
|
||||||
|
// Bir kullanıcı birden çok haneye üye olabilir.
|
||||||
|
{ username: "ayse", role: "viewer" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||||
|
import { getAuth } from "@/lib/auth";
|
||||||
|
import { authErrorCode } from "@/lib/auth-errors";
|
||||||
|
import { getDb, getMongoClient } from "@/lib/db/mongo";
|
||||||
|
import { HouseholdModel } from "@/models/household";
|
||||||
|
import { HouseholdMemberModel } from "@/models/household-member";
|
||||||
|
import { closeSeedConnections, runSeed } from "./run";
|
||||||
|
import { SEED_HOUSEHOLDS, SEED_USERS } from "./fixtures";
|
||||||
|
|
||||||
|
const silent = () => {};
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
expect(getDb().databaseName).toBe("butcem_test");
|
||||||
|
await getDb().dropDatabase();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await getDb().dropDatabase();
|
||||||
|
await closeSeedConnections();
|
||||||
|
await getMongoClient().close();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("seed", () => {
|
||||||
|
it("ilk çalıştırmada admin, örnek kullanıcılar, haneler ve üyelikleri oluşturur", async () => {
|
||||||
|
const summary = await runSeed({ log: silent });
|
||||||
|
const memberCount = SEED_HOUSEHOLDS.reduce((n, h) => n + h.members.length, 0);
|
||||||
|
expect(summary.created).toEqual({
|
||||||
|
users: SEED_USERS.length + 1,
|
||||||
|
households: SEED_HOUSEHOLDS.length,
|
||||||
|
memberships: memberCount,
|
||||||
|
});
|
||||||
|
expect(await getDb().collection("user").countDocuments()).toBe(SEED_USERS.length + 1);
|
||||||
|
expect(await HouseholdMemberModel.countDocuments()).toBe(memberCount);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("idempotenttir: ikinci çalıştırma kopya oluşturmaz", async () => {
|
||||||
|
const summary = await runSeed({ log: silent });
|
||||||
|
expect(summary.created).toEqual({ users: 0, households: 0, memberships: 0 });
|
||||||
|
expect(await getDb().collection("user").countDocuments()).toBe(SEED_USERS.length + 1);
|
||||||
|
expect(await HouseholdModel.countDocuments()).toBe(SEED_HOUSEHOLDS.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("roller ve askı durumu doğru", async () => {
|
||||||
|
const users = await getDb()
|
||||||
|
.collection("user")
|
||||||
|
.find({}, { projection: { username: 1, role: 1, banned: 1 } })
|
||||||
|
.toArray();
|
||||||
|
const by = new Map(users.map((u) => [u.username as string, u]));
|
||||||
|
expect(by.get("admin")?.role).toBe("admin");
|
||||||
|
expect(by.get("burak")?.role).toBe("admin");
|
||||||
|
expect(by.get("ozan")?.banned).toBe(true);
|
||||||
|
expect(users.filter((u) => u.banned)).toHaveLength(1);
|
||||||
|
|
||||||
|
// Bir kullanıcı birden çok hanede, farklı rollerle.
|
||||||
|
const ayse = by.get("ayse")!;
|
||||||
|
const roles = await HouseholdMemberModel.find({ userId: ayse._id }).lean();
|
||||||
|
expect(roles.map((m) => m.role).sort()).toEqual(["owner", "viewer"]);
|
||||||
|
|
||||||
|
// Hanesi olmayan admin.
|
||||||
|
expect(await HouseholdMemberModel.countDocuments({ userId: by.get("burak")!._id })).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("e-postası olmayan kullanıcı kullanıcı adıyla girer; askıdaki kullanıcı giremez", async () => {
|
||||||
|
const api = getAuth().api;
|
||||||
|
const emre = await api.signInUsername({
|
||||||
|
body: { username: "emre", password: "Test-User-12345" },
|
||||||
|
});
|
||||||
|
expect(emre.user.name).toBe("Emre Çakır");
|
||||||
|
|
||||||
|
const error = await api
|
||||||
|
.signInUsername({ body: { username: "ozan", password: "Test-User-12345" } })
|
||||||
|
.then(
|
||||||
|
() => null,
|
||||||
|
(e: unknown) => e,
|
||||||
|
);
|
||||||
|
expect(authErrorCode(error)).toBe("BANNED_USER");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reset koleksiyonları temizleyip yeniden doldurur", async () => {
|
||||||
|
const summary = await runSeed({ reset: true, log: silent });
|
||||||
|
expect(summary.created.users).toBe(SEED_USERS.length + 1);
|
||||||
|
expect(await getDb().collection("user").countDocuments()).toBe(SEED_USERS.length + 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
import mongoose, { Types } from "mongoose";
|
||||||
|
import { getAuth } from "@/lib/auth";
|
||||||
|
import { getDb } from "@/lib/db/mongo";
|
||||||
|
import { connectMongoose } from "@/lib/db/mongoose";
|
||||||
|
import { env } from "@/lib/env";
|
||||||
|
import { HouseholdModel } from "@/models/household";
|
||||||
|
import { HouseholdMemberModel } from "@/models/household-member";
|
||||||
|
import { storedEmail } from "@/shared/account-email";
|
||||||
|
import { ADMIN_USERNAME, SEED_HOUSEHOLDS, SEED_USERS, type SeedUser } from "./fixtures";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* İdempotent seed. Kullanıcılar Better Auth API'siyle oluşturulur (şifreler doğru hashlenir);
|
||||||
|
* tekrar çalıştırınca kopya oluşturmaz (kullanıcı adı / hane adı / üyelik çiftine göre).
|
||||||
|
* Production'da yalnızca admin oluşturulur; örnek kullanıcı ve hane oluşturulmaz.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type SeedOptions = { reset?: boolean; log?: (line: string) => void };
|
||||||
|
|
||||||
|
export type SeedAccountRow = {
|
||||||
|
rol: string;
|
||||||
|
kullaniciAdi: string;
|
||||||
|
eposta: string;
|
||||||
|
durum: string;
|
||||||
|
haneler: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SeedSummary = {
|
||||||
|
created: { users: number; households: number; memberships: number };
|
||||||
|
accounts: SeedAccountRow[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const AUTH_COLLECTIONS = ["user", "account", "session", "verification", "rateLimit"];
|
||||||
|
const DOMAIN_COLLECTIONS = ["households", "householdMembers"];
|
||||||
|
|
||||||
|
type AuthApi = ReturnType<typeof getAuth>["api"];
|
||||||
|
|
||||||
|
async function findUserIdByUsername(username: string): Promise<string | null> {
|
||||||
|
const doc = await getDb()
|
||||||
|
.collection("user")
|
||||||
|
.findOne({ username }, { projection: { _id: 1 } });
|
||||||
|
return doc ? String(doc._id) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Admin olarak giriş yapıp oturum başlıklarını döner (ban gibi oturum isteyen admin API'leri için). */
|
||||||
|
async function adminHeaders(api: AuthApi, password: string): Promise<Headers> {
|
||||||
|
const { headers } = await api.signInUsername({
|
||||||
|
body: { username: ADMIN_USERNAME, password },
|
||||||
|
returnHeaders: true,
|
||||||
|
});
|
||||||
|
const cookie = headers
|
||||||
|
.getSetCookie()
|
||||||
|
.map((c) => c.split(";")[0])
|
||||||
|
.join("; ");
|
||||||
|
return new Headers({ cookie });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function runSeed({
|
||||||
|
reset = false,
|
||||||
|
log = console.log,
|
||||||
|
}: SeedOptions = {}): Promise<SeedSummary> {
|
||||||
|
const e = env();
|
||||||
|
const isProd = e.NODE_ENV === "production";
|
||||||
|
if (reset && isProd) throw new Error("seed:reset production ortamında çalıştırılamaz.");
|
||||||
|
if (!e.SEED_ADMIN_EMAIL || !e.SEED_ADMIN_PASSWORD) {
|
||||||
|
throw new Error("SEED_ADMIN_EMAIL ve SEED_ADMIN_PASSWORD tanımlı olmalı.");
|
||||||
|
}
|
||||||
|
if (!isProd && !e.SEED_USER_PASSWORD) {
|
||||||
|
throw new Error("SEED_USER_PASSWORD tanımlı olmalı (örnek kullanıcıların ortak şifresi).");
|
||||||
|
}
|
||||||
|
|
||||||
|
const api = getAuth().api;
|
||||||
|
const db = getDb();
|
||||||
|
await connectMongoose();
|
||||||
|
const summary: SeedSummary = {
|
||||||
|
created: { users: 0, households: 0, memberships: 0 },
|
||||||
|
accounts: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (reset) {
|
||||||
|
for (const name of [...AUTH_COLLECTIONS, ...DOMAIN_COLLECTIONS]) {
|
||||||
|
await db.collection(name).deleteMany({});
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
|
||||||
|
// 1) Admin
|
||||||
|
const adminEmail = e.SEED_ADMIN_EMAIL.toLowerCase();
|
||||||
|
let adminId = await findUserIdByUsername(ADMIN_USERNAME);
|
||||||
|
if (!adminId) {
|
||||||
|
const res = await api.createUser({
|
||||||
|
body: {
|
||||||
|
email: adminEmail,
|
||||||
|
password: e.SEED_ADMIN_PASSWORD,
|
||||||
|
name: "Yönetici",
|
||||||
|
role: "admin",
|
||||||
|
data: { username: ADMIN_USERNAME },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
adminId = res.user.id;
|
||||||
|
summary.created.users++;
|
||||||
|
log(`✓ Admin oluşturuldu: ${adminEmail} (kullanıcı adı: ${ADMIN_USERNAME})`);
|
||||||
|
} else {
|
||||||
|
log(`• Admin zaten var: ${ADMIN_USERNAME}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isProd) {
|
||||||
|
log("• Production: örnek kullanıcı ve hane oluşturulmadı.");
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Örnek kullanıcılar
|
||||||
|
const ids = new Map<string, string>([[ADMIN_USERNAME, adminId]]);
|
||||||
|
const toBan: SeedUser[] = [];
|
||||||
|
for (const u of SEED_USERS) {
|
||||||
|
let id = await findUserIdByUsername(u.username);
|
||||||
|
if (!id) {
|
||||||
|
const res = await api.createUser({
|
||||||
|
body: {
|
||||||
|
email: storedEmail(u.username, u.email),
|
||||||
|
password: e.SEED_USER_PASSWORD!,
|
||||||
|
name: u.name,
|
||||||
|
role: u.role,
|
||||||
|
data: { username: u.username },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
id = res.user.id;
|
||||||
|
summary.created.users++;
|
||||||
|
log(`✓ Kullanıcı: ${u.name} (@${u.username})`);
|
||||||
|
}
|
||||||
|
ids.set(u.username, id);
|
||||||
|
if (u.banReason) toBan.push(u);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Askıdaki kullanıcı(lar) — admin API'si oturum ister.
|
||||||
|
const pendingBans: { user: SeedUser; id: string }[] = [];
|
||||||
|
for (const u of toBan) {
|
||||||
|
const id = ids.get(u.username)!;
|
||||||
|
const doc = await db
|
||||||
|
.collection("user")
|
||||||
|
.findOne({ _id: new Types.ObjectId(id) }, { projection: { banned: 1 } });
|
||||||
|
if (!doc?.banned) pendingBans.push({ user: u, id });
|
||||||
|
}
|
||||||
|
if (pendingBans.length) {
|
||||||
|
const headers = await adminHeaders(api, e.SEED_ADMIN_PASSWORD);
|
||||||
|
for (const { user, id } of pendingBans) {
|
||||||
|
await api.banUser({ headers, body: { userId: id, banReason: user.banReason } });
|
||||||
|
log(`✓ Askıya alındı: @${user.username}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4) Haneler ve üyelikler
|
||||||
|
const memberships = new Map<string, string[]>();
|
||||||
|
const actor = new Types.ObjectId(adminId);
|
||||||
|
for (const h of SEED_HOUSEHOLDS) {
|
||||||
|
let household = await HouseholdModel.findOne({ name: h.name });
|
||||||
|
if (!household) {
|
||||||
|
household = await HouseholdModel.create({ name: h.name, createdBy: actor, updatedBy: actor });
|
||||||
|
summary.created.households++;
|
||||||
|
log(`✓ Hane: ${h.name}`);
|
||||||
|
}
|
||||||
|
for (const m of h.members) {
|
||||||
|
const userId = new Types.ObjectId(ids.get(m.username)!);
|
||||||
|
const result = await HouseholdMemberModel.updateOne(
|
||||||
|
{ householdId: household._id, userId },
|
||||||
|
{
|
||||||
|
$setOnInsert: { householdId: household._id, userId, createdBy: actor },
|
||||||
|
$set: { role: m.role, updatedBy: actor },
|
||||||
|
},
|
||||||
|
{ upsert: true },
|
||||||
|
);
|
||||||
|
if (result.upsertedCount) summary.created.memberships++;
|
||||||
|
memberships.set(m.username, [
|
||||||
|
...(memberships.get(m.username) ?? []),
|
||||||
|
`${h.name} (${m.role})`,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5) Geliştirme hesapları tablosu
|
||||||
|
const all = [
|
||||||
|
{ username: ADMIN_USERNAME, name: "Yönetici", email: adminEmail, role: "admin" as const },
|
||||||
|
...SEED_USERS,
|
||||||
|
];
|
||||||
|
summary.accounts = all.map((u) => ({
|
||||||
|
rol: u.role,
|
||||||
|
kullaniciAdi: u.username,
|
||||||
|
eposta: u.email ?? "—",
|
||||||
|
durum: "banReason" in u && u.banReason ? "askıda" : "aktif",
|
||||||
|
haneler: (memberships.get(u.username) ?? ["—"]).join(", "),
|
||||||
|
}));
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Seed sonrası bağlantıları kapatır (CLI ve testler). */
|
||||||
|
export async function closeSeedConnections() {
|
||||||
|
await mongoose.disconnect();
|
||||||
|
}
|
||||||
@@ -19,6 +19,8 @@ export const serverEnvSchema = z.object({
|
|||||||
BETTER_AUTH_URL: z.url("BETTER_AUTH_URL geçerli bir URL olmalı"),
|
BETTER_AUTH_URL: z.url("BETTER_AUTH_URL geçerli bir URL olmalı"),
|
||||||
SEED_ADMIN_EMAIL: z.email().optional(),
|
SEED_ADMIN_EMAIL: z.email().optional(),
|
||||||
SEED_ADMIN_PASSWORD: z.string().min(8).optional(),
|
SEED_ADMIN_PASSWORD: z.string().min(8).optional(),
|
||||||
|
/** Örnek (geliştirme) kullanıcılarının ortak şifresi; production'da kullanılmaz. */
|
||||||
|
SEED_USER_PASSWORD: z.string().min(8).optional(),
|
||||||
UPLOAD_DIR: z.string().min(1).default("./uploads"),
|
UPLOAD_DIR: z.string().min(1).default("./uploads"),
|
||||||
MAX_UPLOAD_MB: z.coerce.number().int().positive().max(100).default(10),
|
MAX_UPLOAD_MB: z.coerce.number().int().positive().max(100).default(10),
|
||||||
APP_VERSION: z.string().optional(),
|
APP_VERSION: z.string().optional(),
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
test: {
|
test: {
|
||||||
environment: "node",
|
environment: "node",
|
||||||
include: ["src/**/*.int.test.ts"],
|
include: ["src/**/*.int.test.ts", "scripts/**/*.int.test.ts"],
|
||||||
fileParallelism: false,
|
fileParallelism: false,
|
||||||
testTimeout: 20_000,
|
testTimeout: 20_000,
|
||||||
hookTimeout: 30_000,
|
hookTimeout: 30_000,
|
||||||
@@ -32,6 +32,9 @@ export default defineConfig({
|
|||||||
MONGODB_URI: baseUri,
|
MONGODB_URI: baseUri,
|
||||||
BETTER_AUTH_SECRET: "test-secret-test-secret-test-secret-0123",
|
BETTER_AUTH_SECRET: "test-secret-test-secret-test-secret-0123",
|
||||||
BETTER_AUTH_URL: "http://localhost:3000",
|
BETTER_AUTH_URL: "http://localhost:3000",
|
||||||
|
SEED_ADMIN_EMAIL: "admin@test.local",
|
||||||
|
SEED_ADMIN_PASSWORD: "Test-Admin-12345",
|
||||||
|
SEED_USER_PASSWORD: "Test-User-12345",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user