Project Model
The Project model represents projects synced from Plane.so, containing project metadata and relationships to work items and labels.
Schema Definition
model Project {
id String @id @default(uuid())
planeId String @unique
name String?
identifier String?
description String?
totalMembers Int? @map("total_members")
totalCycles Int? @map("total_cycles")
totalModules Int? @map("total_modules")
isMember Boolean? @map("is_member")
integrationId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
labels Label[]
integration PlaneIntegration @relation(fields: [integrationId], references: [id], onDelete: Cascade)
workItems WorkItem[]
@@map("projects")
}
Field Descriptions
Primary Identification
| Field | Type | Description |
|---|---|---|
id | String | UUID primary key |
planeId | String | Unique Plane.so project identifier |
Basic Information
| Field | Type | Description |
|---|---|---|
name | String? | Project name |
identifier | String? | Project identifier/code (e.g., "MOBILE", "WEB") |
description | String? | Project description |
Statistics
| Field | Type | Description |
|---|---|---|
totalMembers | Int? | Total number of project members |
totalCycles | Int? | Total number of development cycles |
totalModules | Int? | Total number of modules/components |
Permissions
| Field | Type | Description |
|---|---|---|
isMember | Boolean? | Whether current user is a member of this project |
Relations
| Field | Type | Description |
|---|---|---|
integrationId | String | Reference to PlaneIntegration.id |
Metadata
| Field | Type | Description |
|---|---|---|
createdAt | DateTime | Project sync timestamp |
updatedAt | DateTime | Last update timestamp |
Relationships
Belongs To
- integration:
PlaneIntegration- The Plane integration this project belongs to (Cascade delete)
One-to-Many
- labels:
Label[]- Labels associated with this project - workItems:
WorkItem[]- Work items in this project
Usage Examples
Create Synced Project
const project = await prisma.project.create({
data: {
planeId: "plane-project-123",
name: "Mobile App Development",
identifier: "MOBILE",
description: "Main mobile application project",
totalMembers: 8,
totalCycles: 12,
totalModules: 5,
isMember: true,
integrationId: "plane-integration-456"
}
});
Update Project Statistics
await prisma.project.update({
where: { planeId: "plane-project-123" },
data: {
totalMembers: 9,
totalWorkItems: 45,
updatedAt: new Date()
}
});
Get Projects with Relations
const projects = await prisma.project.findMany({
include: {
integration: true,
labels: {
select: {
name: true,
color: true
}
},
workItems: {
where: { state: "in_progress" },
select: {
name: true,
priority: true
}
},
_count: {
select: {
workItems: true,
labels: true
}
}
}
});
Find Projects by Integration
const integrationProjects = await prisma.project.findMany({
where: {
integrationId: "plane-integration-456",
isMember: true
},
orderBy: { name: "asc" }
});
Sync Logic
Initial Project Sync
async function syncProjects(integrationId: string) {
const integration = await prisma.planeIntegration.findUnique({
where: { id: integrationId }
});
// Fetch projects from Plane API
const projects = await fetchPlaneProjects(integration);
// Upsert projects in database
for (const planeProject of projects) {
await prisma.project.upsert({
where: { planeId: planeProject.id },
update: {
name: planeProject.name,
description: planeProject.description,
totalMembers: planeProject.total_members,
isMember: planeProject.is_member,
updatedAt: new Date()
},
create: {
planeId: planeProject.id,
name: planeProject.name,
identifier: planeProject.identifier,
description: planeProject.description,
totalMembers: planeProject.total_members,
isMember: planeProject.is_member,
integrationId: integrationId
}
});
}
}
Project Statistics Calculation
async function updateProjectStats(projectId: string) {
const workItemStats = await prisma.workItem.groupBy({
by: ['state'],
where: { projectId },
_count: true
});
const labelCount = await prisma.label.count({
where: { projectId }
});
await prisma.project.update({
where: { id: projectId },
data: {
totalWorkItems: workItemStats.reduce((sum, stat) => sum + stat._count, 0),
// Update other stats...
updatedAt: new Date()
}
});
}
Performance Considerations
Indexing
- Unique index on
planeIdfor API sync operations - Index on
integrationIdfor filtering by integration - Index on
isMemberfor member-only queries - Index on
updatedAtfor sync status queries
Query Optimization
- Use
selectto fetch only needed fields - Include relations only when necessary
- Use pagination for large project lists
- Cache frequently accessed project data
Bulk Operations
// Bulk update project statistics
const projectUpdates = projects.map(project => ({
where: { planeId: project.planeId },
data: {
totalMembers: project.total_members,
updatedAt: new Date()
}
}));
await prisma.$transaction(
projectUpdates.map(update => prisma.project.update(update))
);
Best Practices
Data Consistency
- Always sync through the integration API
- Validate data before saving
- Handle API errors gracefully
- Implement retry logic for failed syncs
Access Control
- Respect Plane project permissions
- Filter projects based on user membership
- Implement proper authorization checks
Monitoring
- Track sync success/failure rates
- Monitor API rate limit usage
- Log project changes for auditing
- Alert on sync failures
Cleanup
- Remove projects that are no longer accessible
- Archive old project data if needed
- Clean up orphaned records regularly