Skip to content

Commit 2bb1805

Browse files
committed
made project to have multiple categories
1 parent 33a7bde commit 2bb1805

12 files changed

Lines changed: 674 additions & 68 deletions

File tree

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
# Project Category Migration Guide
2+
3+
## Overview
4+
Successfully migrated the project-category relationship from **one-to-one** to **many-to-many** (one project can have multiple categories).
5+
6+
## Changes Summary
7+
8+
### 1. Database Schema Changes
9+
10+
#### New Junction Table Created
11+
- **Table**: `project_categories`
12+
- **Purpose**: Links projects to multiple categories
13+
- **Columns**:
14+
- `id` (UUID, primary key)
15+
- `project_id` (UUID, foreign key to `projects.id`)
16+
- `category_id` (INTEGER, foreign key to `projects_category.id`)
17+
- `created_at` (timestamp)
18+
- **Constraints**:
19+
- Unique constraint on `(project_id, category_id)` to prevent duplicates
20+
- Foreign keys with CASCADE delete
21+
22+
#### Removed Column
23+
- Removed `project_category_id` from `projects` table (old one-to-one relationship)
24+
25+
#### Migration File Location
26+
[migration-scripts/project-category-many-to-many.sql](migration-scripts/project-category-many-to-many.sql)
27+
28+
**The migration includes**:
29+
- Automatic data migration from old `project_category_id` to new junction table
30+
- Indexes for performance optimization
31+
- A helpful view `projects_with_categories` for easy querying
32+
33+
---
34+
35+
### 2. TypeScript Type Updates
36+
37+
#### New Types Added
38+
Location: [apps/codebility/types/home/codev.ts](apps/codebility/types/home/codev.ts)
39+
40+
```typescript
41+
export interface ProjectCategory {
42+
id: number;
43+
name: string;
44+
description?: string;
45+
created_at?: string;
46+
updated_at?: string;
47+
}
48+
49+
export interface ProjectCategoryJunction {
50+
id: string;
51+
project_id: string;
52+
category_id: number;
53+
created_at?: string;
54+
}
55+
```
56+
57+
#### Updated Project Interface
58+
- **Changed**: `project_category_id?: number`
59+
- **To**: `categories?: ProjectCategory[]`
60+
61+
---
62+
63+
### 3. Service Layer Updates
64+
65+
#### Updated Files:
66+
- [apps/codebility/lib/server/project.service.ts](apps/codebility/lib/server/project.service.ts)
67+
- [apps/codebility/app/home/projects/actions.ts](apps/codebility/app/home/projects/actions.ts)
68+
69+
**Changes:**
70+
- All queries now fetch categories through the junction table
71+
- Categories are automatically flattened for easier consumption
72+
- Query structure:
73+
```typescript
74+
categories:project_categories(
75+
projects_category(
76+
id,
77+
name,
78+
description
79+
)
80+
)
81+
```
82+
83+
---
84+
85+
### 4. CRUD Operations Updated
86+
87+
#### Create Project
88+
- Now accepts `category_ids` (JSON array) instead of single `project_category_id`
89+
- Automatically inserts into junction table
90+
91+
#### Update Project
92+
- Accepts `category_ids` (JSON array)
93+
- Replaces all existing categories for the project
94+
95+
#### Get Project
96+
- Automatically includes all categories in the response
97+
98+
---
99+
100+
### 5. New Helper Functions
101+
102+
Location: [apps/codebility/app/home/projects/actions.ts](apps/codebility/app/home/projects/actions.ts)
103+
104+
```typescript
105+
// Get all categories for a specific project
106+
getProjectCategoriesForProject(projectId: string)
107+
108+
// Add categories to a project
109+
addCategoriesToProject(projectId: string, categoryIds: number[])
110+
111+
// Remove a single category from a project
112+
removeCategoryFromProject(projectId: string, categoryId: number)
113+
114+
// Replace all categories for a project
115+
replaceProjectCategories(projectId: string, categoryIds: number[])
116+
```
117+
118+
---
119+
120+
### 6. Admin Dashboard Updates
121+
122+
Location: [apps/codebility/app/home/admin-dashboard/page.tsx](apps/codebility/app/home/admin-dashboard/page.tsx)
123+
124+
- Updated to query the junction table
125+
- Counts unique projects per category
126+
- Handles many-to-many relationship correctly
127+
128+
---
129+
130+
## How to Apply the Migration
131+
132+
### Step 1: Run the Migration SQL
133+
Execute the migration file in your Supabase SQL editor:
134+
135+
```bash
136+
# File: migration-scripts/project-category-many-to-many.sql
137+
```
138+
139+
**This will**:
140+
1. Create `projects_category` table if it doesn't exist
141+
2. Create the `project_categories` junction table
142+
3. Migrate existing data automatically
143+
4. Remove the old `project_category_id` column
144+
5. Create helpful indexes and views
145+
146+
### Step 2: Test the Changes
147+
No code changes needed - the application code has been updated to work with the new schema.
148+
149+
### Step 3: Update Frontend Components (If Needed)
150+
You'll need to update any forms that create/edit projects to send `category_ids` as a JSON array:
151+
152+
#### Example: Creating a Project
153+
```javascript
154+
const formData = new FormData();
155+
formData.append("name", "My Project");
156+
formData.append("category_ids", JSON.stringify([1, 2, 3])); // Multiple categories
157+
```
158+
159+
#### Example: Displaying Categories
160+
```typescript
161+
// Old way (single category)
162+
{project.project_category_id}
163+
164+
// New way (multiple categories)
165+
{project.categories?.map(cat => cat.name).join(", ")}
166+
```
167+
168+
---
169+
170+
## Frontend Components That May Need Updates
171+
172+
These components reference `projects_category` or `project_category_id` and may need UI updates:
173+
174+
1. [apps/codebility/app/home/projects/_components/ProjectAddModal.tsx](apps/codebility/app/home/projects/_components/ProjectAddModal.tsx)
175+
2. [apps/codebility/app/home/projects/_components/ProjectEditModal.tsx](apps/codebility/app/home/projects/_components/ProjectEditModal.tsx)
176+
3. [apps/codebility/app/home/projects/_components/ProjectViewModal.tsx](apps/codebility/app/home/projects/_components/ProjectViewModal.tsx)
177+
4. [apps/codebility/app/home/projects/_components/ProjectCardContainer.tsx](apps/codebility/app/home/projects/_components/ProjectCardContainer.tsx)
178+
179+
**Recommended UI Update:**
180+
- Change single select dropdowns to multi-select or checkbox groups for categories
181+
- Display multiple category badges/chips instead of a single category name
182+
183+
---
184+
185+
## Benefits of This Change
186+
187+
1. **Flexibility**: Projects can now belong to multiple categories
188+
2. **Better Organization**: More accurate categorization of projects
189+
3. **Analytics**: Better insights into project distribution across categories
190+
4. **Scalability**: Easier to add new categories without restructuring
191+
192+
---
193+
194+
## Database View Available
195+
196+
A convenient view has been created for querying projects with their categories:
197+
198+
```sql
199+
SELECT * FROM projects_with_categories;
200+
```
201+
202+
This view returns projects with all their categories as a JSON array.
203+
204+
---
205+
206+
## Rollback Plan (If Needed)
207+
208+
If you need to rollback:
209+
210+
```sql
211+
-- 1. Add back the old column
212+
ALTER TABLE projects ADD COLUMN project_category_id INTEGER;
213+
214+
-- 2. Populate it with the first category from junction table (if any)
215+
UPDATE projects p
216+
SET project_category_id = (
217+
SELECT category_id
218+
FROM project_categories
219+
WHERE project_id = p.id
220+
LIMIT 1
221+
);
222+
223+
-- 3. Drop the junction table
224+
DROP TABLE project_categories;
225+
226+
-- 4. Revert code changes via git
227+
git revert <commit-hash>
228+
```
229+
230+
---
231+
232+
## Questions or Issues?
233+
234+
If you encounter any issues:
235+
1. Check that the migration SQL ran successfully
236+
2. Verify the junction table exists: `SELECT * FROM project_categories LIMIT 5;`
237+
3. Check browser console for any API errors
238+
4. Review the helper functions in [actions.ts](apps/codebility/app/home/projects/actions.ts) for examples
239+
240+
---
241+
242+
## Next Steps
243+
244+
1. **Run the migration** in your Supabase database
245+
2. **Test the application** to ensure project queries work correctly
246+
3. **Update frontend forms** to use multi-select for categories
247+
4. **Update display components** to show multiple categories
248+
5. **Test create/edit operations** with multiple categories
249+
250+
Good luck! 🚀

‎apps/codebility/app/(marketing)/services/_components/ServicesServiceCard.tsx‎

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,11 @@ interface ServiceProject {
2929
start_date?: string;
3030
end_date?: string;
3131
members?: TeamMember[];
32-
project_category_id?: number;
33-
project_category_name?: string;
32+
categories?: Array<{
33+
id: number;
34+
name: string;
35+
description?: string;
36+
}>;
3437
client_id?: string;
3538
created_at?: string;
3639
updated_at?: string;
@@ -51,7 +54,7 @@ function ServiceCard({ service }: Props) {
5154
description,
5255
members = [],
5356
website_url,
54-
project_category_name,
57+
categories = [],
5558
tech_stack = [],
5659
} = service;
5760

@@ -115,9 +118,16 @@ function ServiceCard({ service }: Props) {
115118
loading="lazy"
116119
quality={75}
117120
/>
118-
{project_category_name && (
119-
<div className="absolute left-3 top-3 inline-flex items-center px-3 py-1.5 text-xs font-semibold text-white rounded-full backdrop-blur-sm shadow-lg bg-blue-600/80 transition-all duration-300 group-hover:scale-105 group-hover:shadow-lg group-hover:shadow-blue-500/20">
120-
{project_category_name}
121+
{categories.length > 0 && (
122+
<div className="absolute left-3 top-3 flex flex-wrap gap-1.5 max-w-[calc(100%-1.5rem)]">
123+
{categories.map((category) => (
124+
<div
125+
key={category.id}
126+
className="inline-flex items-center px-3 py-1.5 text-xs font-semibold text-white rounded-full backdrop-blur-sm shadow-lg bg-blue-600/80 transition-all duration-300 group-hover:scale-105 group-hover:shadow-lg group-hover:shadow-blue-500/20"
127+
>
128+
{category.name}
129+
</div>
130+
))}
121131
</div>
122132
)}
123133

‎apps/codebility/app/(marketing)/services/_components/ServicesTab.tsx‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,11 @@ interface ProjectData {
3030
team_leader?: TeamMember;
3131
client_id?: string;
3232
members?: TeamMember[];
33-
project_category_id?: number;
34-
project_category_name?: string;
33+
categories?: Array<{
34+
id: number;
35+
name: string;
36+
description?: string;
37+
}>;
3538
created_at?: string;
3639
updated_at?: string;
3740
tech_stack?: string[];
@@ -64,7 +67,7 @@ function ServicesTab({ servicesData }: Props) {
6467
return currentCategory === ALL_CATEGORY_ID
6568
? servicesData
6669
: servicesData.filter(
67-
(project) => project.project_category_id === currentCategory,
70+
(project) => project.categories?.some(cat => cat.id === currentCategory),
6871
);
6972
}, [currentCategory, servicesData]);
7073

‎apps/codebility/app/(marketing)/services/page.tsx‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,11 @@ interface ProjectData {
3535
figma_link?: string;
3636
client_id?: string;
3737
members?: TeamMember[];
38-
project_category_id?: number;
39-
project_category_name?: string;
38+
categories?: Array<{
39+
id: number;
40+
name: string;
41+
description?: string;
42+
}>;
4043
created_at?: string;
4144
updated_at?: string;
4245
tech_stack?: string[];
@@ -59,8 +62,7 @@ const ServicesPage = async () => {
5962
figma_link: item.figma_link || "",
6063
start_date: item.start_date || "",
6164
end_date: item.end_date || "",
62-
project_category_id: item.projects_category?.id || 0,
63-
project_category_name: item.projects_category?.name || "",
65+
categories: item.categories || [],
6466
client_id: item.client_id,
6567
members:
6668
item.project_members?.map((pm: any) => ({

‎apps/codebility/app/api/all-active-interns-codev-prioritized/route.ts‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,13 @@ export async function GET() {
6363
website_url,
6464
figma_link,
6565
client_id,
66-
project_category_id,
66+
categories:project_categories(
67+
projects_category(
68+
id,
69+
name,
70+
description
71+
)
72+
),
6773
kanban_display,
6874
public_display
6975
)
@@ -87,6 +93,10 @@ export async function GET() {
8793
...pm.projects,
8894
role: pm.role,
8995
joined_at: pm.joined_at,
96+
// Flatten categories from nested structure
97+
categories: (pm.projects?.categories || []).map(
98+
(cat: any) => cat.projects_category
99+
).filter(Boolean),
90100
})) || [];
91101

92102
return {

0 commit comments

Comments
 (0)