Skip to content

Commit ce87b37

Browse files
authored
refactor: introduce ProjectTask platform for project-level async work (#147)
* refactor: add project task platform * docs: update architecture direction * chore: update repo guidance
1 parent c384023 commit ce87b37

26 files changed

Lines changed: 1696 additions & 727 deletions

File tree

AGENTS.md

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
# AGENTS.md
2+
3+
This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
**Fulling v2** is an AI-powered development platform that integrates AI Agent ecosystem to provide full-stack development capabilities. Users can import existing projects from GitHub or create new projects directly on the platform.
8+
9+
**Core Value**: Free users' mental bandwidth through AI Agents. Users focus on development while Agents silently handle complex operations (deployment, infrastructure, etc.) without interruption.
10+
11+
**Key Features**:
12+
- **Flexible Project Creation**: Import from GitHub repositories or create new projects from scratch
13+
- **Optional Database**: Add PostgreSQL database on-demand when needed
14+
- **AI Agent Ecosystem**: AI agents handle development, testing, deployment, and infrastructure management
15+
- **Automated Operations**: Deployment, scaling, and infrastructure management happen automatically in the background
16+
- **Full-Stack Development**: Complete environment with optional database, terminal, and file management
17+
- **Zero Infrastructure Knowledge Required**: Users don't need to understand Kubernetes, networking, or DevOps
18+
19+
**Architecture**: The platform uses an **asynchronous reconciliation pattern** where API endpoints return immediately and background jobs sync desired state (database) with actual state (Kubernetes) every 3 seconds.
20+
21+
## Tech Stack
22+
23+
### Frontend
24+
- Framework: Next.js 16 (App Router) + React 19
25+
- Language: TypeScript
26+
- Styling: Tailwind CSS v4
27+
- UI Components: Shadcn/UI
28+
29+
### Backend
30+
- Runtime: Node.js 22
31+
- API: Next.js API Routes
32+
- Database ORM: Prisma
33+
- Authentication: NextAuth v5 (GitHub, Password, Sealos OAuth)
34+
35+
### Infrastructure
36+
- Container Orchestration: Kubernetes
37+
- Database: PostgreSQL (via KubeBlocks)
38+
- Web Terminal: ttyd (HTTP Basic Auth)
39+
- File Manager: FileBrowser
40+
41+
## Key Conventions
42+
43+
### Code Style
44+
- Use TypeScript strict mode
45+
- Always follow best practices
46+
- Write self-documenting code: for complex functions, describe purpose, expected inputs, and expected outputs above the function
47+
- Use functional components with hooks
48+
49+
### Naming Conventions
50+
- K8s resources: `{k8s-project-name}-{resource-type}-{6chars}`
51+
- Environment variables: `UPPER_SNAKE_CASE`
52+
- Database tables: PascalCase (Prisma models)
53+
- API routes: kebab-case
54+
- Files: kebab-case
55+
56+
### Component Organization
57+
- **Route-specific components**: Place in `_components/` directory under the route folder
58+
- Use `_` prefix to prevent Next.js from treating it as a route
59+
- Example: `app/(dashboard)/settings/_components/github-status-card.tsx`
60+
- **Shared components**: Place in top-level `components/` directory
61+
- Only for components used across multiple routes
62+
- Example: `components/ui/button.tsx`, `components/sidebar.tsx`
63+
64+
### Important Patterns
65+
66+
1. **Always use user-specific K8s service**:
67+
```typescript
68+
const k8sService = await getK8sServiceForUser(userId)
69+
```
70+
71+
2. **API endpoints are non-blocking**:
72+
- Only update database
73+
- Return immediately
74+
- Reconciliation jobs handle K8s operations
75+
76+
3. **Use optimistic locking**:
77+
- Repository layer handles locking automatically
78+
- Prevents concurrent updates
79+
80+
4. **Follow reconciliation pattern**:
81+
- API → Database → Reconciliation Job → Event → K8s Operation
82+
- Status updates happen asynchronously
83+
84+
## Key Design Decisions
85+
86+
### Why StatefulSet?
87+
- Persistent storage for each pod
88+
- Stable network identities
89+
- Ordered pod deployment
90+
91+
### Why Reconciliation Pattern?
92+
- Non-blocking API responses
93+
- Automatic recovery from failures
94+
- Consistent state management
95+
- Easy to monitor and debug
96+
97+
### Why User-Specific Namespaces?
98+
- Multi-tenancy isolation
99+
- Resource quotas per user
100+
- Separate kubeconfig per user
101+
- No cross-user access
102+
103+
## Project Structure
104+
105+
```
106+
Fulling/
107+
├── app/ # Next.js App Router
108+
│ ├── api/ # API Routes
109+
│ ├── (auth)/ # Auth pages
110+
│ └── (dashboard)/projects/[id]/
111+
│ └── _components/ # Route-specific components
112+
113+
├── components/ # Shared components
114+
├── hooks/ # Client-side hooks
115+
116+
├── lib/
117+
│ ├── data/ # Server-side data access (for Server Components)
118+
│ ├── actions/ # Client-side data access (Server Actions)
119+
│ ├── repo/ # Repository layer with optimistic locking
120+
│ ├── services/ # Business logic services
121+
│ ├── events/ # Event bus and listeners
122+
│ ├── jobs/ # Reconciliation background jobs
123+
│ ├── startup/ # Application initialization
124+
│ ├── k8s/ # Kubernetes operations
125+
│ └── util/ # Utility functions
126+
127+
├── prisma/ # Prisma schema
128+
├── provider/ # React Context providers
129+
├── runtime/ # Sandbox Docker image
130+
└── yaml/ # Kubernetes templates
131+
```
132+
133+
**Key Directories**:
134+
- `lib/data/` - Server-side data access, used by Server Components
135+
- `lib/actions/` - Server Actions, used by Client Components
136+
- `lib/repo/` - Repository with optimistic locking, used by Jobs/Events
137+
- `lib/events/` + `lib/jobs/` - Core of reconciliation pattern
138+
- `lib/startup/` - Initializes event listeners and reconciliation jobs
139+
140+
## Documentation Index
141+
142+
- [Architecture](./docs/architecture.md) - Reconciliation pattern, event system, state management
143+
- [Development Guide](./docs/development.md) - Local development, code patterns, testing
144+
- [Operations Manual](./docs/operations.md) - Deployment, monitoring, K8s operations
145+
- [Troubleshooting](./docs/troubleshooting.md) - Common issues, debugging commands
146+
147+
## Quick Reference
148+
149+
### Development Commands
150+
```bash
151+
pnpm dev # Start dev server
152+
pnpm build # Build for production
153+
pnpm lint # Run ESLint
154+
npx prisma generate # Generate Prisma client
155+
npx prisma db push # Push schema to database
156+
```
157+
158+
### Key Files
159+
- `lib/k8s/k8s-service-helper.ts` - User-specific K8s service
160+
- `lib/events/sandbox/sandboxListener.ts` - Sandbox lifecycle handlers
161+
- `lib/jobs/sandbox/sandboxReconcile.ts` - Sandbox reconciliation job
162+
- `lib/events/database/databaseListener.ts` - Database lifecycle handlers
163+
- `lib/jobs/database/databaseReconcile.ts` - Database reconciliation job
164+
- `lib/actions/project.ts` - Project creation (creates Sandbox only)
165+
- `lib/actions/database.ts` - Database creation/deletion (on-demand)
166+
- `prisma/schema.prisma` - Database schema
167+
- `instrumentation.ts` - Application startup
168+
169+
### Environment Variables
170+
- `DATABASE_URL` - Main app database connection
171+
- `NEXTAUTH_SECRET` - NextAuth secret
172+
- `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET` - GitHub OAuth
173+
- `SEALOS_JWT_SECRET` - Sealos OAuth validation
174+
- `RUNTIME_IMAGE` - Container image version
175+
176+
### Resource Status
177+
- `CREATING``STARTING``RUNNING``STOPPING``STOPPED`
178+
- `UPDATING` - Environment variables being updated
179+
- `TERMINATING``TERMINATED`
180+
- `ERROR` - Operation failed
181+
182+
### Port Exposure
183+
- **3000**: Next.js application
184+
- **7681**: ttyd web terminal
185+
- **8080**: FileBrowser (file manager)
186+
187+
## Important Notes
188+
189+
- **Project Resources**: Each project includes a Sandbox (required) and can optionally have a Database (PostgreSQL). Database can be added on-demand after project creation.
190+
- **Reconciliation Delay**: Status updates may take up to 3 seconds
191+
- **User-Specific Namespaces**: Each user operates in their own K8s namespace
192+
- **Frontend Polling**: Client components poll every 3 seconds for status updates
193+
- **Database Wait Time**: PostgreSQL cluster takes 2-3 minutes to reach "Running" (when added)
194+
- **Idempotent Operations**: All K8s methods can be called multiple times safely
195+
- **Lock Duration**: Optimistic locks held for 30 seconds
196+
- **Deployment Domain**: Main app listens on `0.0.0.0:3000` (not localhost) for Sealos

app/(dashboard)/projects/(list)/_components/home-page-content.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { ProjectList } from './project-list'
1515
const REFRESH_INTERVAL_MS = 3000
1616

1717
interface HomePageContentProps {
18-
projects: ProjectWithRelations<{ sandboxes: true }>[]
18+
projects: ProjectWithRelations<{ sandboxes: true; tasks: true }>[]
1919
}
2020

2121
export function HomePageContent({ projects }: HomePageContentProps) {

app/(dashboard)/projects/(list)/_components/project-list.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { CreateProjectCard } from './create-project-card'
99
import { ProjectCard } from './project-card'
1010

1111
interface ProjectListProps {
12-
projects: ProjectWithRelations<{ sandboxes: true }>[]
12+
projects: ProjectWithRelations<{ sandboxes: true; tasks: true }>[]
1313
activeFilter: 'ALL' | ProjectDisplayStatus
1414
}
1515

app/(dashboard)/projects/(list)/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export default async function HomePage() {
1717
redirect('/login')
1818
}
1919

20-
const projects = await getProjects(session.user.id, { sandboxes: true })
20+
const projects = await getProjects(session.user.id, { sandboxes: true, tasks: true })
2121

2222
return (
2323
<main className="flex-1 overflow-y-auto p-6 scroll-smooth">
@@ -26,4 +26,4 @@ export default async function HomePage() {
2626
</div>
2727
</main>
2828
)
29-
}
29+
}

app/(dashboard)/projects/_components/import-github-dialog.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { useCallback, useEffect, useState } from 'react'
44
import { FaGithub } from 'react-icons/fa'
55
import { MdRefresh } from 'react-icons/md'
6-
import type { ProjectImportStatus } from '@prisma/client'
6+
import type { ProjectTaskStatus, ProjectTaskType } from '@prisma/client'
77
import { useRouter } from 'next/navigation'
88
import { toast } from 'sonner'
99

@@ -97,19 +97,20 @@ export function ImportGitHubDialog({ open, onOpenChange }: ImportGitHubDialogPro
9797

9898
const pollImportStatus = async () => {
9999
try {
100-
const project = await GET<{ importStatus: ProjectImportStatus }>(
100+
const project = await GET<{ tasks: Array<{ type: ProjectTaskType; status: ProjectTaskStatus }> }>(
101101
`/api/projects/${importProjectId}`
102102
)
103103

104-
if (project.importStatus === 'READY') {
104+
const cloneTask = project.tasks.find((task) => task.type === 'CLONE_REPOSITORY')
105+
if (!cloneTask || cloneTask.status === 'SUCCEEDED') {
105106
toast.success('Repository imported successfully')
106107
onOpenChange(false)
107108
setImportProjectId(null)
108109
router.refresh()
109110
return
110111
}
111112

112-
if (project.importStatus === 'FAILED') {
113+
if (cloneTask.status === 'FAILED' || cloneTask.status === 'CANCELLED') {
113114
toast.error('Repository import failed. An empty project was created instead.')
114115
onOpenChange(false)
115116
setImportProjectId(null)

app/api/projects/[id]/route.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ type ProjectWithFullRelations = Prisma.ProjectGetPayload<{
99
sandboxes: true
1010
databases: true
1111
environments: true
12+
tasks: true
1213
}
1314
}>
1415

@@ -39,6 +40,9 @@ export const GET = withAuth<GetProjectResponse>(async (_req, context, session) =
3940
environments: {
4041
orderBy: { createdAt: 'asc' },
4142
},
43+
tasks: {
44+
orderBy: { createdAt: 'desc' },
45+
},
4246
},
4347
})
4448

@@ -51,4 +55,4 @@ export const GET = withAuth<GetProjectResponse>(async (_req, context, session) =
5155
console.error('Error fetching project:', error)
5256
return NextResponse.json({ error: 'Failed to fetch project details' }, { status: 500 })
5357
}
54-
})
58+
})

0 commit comments

Comments
 (0)