This commit is contained in:
@@ -229,8 +229,8 @@ model User {
|
||||
nickname String @unique
|
||||
passwordHash String?
|
||||
name String
|
||||
birth DateTime
|
||||
phone String @unique
|
||||
birth DateTime?
|
||||
phone String? @unique
|
||||
rank Int @default(0)
|
||||
// 누적 포인트, 레벨, 등급(0~10)
|
||||
points Int @default(0)
|
||||
@@ -259,6 +259,7 @@ model User {
|
||||
blocksInitiated Block[] @relation("Blocker")
|
||||
blocksReceived Block[] @relation("Blocked")
|
||||
pointTxns PointTransaction[]
|
||||
attendances Attendance[]
|
||||
sanctions Sanction[]
|
||||
nicknameChanges NicknameChange[]
|
||||
passwordResetTokens PasswordResetToken[] @relation("PasswordResetUser")
|
||||
@@ -506,6 +507,20 @@ model PointTransaction {
|
||||
@@map("point_transactions")
|
||||
}
|
||||
|
||||
// 출석부 기록 (사용자별 일자 단위 출석)
|
||||
model Attendance {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
date DateTime // 자정 기준 날짜만 사용
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
user User @relation(fields: [userId], references: [userId], onDelete: Cascade)
|
||||
|
||||
@@unique([userId, date])
|
||||
@@index([userId, date])
|
||||
@@map("attendance")
|
||||
}
|
||||
|
||||
// 레벨 임계치(선택)
|
||||
model LevelThreshold {
|
||||
id String @id @default(cuid())
|
||||
|
||||
@@ -270,6 +270,25 @@ async function upsertAdmin() {
|
||||
return admin;
|
||||
}
|
||||
|
||||
async function seedAdminAttendance(admin) {
|
||||
try {
|
||||
const now = new Date();
|
||||
const year = now.getUTCFullYear();
|
||||
const days = [1, 2, 5, 6]; // 11월 1,2,5,6일
|
||||
for (const d of days) {
|
||||
const date = new Date(Date.UTC(year, 10, d, 0, 0, 0, 0)); // 10 = November (0-based)
|
||||
// @@unique([userId, date]) 기준으로 업서트
|
||||
await prisma.attendance.upsert({
|
||||
where: { userId_date: { userId: admin.userId, date } },
|
||||
update: {},
|
||||
create: { userId: admin.userId, date },
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("seedAdminAttendance failed:", e);
|
||||
}
|
||||
}
|
||||
|
||||
async function upsertBoards(admin, categoryMap) {
|
||||
const boards = [
|
||||
// 일반
|
||||
@@ -292,11 +311,12 @@ async function upsertBoards(admin, categoryMap) {
|
||||
];
|
||||
|
||||
const created = [];
|
||||
// 특수 랭킹/텍스트/미리보기 뷰 타입 ID 조회 (사전에 upsertViewTypes로 생성됨)
|
||||
// 텍스트/특수랭킹/특수출석 뷰 타입 ID 조회 (사전에 upsertViewTypes로 생성됨)
|
||||
const mainText = await prisma.boardViewType.findUnique({ where: { key: "main_text" } });
|
||||
const listText = await prisma.boardViewType.findUnique({ where: { key: "list_text" } });
|
||||
const mainSpecial = await prisma.boardViewType.findUnique({ where: { key: "main_special_rank" } });
|
||||
const listSpecial = await prisma.boardViewType.findUnique({ where: { key: "list_special_rank" } });
|
||||
const mainText = await prisma.boardViewType.findUnique({ where: { key: "main_text" } });
|
||||
const mainPreview = await prisma.boardViewType.findUnique({ where: { key: "main_preview" } });
|
||||
const listSpecialAttendance = await prisma.boardViewType.findUnique({ where: { key: "list_special_attendance" } });
|
||||
|
||||
for (const b of boards) {
|
||||
// 카테고리 매핑 규칙 (트리 기준 상위 카테고리)
|
||||
@@ -330,13 +350,12 @@ async function upsertBoards(admin, categoryMap) {
|
||||
allowAnonymousPost: !!b.allowAnonymousPost,
|
||||
readLevel: b.readLevel || undefined,
|
||||
categoryId: category ? category.id : undefined,
|
||||
// 메인뷰: 기본이 아니라 텍스트(main_text)로 설정, 랭킹 보드는 특수 랭킹 유지, TEST는 미리보기
|
||||
...(b.slug === "ranking"
|
||||
? (mainSpecial ? { mainPageViewTypeId: mainSpecial.id } : {})
|
||||
: b.slug === "test"
|
||||
? (mainPreview ? { mainPageViewTypeId: mainPreview.id } : {})
|
||||
: (mainText ? { mainPageViewTypeId: mainText.id } : {})),
|
||||
// 기본은 텍스트, 'ranking'은 특수랭킹, 'attendance'는 특수출석으로 오버라이드
|
||||
...(mainText ? { mainPageViewTypeId: mainText.id } : {}),
|
||||
...(listText ? { listViewTypeId: listText.id } : {}),
|
||||
...(b.slug === "ranking" && mainSpecial ? { mainPageViewTypeId: mainSpecial.id } : {}),
|
||||
...(b.slug === "ranking" && listSpecial ? { listViewTypeId: listSpecial.id } : {}),
|
||||
...(b.slug === "attendance" && listSpecialAttendance ? { listViewTypeId: listSpecialAttendance.id } : {}),
|
||||
},
|
||||
create: {
|
||||
name: b.name,
|
||||
@@ -346,13 +365,12 @@ async function upsertBoards(admin, categoryMap) {
|
||||
allowAnonymousPost: !!b.allowAnonymousPost,
|
||||
readLevel: b.readLevel || undefined,
|
||||
categoryId: category ? category.id : undefined,
|
||||
// 메인뷰: 기본이 아니라 텍스트(main_text)로 설정, 랭킹 보드는 특수 랭킹 유지, TEST는 미리보기
|
||||
...(b.slug === "ranking"
|
||||
? (mainSpecial ? { mainPageViewTypeId: mainSpecial.id } : {})
|
||||
: b.slug === "test"
|
||||
? (mainPreview ? { mainPageViewTypeId: mainPreview.id } : {})
|
||||
: (mainText ? { mainPageViewTypeId: mainText.id } : {})),
|
||||
// 기본은 텍스트, 'ranking'은 특수랭킹, 'attendance'는 특수출석으로 오버라이드
|
||||
...(mainText ? { mainPageViewTypeId: mainText.id } : {}),
|
||||
...(listText ? { listViewTypeId: listText.id } : {}),
|
||||
...(b.slug === "ranking" && mainSpecial ? { mainPageViewTypeId: mainSpecial.id } : {}),
|
||||
...(b.slug === "ranking" && listSpecial ? { listViewTypeId: listSpecial.id } : {}),
|
||||
...(b.slug === "attendance" && listSpecialAttendance ? { listViewTypeId: listSpecialAttendance.id } : {}),
|
||||
},
|
||||
});
|
||||
created.push(board);
|
||||
@@ -373,16 +391,15 @@ async function upsertBoards(admin, categoryMap) {
|
||||
|
||||
async function upsertViewTypes() {
|
||||
const viewTypes = [
|
||||
// main scope
|
||||
{ key: "main_default", name: "기본", scope: "main" },
|
||||
// main scope (기본/없음 제거, 텍스트 중심)
|
||||
{ key: "main_text", name: "텍스트", scope: "main" },
|
||||
{ key: "main_preview", name: "미리보기", scope: "main" },
|
||||
{ key: "main_special_rank", name: "특수랭킹", scope: "main" },
|
||||
// list scope
|
||||
{ key: "list_default", name: "기본", scope: "list" },
|
||||
// list scope (기본/없음 제거, 텍스트 중심)
|
||||
{ key: "list_text", name: "텍스트", scope: "list" },
|
||||
{ key: "list_preview", name: "미리보기", scope: "list" },
|
||||
{ key: "list_special_rank", name: "특수랭킹", scope: "list" },
|
||||
{ key: "list_special_attendance", name: "특수출석", scope: "list" },
|
||||
];
|
||||
for (const vt of viewTypes) {
|
||||
await prisma.boardViewType.upsert({
|
||||
@@ -404,6 +421,8 @@ async function createPostsForAllBoards(boards, countPerBoard = 100, admin) {
|
||||
const users = await prisma.user.findMany({ select: { userId: true } });
|
||||
const userIds = users.map((u) => u.userId);
|
||||
for (const board of boards) {
|
||||
// 회원랭킹 보드는 특수랭킹용이라 게시글을 시드하지 않습니다.
|
||||
if (board.slug === "ranking") continue;
|
||||
const data = [];
|
||||
for (let i = 0; i < countPerBoard; i++) {
|
||||
const authorId = ["notice", "bug-report"].includes(board.slug)
|
||||
@@ -530,6 +549,21 @@ async function main() {
|
||||
);
|
||||
await prisma.$executeRawUnsafe("CREATE INDEX IF NOT EXISTS idx_partner_categories_sortOrder ON partner_categories(sortOrder)");
|
||||
}
|
||||
// Attendance 테이블 보장 (마이그레이션 미실행 환경 대응)
|
||||
const att = await prisma.$queryRaw`SELECT name FROM sqlite_master WHERE type='table' AND name='attendance'`;
|
||||
if (!Array.isArray(att) || att.length === 0) {
|
||||
console.log("Creating missing table: attendance");
|
||||
await prisma.$executeRawUnsafe(
|
||||
"CREATE TABLE IF NOT EXISTS attendance (\n" +
|
||||
"id TEXT PRIMARY KEY,\n" +
|
||||
"userId TEXT NOT NULL,\n" +
|
||||
"date DATETIME NOT NULL,\n" +
|
||||
"createdAt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP\n" +
|
||||
")"
|
||||
);
|
||||
await prisma.$executeRawUnsafe("CREATE UNIQUE INDEX IF NOT EXISTS idx_attendance_user_date ON attendance(userId, date)");
|
||||
await prisma.$executeRawUnsafe("CREATE INDEX IF NOT EXISTS idx_attendance_date ON attendance(date)");
|
||||
}
|
||||
const cols = await prisma.$queryRaw`PRAGMA table_info('partners')`;
|
||||
const hasCategoryId = Array.isArray(cols) && cols.some((c) => (c.name || c.COLUMN_NAME) === 'categoryId');
|
||||
if (!hasCategoryId) {
|
||||
@@ -566,6 +600,7 @@ async function main() {
|
||||
await createRandomUsers(100);
|
||||
await removeNonPrimaryBoards();
|
||||
const boards = await upsertBoards(admin, categoryMap);
|
||||
await seedAdminAttendance(admin);
|
||||
await seedMainpageVisibleBoards(boards);
|
||||
await createPostsForAllBoards(boards, 100, admin);
|
||||
await seedPartnerShops();
|
||||
|
||||
Reference in New Issue
Block a user