diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..e6be63d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,115 @@ +# Cline's Memory Bank + +I am Cline, an expert software engineer with a unique characteristic: my memory resets completely between sessions. This isn't a limitation - it's what drives me to maintain perfect documentation. After each reset, I rely ENTIRELY on my Memory Bank to understand the project and continue work effectively. I MUST read ALL memory bank files at the start of EVERY task - this is not optional. + +## Memory Bank Structure + +The Memory Bank consists of core files and optional context files, all in Markdown format. Files build upon each other in a clear hierarchy: + +flowchart TD + PB[projectbrief.md] --> PC[productContext.md] + PB --> SP[systemPatterns.md] + PB --> TC[techContext.md] + + PC --> AC[activeContext.md] + SP --> AC + TC --> AC + + AC --> P[progress.md] + +### Core Files (Required) +1. `projectbrief.md` + - Foundation document that shapes all other files + - Created at project start if it doesn't exist + - Defines core requirements and goals + - Source of truth for project scope + +2. `productContext.md` + - Why this project exists + - Problems it solves + - How it should work + - User experience goals + +3. `activeContext.md` + - Current work focus + - Recent changes + - Next steps + - Active decisions and considerations + - Important patterns and preferences + - Learnings and project insights + +4. `systemPatterns.md` + - System architecture + - Key technical decisions + - Design patterns in use + - Component relationships + - Critical implementation paths + +5. `techContext.md` + - Technologies used + - Development setup + - Technical constraints + - Dependencies + - Tool usage patterns + +6. `progress.md` + - What works + - What's left to build + - Current status + - Known issues + - Evolution of project decisions + +### Additional Context +Create additional files/folders within memory-bank/ when they help organize: +- Complex feature documentation +- Integration specifications +- API documentation +- Testing strategies +- Deployment procedures + +## Core Workflows + +### Plan Mode +flowchart TD + Start[Start] --> ReadFiles[Read Memory Bank] + ReadFiles --> CheckFiles{Files Complete?} + + CheckFiles -->|No| Plan[Create Plan] + Plan --> Document[Document in Chat] + + CheckFiles -->|Yes| Verify[Verify Context] + Verify --> Strategy[Develop Strategy] + Strategy --> Present[Present Approach] + +### Act Mode +flowchart TD + Start[Start] --> Context[Check Memory Bank] + Context --> Update[Update Documentation] + Update --> Execute[Execute Task] + Execute --> Document[Document Changes] + +## Documentation Updates + +Memory Bank updates occur when: +1. Discovering new project patterns +2. After implementing significant changes +3. When user requests with **update memory bank** (MUST review ALL files) +4. When context needs clarification + +flowchart TD + Start[Update Process] + + subgraph Process + P1[Review ALL Files] + P2[Document Current State] + P3[Clarify Next Steps] + P4[Document Insights & Patterns] + + P1 --> P2 --> P3 --> P4 + end + + Start --> Process + +Note: When triggered by **update memory bank**, I MUST review every memory bank file, even if some don't require updates. Focus particularly on activeContext.md and progress.md as they track current state. + +REMEMBER: After every memory reset, I begin completely fresh. The Memory Bank is my only link to previous work. It must be maintained with precision and clarity, as my effectiveness depends entirely on its accuracy. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index b3ccc68..98eb0de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@auth/prisma-adapter": "^2.11.1", "@prisma/adapter-pg": "^7.2.0", "@prisma/client": "^7.2.0", + "@radix-ui/react-label": "^2.1.8", "@radix-ui/react-slot": "^1.2.4", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -1645,6 +1646,52 @@ } } }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz", + "integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slot": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", @@ -2053,7 +2100,7 @@ "version": "19.2.3", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "dev": true, + "devOptional": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.2.0" diff --git a/package.json b/package.json index 392c9db..5c83ba4 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@auth/prisma-adapter": "^2.11.1", "@prisma/adapter-pg": "^7.2.0", "@prisma/client": "^7.2.0", + "@radix-ui/react-label": "^2.1.8", "@radix-ui/react-slot": "^1.2.4", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/prisma/migrations/20251224113949_add_message_models/migration.sql b/prisma/migrations/20251224113949_add_message_models/migration.sql new file mode 100644 index 0000000..a823d35 --- /dev/null +++ b/prisma/migrations/20251224113949_add_message_models/migration.sql @@ -0,0 +1,42 @@ +-- CreateTable +CREATE TABLE "Message" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "title" TEXT NOT NULL, + "content" TEXT, + "status" TEXT NOT NULL DEFAULT 'DRAFT', + "lastPing" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "checkInterval" INTEGER NOT NULL DEFAULT 24, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Message_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Recipient" ( + "id" TEXT NOT NULL, + "messageId" TEXT NOT NULL, + "email" TEXT NOT NULL, + + CONSTRAINT "Recipient_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Attachment" ( + "id" TEXT NOT NULL, + "messageId" TEXT NOT NULL, + "fileUrl" TEXT NOT NULL, + "fileName" TEXT NOT NULL, + + CONSTRAINT "Attachment_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "Message" ADD CONSTRAINT "Message_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Recipient" ADD CONSTRAINT "Recipient_messageId_fkey" FOREIGN KEY ("messageId") REFERENCES "Message"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Attachment" ADD CONSTRAINT "Attachment_messageId_fkey" FOREIGN KEY ("messageId") REFERENCES "Message"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 74adb23..4a362b8 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1,4 +1,3 @@ -// ... existing code ... generator client { provider = "prisma-client-js" } @@ -42,6 +41,37 @@ model User { image String? accounts Account[] sessions Session[] + messages Message[] +} + +model Message { + id String @id @default(cuid()) + userId String + title String + content String? @db.Text + status String @default("DRAFT") + lastPing DateTime @default(now()) + checkInterval Int @default(24) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + recipients Recipient[] + attachments Attachment[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model Recipient { + id String @id @default(cuid()) + messageId String + email String + message Message @relation(fields: [messageId], references: [id], onDelete: Cascade) +} + +model Attachment { + id String @id @default(cuid()) + messageId String + fileUrl String + fileName String + message Message @relation(fields: [messageId], references: [id], onDelete: Cascade) } model VerificationToken { diff --git a/src/app/api/messages/[messageId]/route.ts b/src/app/api/messages/[messageId]/route.ts new file mode 100644 index 0000000..e824650 --- /dev/null +++ b/src/app/api/messages/[messageId]/route.ts @@ -0,0 +1,80 @@ +import { NextResponse } from "next/server"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/lib/auth/options"; +import { prisma } from "@/lib/prisma"; + +export async function PATCH( + req: Request, + { params }: { params: Promise<{ messageId: string }> } +) { + const { messageId } = await params; + const session = await getServerSession(authOptions); + + if (!session?.user?.id) { + return new NextResponse("Unauthorized", { status: 401 }); + } + + try { + const body = await req.json(); + const { title, content, recipients, checkInterval } = body; + + const existingMessage = await prisma.message.findUnique({ + where: { id: messageId, userId: session.user.id }, + }); + + if (!existingMessage) { + return new NextResponse("Unauthorized", { status: 401 }); + } + + // Delete existing recipients and recreate them for simplicity in update + await prisma.recipient.deleteMany({ + where: { messageId: messageId }, + }); + + const message = await prisma.message.update({ + where: { + id: messageId, + userId: session.user.id, + }, + data: { + title, + content, + checkInterval, + recipients: { + create: recipients.map((email: string) => ({ email })), + }, + }, + }); + + return NextResponse.json(message); + } catch (error) { + console.error("MESSAGE_PATCH_ERROR", error); + return new NextResponse("Internal Server Error", { status: 500 }); + } +} + +export async function DELETE( + req: Request, + { params }: { params: Promise<{ messageId: string }> } +) { + const { messageId } = await params; + const session = await getServerSession(authOptions); + + if (!session?.user?.id) { + return new NextResponse("Unauthorized", { status: 401 }); + } + + try { + const message = await prisma.message.delete({ + where: { + id: messageId, + userId: session.user.id, + }, + }); + + return NextResponse.json(message); + } catch (error) { + console.error("MESSAGE_DELETE_ERROR", error); + return new NextResponse("Internal Server Error", { status: 500 }); + } +} diff --git a/src/app/api/messages/route.ts b/src/app/api/messages/route.ts new file mode 100644 index 0000000..b5e8fd8 --- /dev/null +++ b/src/app/api/messages/route.ts @@ -0,0 +1,61 @@ +import { NextResponse } from "next/server"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/lib/auth/options"; +import { prisma } from "@/lib/prisma"; + +export async function POST(req: Request) { + const session = await getServerSession(authOptions); + + if (!session?.user?.id) { + return new NextResponse("Unauthorized", { status: 401 }); + } + + try { + const body = await req.json(); + const { title, content, recipients, checkInterval } = body; + + const message = await prisma.message.create({ + data: { + userId: session.user.id, + title, + content, + checkInterval, + recipients: { + create: recipients.map((email: string) => ({ email })), + }, + }, + }); + + return NextResponse.json(message); + } catch (error) { + console.error("MESSAGE_POST_ERROR", error); + return new NextResponse("Internal Server Error", { status: 500 }); + } +} + +export async function GET(req: Request) { + const session = await getServerSession(authOptions); + + if (!session?.user?.id) { + return new NextResponse("Unauthorized", { status: 401 }); + } + + try { + const messages = await prisma.message.findMany({ + where: { + userId: session.user.id, + }, + include: { + recipients: true, + }, + orderBy: { + createdAt: "desc", + }, + }); + + return NextResponse.json(messages); + } catch (error) { + console.error("MESSAGES_GET_ERROR", error); + return new NextResponse("Internal Server Error", { status: 500 }); + } +} diff --git a/src/app/dashboard/messages/[messageId]/page.tsx b/src/app/dashboard/messages/[messageId]/page.tsx new file mode 100644 index 0000000..4c0d208 --- /dev/null +++ b/src/app/dashboard/messages/[messageId]/page.tsx @@ -0,0 +1,47 @@ +import { prisma } from "@/lib/prisma"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/lib/auth/options"; +import { redirect } from "next/navigation"; +import MessageForm from "@/components/messages/message-form"; + +export default async function EditMessagePage({ + params, +}: { + params: Promise<{ messageId: string }>; +}) { + const { messageId } = await params; + const session = await getServerSession(authOptions); + + if (!session) { + redirect("/auth/signin"); + } + + const message = await prisma.message.findUnique({ + where: { + id: messageId, + userId: session.user.id, + }, + include: { + recipients: true, + }, + }); + + if (!message) { + redirect("/dashboard"); + } + + const initialData = { + id: message.id, + title: message.title, + content: message.content || "", + checkInterval: message.checkInterval, + recipients: message.recipients.map((r) => r.email), + }; + + return ( +
+ İlk "Ölü Adam Anahtarı" mesajınızı oluşturarak başlayın. +
+ + + ++ Durum: {msg.status} +
++ Alıcılar: {msg.recipients.length} +
++ Kontrol: {msg.checkInterval} saat başı +
+