Skip to content

Commit 16e159f

Browse files
larrylaaluisd101
andauthored
feat: v1.0.8 (#54)
* feat: ai summary feature (#51) * feat. AI suggest and rate * chore: ui adjustments and actions secret key * feat: refined project rating * feat: match score ai rating --------- Co-authored-by: luisd101 <[email protected]> * feat: leave/join feature (#52) * chore: basic leave/join * chore: fix unit test * feat: tabs refactor (#53) * feat: markdown allowed for overview and wanted * feat: all tabs new * chore: edit tweaks * chore: edit page images * chore: final tweaks --------- Co-authored-by: luisd101 <[email protected]>
1 parent 9e81de4 commit 16e159f

25 files changed

+3664
-765
lines changed

.github/workflows/deployment.yml

+1
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ jobs:
110110
env:
111111
VITE_CLERK_PUBLISHABLE_KEY: ${{ secrets.VITE_CLERK_PUBLISHABLE_KEY }}
112112
VITE_SERVER_HOST: ${{ secrets.VITE_SERVER_HOST }}
113+
VITE_GEMINI_API_KEY: ${{ secrets.VITE_GEMINI_API_KEY }}
113114
working-directory: frontend/bitmatch
114115

115116
- name: Sync build to S3

backend/projects/admin.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# Register the Project model
55
@admin.register(Project)
66
class ProjectAdmin(admin.ModelAdmin):
7-
list_display = ('title', 'group', 'institution', 'match_percentage', 'followers_count', 'likes_count')
7+
list_display = ('title', 'group', 'institution', 'followers_count', 'likes_count')
88
search_fields = ('title', 'group', 'institution')
99
list_filter = ('group', 'institution')
1010

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Generated by Django 5.1.7 on 2025-04-26 03:39
2+
3+
from django.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('projects', '0002_initial'),
10+
]
11+
12+
operations = [
13+
migrations.RemoveField(
14+
model_name='project',
15+
name='match_percentage',
16+
),
17+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Generated by Django 5.1.7 on 2025-04-26 05:09
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('projects', '0003_remove_project_match_percentage'),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name='project',
15+
name='description',
16+
field=models.TextField(max_length=255),
17+
),
18+
migrations.AlterField(
19+
model_name='project',
20+
name='full_description',
21+
field=models.TextField(blank=True, max_length=2500, null=True),
22+
),
23+
migrations.AlterField(
24+
model_name='project',
25+
name='wanted_description',
26+
field=models.TextField(blank=True, max_length=2500, null=True),
27+
),
28+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 5.1.7 on 2025-04-26 05:57
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('projects', '0004_alter_project_description_and_more'),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name='project',
15+
name='location',
16+
field=models.CharField(max_length=255),
17+
),
18+
]

backend/projects/models.py

+4-6
Original file line numberDiff line numberDiff line change
@@ -10,32 +10,30 @@ class Project(models.Model):
1010
# REQUIRED
1111
title = models.CharField(max_length=255, blank=False, null=False)
1212
institution = models.CharField(max_length=255, blank=False, null=False)
13-
description = models.TextField(blank=False, null=False)
13+
description = models.TextField(max_length=255,blank=False, null=False)
1414
positions = models.JSONField(default=list)
1515
image_url = models.ImageField(upload_to="projects/", blank=False, null=False)
1616

1717
# Not Required
1818
group = models.CharField(max_length=255, blank=True, null=True)
1919
followers_count = models.IntegerField(default=0)
2020
likes_count = models.IntegerField(default=0)
21-
match_percentage = models.FloatField(blank=True, null=True)
22-
location = models.JSONField(default=list, blank=True)
21+
location = models.CharField(max_length=255, blank=False, null=False)
2322
images = models.JSONField(default=list, blank=True)
2423
interest_tags = ArrayField(models.CharField(max_length=225), blank=True, null=True)
2524
skill_tags = ArrayField(models.CharField(max_length=225), blank=True, null=True)
26-
full_description = models.CharField(max_length=1000, blank=True, null=True)
25+
full_description = models.TextField(max_length=2500, blank=True, null=True)
2726
email = models.EmailField(unique=True, blank=True, null=True)
2827
other_contact = models.CharField(max_length=225, blank=True, null=True)
2928
updates = ArrayField(models.CharField(max_length=540), blank=True, null=True)
30-
wanted_description = models.CharField(max_length=540, blank=True, null=True)
29+
wanted_description = models.TextField(max_length=2500, blank=True, null=True)
3130

3231
members = models.ManyToManyField(
3332
'userauth.User',
3433
related_name='member_of_projects',
3534
blank=True
3635
)
3736

38-
# todo: see if we can remove this without hurting anything
3937
owner = models.ForeignKey(
4038
'userauth.User',
4139
on_delete=models.CASCADE,

backend/projects/tests.py

+2-10
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ def setUp(self):
1111
# Create a sample project record
1212
self.project = Project.objects.create(
1313
group="CPP SEA",
14-
match_percentage=85.5,
1514
title="Icebreak",
1615
institution="Cal Poly Pomona",
1716
description="We help connect organizations with their members.",
@@ -23,7 +22,6 @@ def setUp(self):
2322
# Rebecca's test project
2423
self.project2 = Project.objects.create(
2524
group="Codebreaker's Club",
26-
match_percentage=20,
2725
title="Enigma Machine Emulator",
2826
institution="University of California, Irvine",
2927
description="An Enigma machine emulator that replicates the encryption process of the Enigma M3 series used by the Germans.",
@@ -35,7 +33,6 @@ def setUp(self):
3533
# Luis' test project
3634
self.project3 = Project.objects.create(
3735
group="CodeWarriors",
38-
match_percentage=60,
3936
title="PersonalityPlaylist",
4037
institution="University of California, Riverside",
4138
description="An app to create custom Spotify playlists based on a personality profile.",
@@ -47,7 +44,6 @@ def setUp(self):
4744
#William's test project
4845
self.project4 = Project.objects.create(
4946
group="Byte Me",
50-
match_percentage=10,
5147
title="F1 Race Machine Learning Model",
5248
institution="University of California, Berkley",
5349
description="A machine learning model that takes data from F1 races and predicts winners based on previous results.",
@@ -59,7 +55,6 @@ def setUp(self):
5955
def test_project_creation_1(self):
6056
# Check if the record populated correctly
6157
self.assertEqual(self.project.group, "CPP SEA")
62-
self.assertEqual(self.project.match_percentage, 85.5)
6358
self.assertEqual(self.project.title, "Icebreak")
6459
self.assertEqual(self.project.institution, "Cal Poly Pomona")
6560
self.assertEqual(self.project.description, "We help connect organizations with their members.")
@@ -71,7 +66,6 @@ def test_project_creation_1(self):
7166
# Rebecca's test project
7267
def test_project_creation_2(self):
7368
self.assertEqual(self.project2.group, "Codebreaker's Club")
74-
self.assertEqual(self.project2.match_percentage, 20)
7569
self.assertEqual(self.project2.title, "Enigma Machine Emulator")
7670
self.assertEqual(self.project2.institution, "University of California, Irvine")
7771
self.assertEqual(self.project2.description, "An Enigma machine emulator that replicates the encryption process of the Enigma M3 series used by the Germans.")
@@ -84,7 +78,6 @@ def test_project_creation_2(self):
8478
def test_project_creation_3(self):
8579
# Check if the record populated correctly
8680
self.assertEqual(self.project3.group, "CodeWarriors")
87-
self.assertEqual(self.project3.match_percentage, 60)
8881
self.assertEqual(self.project3.title, "PersonalityPlaylist")
8982
self.assertEqual(self.project3.institution, "University of California, Riverside")
9083
self.assertEqual(self.project3.description, "An app to create custom Spotify playlists based on a personality profile.")
@@ -97,7 +90,6 @@ def test_project_creation_3(self):
9790
def test_project_creation_4(self):
9891
# Check if the record populated correctly
9992
self.assertEqual(self.project4.group, "Byte Me")
100-
self.assertEqual(self.project4.match_percentage, 10)
10193
self.assertEqual(self.project4.title, "F1 Race Machine Learning Model")
10294
self.assertEqual(self.project4.institution, "University of California, Berkley")
10395
self.assertEqual(self.project4.description, "A machine learning model that takes data from F1 races and predicts winners based on previous results.")
@@ -110,10 +102,10 @@ class GetProjectsAPITest(APITestCase):
110102
def setUp(self):
111103
# Add mock project data
112104
self.project1 = Project.objects.create(
113-
title="AI for Healthcare", institution="MIT", match_percentage=85.5
105+
title="AI for Healthcare", institution="MIT"
114106
)
115107
self.project2 = Project.objects.create(
116-
title="Web Dev", institution="Stanford", match_percentage=70.0
108+
title="Web Dev", institution="Stanford"
117109
)
118110
self.url = reverse("get_projects")
119111

backend/projects/urls.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
from django.urls import path
2-
from .views import ProjectCRUDView, get_projects, toggle_follow, toggle_like
2+
from .views import ProjectCRUDView, get_projects, toggle_follow, toggle_like, project_member_hander
33

44
urlpatterns = [
55
path('', get_projects, name='get_projects'), # Fetch all projects
66
path('create/', ProjectCRUDView.as_view(), name='project-create'), # Create project
77
path('<str:pk>/', ProjectCRUDView.as_view(), name='project-detail'), # Read, Update, Delete project by ID
88
path('like/<str:project_id>', toggle_like, name='toggle_like'),
9-
path('follow/<str:project_id>', toggle_follow, name='toggle_follow')
9+
path('follow/<str:project_id>', toggle_follow, name='toggle_follow'),
10+
path('member/manage/<str:project_id>', project_member_hander, name='project-member-handler'),
1011
]

backend/projects/views.py

+33
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,36 @@ def toggle_follow(request, project_id):
122122
return Response({'message': 'Project unfollowed successfully'}, status=status.HTTP_200_OK)
123123
else:
124124
return Response({'message': 'You already unfollowed this project.'}, status=status.HTTP_400_BAD_REQUEST)
125+
126+
@api_view(['PATCH'])
127+
@permission_classes([AllowAny])
128+
def project_member_hander(request, project_id):
129+
try:
130+
project = Project.objects.get(id=project_id)
131+
user = User.objects.get(id=request.data['user_id'])
132+
action = request.data.get('action')
133+
134+
is_member = project.members.filter(id=user.id).exists()
135+
136+
if action == "join":
137+
if is_member:
138+
return Response({'error': 'User is already a member of this project'}, status=400)
139+
project.members.add(user)
140+
user.projects.add(project)
141+
return Response({'message': 'Member added successfully'})
142+
143+
elif action == "leave":
144+
if not is_member:
145+
return Response({'error': 'User is not a member of this project'}, status=400)
146+
project.members.remove(user)
147+
user.projects.remove(project)
148+
return Response({'message': 'Member removed successfully'})
149+
150+
else:
151+
return Response({'error': 'Invalid action'}, status=400)
152+
153+
except Project.DoesNotExist:
154+
return Response({'error': 'Project not found'}, status=404)
155+
except User.DoesNotExist:
156+
return Response({'error': 'User not found'}, status=404)
157+

backend/userauth/admin.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ class UserAdmin(admin.ModelAdmin):
1010
)
1111
search_fields = ('username', 'email', 'first_name', 'last_name')
1212
list_filter = ('location',)
13-
filter_horizontal = ('projects', 'owned')
13+
filter_horizontal = ('projects',)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Generated by Django 5.1.7 on 2025-04-26 03:39
2+
3+
from django.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('userauth', '0009_remove_user_colleges_user_about_me_user_college_and_more'),
10+
]
11+
12+
operations = [
13+
migrations.RemoveField(
14+
model_name='user',
15+
name='owned',
16+
),
17+
]

backend/userauth/models.py

-5
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,6 @@ class User(models.Model):
2626
related_name='contributors',
2727
blank=True
2828
)
29-
owned = models.ManyToManyField(
30-
'projects.Project',
31-
related_name='owners',
32-
blank=True
33-
)
3429

3530
def __str__(self):
3631
return self.username or f"User-{self.id}"

backend/userauth/urls.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from django.urls import path
2-
from .views import UsersCRUDView, check_if_onboarded
2+
from .views import UsersCRUDView, check_if_onboarded, fetch_by_uuid
33

44
urlpatterns = [
55
path('onboard/', UsersCRUDView.as_view(), name='onboard-user'), # Create user
66
path('onboard/check/<str:clerk_id>', check_if_onboarded, name="onboard-check"), # Check if user onboarded
77
path('<str:clerk_id>/', UsersCRUDView.as_view(), name='user'), # Read, Update, Delete user by ID
8+
path('fetch/<str:uuid>/', fetch_by_uuid, name='uuid-fetch'),
89
]

backend/userauth/views.py

+7
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ def check_if_onboarded(request, clerk_id):
1414
return Response({"message": "User already onboarded"})
1515
else:
1616
return Response({"message": "User not onboarded yet!"})
17+
18+
@api_view(['GET'])
19+
@permission_classes([AllowAny])
20+
def fetch_by_uuid(request, uuid):
21+
user = get_object_or_404(User, pk=uuid)
22+
serializer = UserSerializer(user)
23+
return Response(serializer.data)
1724

1825
# DRF CRUD views for Users model
1926
class UsersCRUDView(APIView):

0 commit comments

Comments
 (0)