Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,246 changes: 0 additions & 3,246 deletions apps/web/bun.lock

This file was deleted.

1 change: 1 addition & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@better-auth/infra": "0.1.9-beta.1",
"@better-auth/stripe": "1.5.1",
"@better-auth/utils": "^0.3.1",
"@hello-pangea/dnd": "^18.0.1",
"@mixedbread-ai/sdk": "^2.2.11",
"@octokit/rest": "^22.0.1",
"@openrouter/ai-sdk-provider": "^2.2.3",
Expand Down
42 changes: 42 additions & 0 deletions apps/web/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,48 @@ model PromptRequestReaction {
@@map("prompt_request_reactions")
}

model KanbanItem {
id String @id
owner String
repo String
issueNumber Int
issueTitle String
issueUrl String
issueBody String?
status String @default("backlog")
aiSummary String?
assigneeLogin String?
assigneeAvatar String?
kanbanAssigneeLogin String?
kanbanAssigneeAvatar String?
authorLogin String?
authorAvatar String?
labels String? // JSON array of {name, color} objects
issueCommentCount Int @default(0)
issueState String @default("open") // open or closed
linkedPRs String? // JSON array of linked PR objects
createdAt String
updatedAt String

@@unique([owner, repo, issueNumber])
@@index([owner, repo, status])
@@map("kanban_items")
}

model KanbanComment {
id String @id
kanbanItemId String
userId String
userLogin String?
userName String
userAvatarUrl String
body String
createdAt String
updatedAt String

@@index([kanbanItemId, createdAt])
@@map("kanban_comments")
}
model PrOverviewAnalysis {
id String @id
owner String
Expand Down
90 changes: 90 additions & 0 deletions apps/web/src/app/(app)/repos/[owner]/[repo]/kanban/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { getOctokit, extractRepoPermissions } from "@/lib/github";
import { getKanbanItem, listKanbanComments } from "@/lib/kanban-store";
import { KanbanItemDetail } from "@/components/kanban/kanban-item-detail";
import { getServerSession } from "@/lib/auth";
import { ShieldAlert } from "lucide-react";

export async function generateMetadata({
params,
}: {
params: Promise<{ owner: string; repo: string; id: string }>;
}): Promise<Metadata> {
const { owner, repo, id } = await params;
const item = await getKanbanItem(id);
if (!item) {
return { title: `Kanban · ${owner}/${repo}` };
}
return { title: `${item.issueTitle} · Kanban · ${owner}/${repo}` };
}

async function checkMaintainerAccess(owner: string, repo: string): Promise<boolean> {
const octokit = await getOctokit();
if (!octokit) return false;

try {
const { data } = await octokit.repos.get({ owner, repo });
const perms = extractRepoPermissions(data);
return perms.push || perms.admin || perms.maintain;
} catch {
return false;
}
}

export default async function KanbanItemPage({
params,
}: {
params: Promise<{ owner: string; repo: string; id: string }>;
}) {
const { owner, repo, id } = await params;

const isMaintainer = await checkMaintainerAccess(owner, repo);
if (!isMaintainer) {
return (
<div className="py-16 flex flex-col items-center justify-center gap-4 text-center max-w-md mx-auto">
<div className="w-12 h-12 rounded-full bg-muted flex items-center justify-center">
<ShieldAlert className="w-6 h-6 text-amber-500" />
</div>
<div className="space-y-2">
<h1 className="text-sm font-medium">
Maintainer Access Required
</h1>
<p className="text-xs text-muted-foreground/80 leading-relaxed">
The Kanban board is only available to repository
maintainers.
</p>
</div>
</div>
);
}

const item = await getKanbanItem(id);
if (!item) {
notFound();
}

const comments = await listKanbanComments(id);
const session = await getServerSession();

const currentUser = session?.user
? {
id: session.user.id,
login: session.githubUser?.login ?? null,
name: session.user.name,
image: session.user.image ?? "",
}
: null;

return (
<div className="flex-1 min-h-0 flex flex-col p-4">
<KanbanItemDetail
owner={owner}
repo={repo}
item={item}
comments={comments}
currentUser={currentUser}
/>
</div>
);
}
Loading
Loading