This commit is contained in:
2025-09-08 18:30:34 +00:00
parent c116a85ead
commit a93493fb62
15 changed files with 134 additions and 182 deletions

View File

@@ -1,115 +0,0 @@
import { NextResponse } from 'next/server';
import { auth } from '@/auth';
import { PrismaClient } from '@/app/generated/prisma';
const prisma = new PrismaClient();
function parseDateOr(value: string | null, fallback: Date): Date {
if (!value) return fallback;
const d = new Date(value);
return isNaN(d.getTime()) ? fallback : d;
}
export async function GET(request: Request) {
try {
const session = await auth();
if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const email = session.user?.email as string | undefined;
if (!email) return NextResponse.json({ error: '세션 이메일을 찾을 수 없습니다' }, { status: 400 });
const { searchParams } = new URL(request.url);
const endDefault = new Date();
const startDefault = new Date(endDefault.getTime() - 30 * 24 * 60 * 60 * 1000);
const startParam = searchParams.get('start');
const endParam = searchParams.get('end');
const startDate = startParam ? new Date(startParam) : startDefault;
const endDate = endParam ? new Date(endParam) : endDefault;
// 날짜 경계 보정: 하루 전체 포함 (UTC 기준)
const startInclusive = new Date(startDate);
startInclusive.setUTCHours(0, 0, 0, 0);
const endInclusive = new Date(endDate);
endInclusive.setUTCHours(23, 59, 59, 999);
// 안전가드
if (endInclusive < startInclusive) endInclusive.setTime(startInclusive.getTime());
console.log('startDate', startDate.toISOString());
console.log('endDate', endDate.toISOString());
// 1) 내 핸들
const handles = await prisma.userHandle.findMany({
where: { email },
select: { id: true, handle: true, icon: true }
});
if (handles.length === 0) return NextResponse.json({ items: [] });
const handleStrs = handles.map(h => h.handle);
const handleByStr = new Map(handles.map(h => [h.handle, h] as const));
// 2) 매핑된 콘텐츠
const links = await prisma.contentHandle.findMany({
where: { handle: { in: handleStrs } },
select: { contentId: true, handle: true }
});
if (links.length === 0) return NextResponse.json({ items: [] });
const contentIds = links.map(l => l.contentId);
const normalizeId = (s: string) => (s ?? '').trim();
const contentIdsNormalized = Array.from(new Set(contentIds.map(normalizeId)));
const idsForQuery = Array.from(new Set([...contentIds, ...contentIdsNormalized]));
const handleByContentId = new Map(links.map(l => [l.contentId, l.handle] as const));
// 3) 콘텐츠 본문
const contents = await prisma.content.findMany({
where: { id: { in: contentIds } },
select: {
id: true, subject: true, pubDate: true,
views: true, premiumViews: true, watchTime: true
}
});
// 4) 기간 합계 유효조회수 (findMany로 가져와 JS에서 합산 - groupBy 일부 환경 이슈 회피)
const viewRows = await prisma.viewPerDay.findMany({
where: {
contented: { in: idsForQuery },
date: { gte: startInclusive, lte: endInclusive }
},
select: { contented: true, validViewDay: true }
});
const validSumById = new Map<string, number>();
for (const r of viewRows) {
const key = normalizeId(r.contented);
validSumById.set(key, (validSumById.get(key) ?? 0) + (r.validViewDay ?? 0));
}
// 5) 비용 단가
const cpvRow = await prisma.costPerView.findUnique({ where: { id: 1 } });
const cpv = cpvRow?.costPerView ?? 0;
const items = contents.map(c => {
const handle = handleByContentId.get(c.id) ?? '';
const handleObj = handleByStr.get(handle);
const validViews = validSumById.get(normalizeId(c.id)) ?? 0;
const expectedRevenue = validViews * cpv;
return {
id: c.id,
subject: c.subject,
pubDate: c.pubDate,
views: c.views,
premiumViews: c.premiumViews,
watchTime: c.watchTime,
handle,
handleId: handleObj?.id ?? null,
icon: handleObj?.icon ?? '',
validViews,
expectedRevenue,
};
});
return NextResponse.json({ items, cpv, start: startInclusive.toISOString(), end: endInclusive.toISOString() });
} catch (e) {
console.error('my_contents 오류:', e);
return NextResponse.json({ error: '조회 실패' }, { status: 500 });
} finally {
await prisma.$disconnect();
}
}

View File

@@ -37,9 +37,10 @@ const NavBar: React.FC<NavBarProps> = ({ isOpen, setIsOpen, user }) => {
<div className="flex-0 hidden lg:block">
<Link href="/">
<div className="flex items-center justify-center gap-1 mb-5 p-2 cursor-pointer">
<svg width="32" height="20" viewBox="0 0 32 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<Image src="/falogo2.png" alt="logo" width={64} height={64} />
{/* <svg width="32" height="20" viewBox="0 0 32 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 13.7815C0 11.1485 1.5051 9.48685 3.07055 8.61376C4.53165 7.7989 6.17151 7.56314 7.25262 7.56307H29.6288C30.9383 7.56309 32 8.65413 32 10C31.9999 11.3458 30.9383 12.4369 29.6288 12.4369H7.25262C6.70651 12.437 5.90554 12.5782 5.33295 12.8974C5.06963 13.0443 4.93117 13.1859 4.86171 13.2901C4.80809 13.3706 4.74246 13.5037 4.74246 13.7815C4.7425 14.0589 4.80813 14.1913 4.86171 14.2718C4.93111 14.376 5.06944 14.5175 5.33295 14.6644C5.90555 14.9838 6.70643 15.126 7.25262 15.1261H23.5259C24.8355 15.1261 25.8971 16.2172 25.8971 17.5631C25.8969 18.9088 24.8354 20 23.5259 20H7.25262C6.17151 19.9999 4.53165 19.763 3.07055 18.9481C1.50525 18.075 9.38884e-05 16.4143 0 13.7815ZM29.6288 0C30.9383 2.26081e-05 32 1.09107 32 2.43693C32 3.7828 30.9383 4.87385 29.6288 4.87387H14.5759C13.2664 4.87375 12.2046 3.78275 12.2046 2.43693C12.2046 1.09112 13.2664 0.000115545 14.5759 0H29.6288Z" fill="#F94B37" />
</svg>
</svg> */}
<span className="text-black text-2xl font-bold">EVERFACTORY</span>
</div>
</Link>

View File

@@ -3,6 +3,7 @@
import { signIn } from "next-auth/react"
export default function SignIn() {
return <button onClick={() => signIn("google")}>
return
<button onClick={() => signIn("google")}>
</button>
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,13 +0,0 @@
"use client";
import SignIn from "@/app/components/SignInClient";
export default function Test() {
return (
<div>
<SignIn/>
</div>
);
}

View File

@@ -44,7 +44,7 @@ export default function Home() {
<Image src="/ever_logo.png" alt="logo" width={100} height={100} />
</div>
<div className="text-white text-4xl font-bold text-center mb-3"> <br/> </div>
<div className="text-white text-md text-center mb-3">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi lobortis maximus</div>
{/* <div className="text-white text-md text-center mb-3">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi lobortis maximus</div> */}
</div>
<div>
<div className="text-white text-md text-center mb-3 cursor-pointer ">Ready to travel with us?<br/> Enter your email to create or restart your membership.</div>

View File

@@ -5,7 +5,8 @@ import DashboardClient from "@/app/components/Dashboard"; // 기존 코드 대
export default async function Page() {
const session = await auth();
if (!session) redirect("/login");
if (!session) redirect("/");
// 초기 데이터 서버에서 미리 가져오면 더 빠르고 안전 (예시)
// const [channels, contents] = await Promise.all([

12
auth.ts
View File

@@ -11,5 +11,15 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
],
// ...callbacks 등등
events: {
async signIn({ user, account, profile }) {
console.log("[NextAuth] signIn user:", {
id: (user as any)?.id,
name: user?.name,
email: user?.email,
image: (user as any)?.image,
provider: account?.provider,
});
},
},
});

View File

@@ -26,7 +26,6 @@ export default auth(async (req) => {
}
}
// 이미 로그인된 경우 루트 경로로 접근 시 대시보드로 리다이렉트
if (session) {
if (pathname === "/") {

View File

@@ -0,0 +1,18 @@
/*
Warnings:
- You are about to drop the column `premiumViews` on the `content` table. All the data in the column will be lost.
- You are about to drop the column `validViews` on the `content` table. All the data in the column will be lost.
- You are about to drop the column `views` on the `content` table. All the data in the column will be lost.
- You are about to drop the column `watchTime` on the `content` table. All the data in the column will be lost.
- You are about to drop the `costPerView` table. If the table is not empty, all the data it contains will be lost.
*/
-- AlterTable
ALTER TABLE `content` DROP COLUMN `premiumViews`,
DROP COLUMN `validViews`,
DROP COLUMN `views`,
DROP COLUMN `watchTime`;
-- DropTable
DROP TABLE `costPerView`;

View File

@@ -0,0 +1,49 @@
/*
Warnings:
- You are about to drop the `ContentHandle` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `userHandle` table. If the table is not empty, all the data it contains will be lost.
- Added the required column `handleId` to the `content` table without a default value. This is not possible if the table is not empty.
*/
-- DropForeignKey
ALTER TABLE `ContentHandle` DROP FOREIGN KEY `ContentHandle_contentId_fkey`;
-- DropForeignKey
ALTER TABLE `ContentHandle` DROP FOREIGN KEY `ContentHandle_handle_fkey`;
-- AlterTable
ALTER TABLE `content` ADD COLUMN `handleId` VARCHAR(191) NOT NULL;
-- DropTable
DROP TABLE `ContentHandle`;
-- DropTable
DROP TABLE `userHandle`;
-- CreateTable
CREATE TABLE `user` (
`id` VARCHAR(191) NOT NULL,
`email` VARCHAR(191) NOT NULL,
`icon` VARCHAR(191) NOT NULL DEFAULT '',
`isApproved` BOOLEAN NOT NULL DEFAULT false,
`createtime` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `handle` (
`id` VARCHAR(191) NOT NULL,
`handle` VARCHAR(191) NOT NULL,
`userId` VARCHAR(191) NOT NULL,
UNIQUE INDEX `handle_handle_key`(`handle`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- AddForeignKey
ALTER TABLE `content` ADD CONSTRAINT `content_handleId_fkey` FOREIGN KEY (`handleId`) REFERENCES `handle`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `handle` ADD CONSTRAINT `handle_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -14,16 +14,54 @@ datasource db {
url = env("DATABASE_URL")
}
model content{
// user
model User {
id String @id @default(uuid())
email String
icon String @default("")
isApproved Boolean @default(false)
createtime DateTime @default(now())
RegisgerCode String @default("")
// Many-to-many: a user can have many handles
handles Handle[]
// One-to-many: a user can have many register channels
registerChannels RegisterChannel[]
}
model RegisterChannel {
id String @id @default(uuid())
handle String
// Relation: many register channels belong to one user
userId String
user User @relation(fields: [userId], references: [id])
}
// handle
model Handle {
id String @id @default(uuid())
handle String @unique
constPerView Float @default(1)
// Relations
contents Content[]
// Many-to-many: a handle can belong to many users
users User[]
}
// content
model Content{
id String @id @default(uuid())
subject String
pubDate DateTime
views Int
validViews Int
premiumViews Int
watchTime Int
// Back relation: a content may link to at most one handle
contentHandle ContentHandle?
// views Int
// validViews Int
// premiumViews Int
// watchTime Int
// Relation: many contents belong to one handle
handleId String
handle Handle @relation(fields: [handleId], references: [id])
// Back relation: one content has many day views
dayViews contentDayView[]
}
model contentDayView{
@@ -34,21 +72,13 @@ model contentDayView{
validViews Int
premiumViews Int
watchTime Int
// Relation: many day views belong to one content
content Content @relation(fields: [contentId], references: [id])
@@unique([contentId, date])
}
model userHandle {
id String @id @default(uuid())
email String
handle String @unique
icon String @default("")
isApproved Boolean @default(false)
createtime DateTime @default(now())
// Relation: a handle can be linked to many contents
contentLinks ContentHandle[]
}
// notice
model noticeBoard{
id String @id @default(uuid())
title String
@@ -59,35 +89,6 @@ model noticeBoard{
}
model registerChannel {
id String @id @default(uuid())
email String
handle String
randomcode String
createtime DateTime @default(now())
}
model costPerView{
id Int @id @default(1)
costPerView Float
}
// Mapping table: Each content can be linked to at most one handle (unique contentId)
// A handle can have many contents
model ContentHandle {
id String @id @default(uuid())
contentId String @unique
handle String
// Relations
content content @relation(fields: [contentId], references: [id])
user userHandle @relation(fields: [handle], references: [handle])
@@index([handle])
}
/*
npx prisma migrate dev --name <migration_name>

BIN
public/falogo1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 KiB

BIN
public/falogo2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

BIN
public/falogo3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB