Skip to content

Commit 1034dfa

Browse files
committed
feat(badge): add mentorship badges for mentors and mentees
1 parent 35a05d0 commit 1034dfa

File tree

11 files changed

+306
-1
lines changed

11 files changed

+306
-1
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.bounswe.jobboardbackend.badge.event;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Getter;
5+
6+
/**
7+
* Event published when a user creates a mentor profile.
8+
* Used to trigger badge checks for mentor badges.
9+
*/
10+
@Getter
11+
@AllArgsConstructor
12+
public class MentorProfileCreatedEvent {
13+
private final Long mentorUserId;
14+
}
15+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.bounswe.jobboardbackend.badge.event;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Getter;
5+
6+
/**
7+
* Event published when a mentee leaves a review for a mentor.
8+
* Used to trigger badge checks for feedback giver badges.
9+
*/
10+
@Getter
11+
@AllArgsConstructor
12+
public class MentorReviewCreatedEvent {
13+
private final Long reviewerUserId;
14+
private final Long reviewId;
15+
}
16+
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.bounswe.jobboardbackend.badge.event;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Getter;
5+
6+
/**
7+
* Event published when a mentor accepts a mentorship request.
8+
* Used to trigger badge checks for both mentor and mentee.
9+
*/
10+
@Getter
11+
@AllArgsConstructor
12+
public class MentorshipRequestAcceptedEvent {
13+
private final Long mentorUserId;
14+
private final Long menteeUserId;
15+
private final Long requestId;
16+
}
17+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.bounswe.jobboardbackend.badge.event;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Getter;
5+
6+
/**
7+
* Event published when a mentee sends a mentorship request.
8+
* Used to trigger badge checks for mentee badges.
9+
*/
10+
@Getter
11+
@AllArgsConstructor
12+
public class MentorshipRequestCreatedEvent {
13+
private final Long menteeUserId;
14+
private final Long requestId;
15+
}
16+

apps/jobboard-backend/src/main/java/org/bounswe/jobboardbackend/badge/listener/BadgeEventListener.java

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
import org.bounswe.jobboardbackend.badge.event.JobPostCreatedEvent;
99
import org.bounswe.jobboardbackend.badge.event.JobApplicationCreatedEvent;
1010
import org.bounswe.jobboardbackend.badge.event.JobApplicationApprovedEvent;
11+
import org.bounswe.jobboardbackend.badge.event.MentorProfileCreatedEvent;
12+
import org.bounswe.jobboardbackend.badge.event.MentorshipRequestCreatedEvent;
13+
import org.bounswe.jobboardbackend.badge.event.MentorshipRequestAcceptedEvent;
14+
import org.bounswe.jobboardbackend.badge.event.MentorReviewCreatedEvent;
1115
import org.bounswe.jobboardbackend.badge.service.BadgeService;
1216
import org.springframework.stereotype.Component;
1317
import org.springframework.transaction.event.TransactionPhase;
@@ -114,5 +118,67 @@ public void onJobApplicationApproved(JobApplicationApprovedEvent event) {
114118
log.error("Badge check failed for job application approval for job seeker {}: {}", event.getJobSeekerId(), e.getMessage());
115119
}
116120
}
121+
122+
// ==================== MENTORSHIP EVENTS ====================
123+
124+
/**
125+
* Handle mentor profile creation - award GUIDE badge.
126+
* Only executes after the transaction commits successfully.
127+
*/
128+
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
129+
public void onMentorProfileCreated(MentorProfileCreatedEvent event) {
130+
try {
131+
log.debug("Mentor profile created by user {}, checking badges...", event.getMentorUserId());
132+
badgeService.checkMentorProfileBadge(event.getMentorUserId());
133+
} catch (Exception e) {
134+
log.error("Badge check failed for mentor profile creation by user {}: {}", event.getMentorUserId(), e.getMessage());
135+
}
136+
}
137+
138+
/**
139+
* Handle mentorship request creation - check for mentee request badges.
140+
* Only executes after the transaction commits successfully.
141+
*/
142+
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
143+
public void onMentorshipRequestCreated(MentorshipRequestCreatedEvent event) {
144+
try {
145+
log.debug("Mentorship request created by mentee {}, checking badges...", event.getMenteeUserId());
146+
badgeService.checkMenteeRequestBadges(event.getMenteeUserId());
147+
} catch (Exception e) {
148+
log.error("Badge check failed for mentorship request by mentee {}: {}", event.getMenteeUserId(), e.getMessage());
149+
}
150+
}
151+
152+
/**
153+
* Handle mentorship request acceptance - check badges for both mentor and mentee.
154+
* Only executes after the transaction commits successfully.
155+
*/
156+
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
157+
public void onMentorshipRequestAccepted(MentorshipRequestAcceptedEvent event) {
158+
try {
159+
log.debug("Mentorship request accepted by mentor {}, checking badges...", event.getMentorUserId());
160+
// Check mentor badges
161+
badgeService.checkMentorAcceptanceBadges(event.getMentorUserId());
162+
// Check mentee badges
163+
badgeService.checkMenteeAcceptanceBadges(event.getMenteeUserId());
164+
} catch (Exception e) {
165+
log.error("Badge check failed for mentorship acceptance: mentor {}, mentee {}: {}",
166+
event.getMentorUserId(), event.getMenteeUserId(), e.getMessage());
167+
}
168+
}
169+
170+
/**
171+
* Handle mentor review creation - check for feedback giver badges.
172+
* Only executes after the transaction commits successfully.
173+
*/
174+
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
175+
public void onMentorReviewCreated(MentorReviewCreatedEvent event) {
176+
try {
177+
log.debug("Mentor review created by user {}, checking badges...", event.getReviewerUserId());
178+
badgeService.checkFeedbackGiverBadges(event.getReviewerUserId());
179+
} catch (Exception e) {
180+
log.error("Badge check failed for mentor review by user {}: {}", event.getReviewerUserId(), e.getMessage());
181+
}
182+
}
117183
}
118184

apps/jobboard-backend/src/main/java/org/bounswe/jobboardbackend/badge/model/BadgeType.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,54 @@ public enum BadgeType {
144144
"🏆",
145145
"Get accepted for 5 jobs",
146146
5
147+
),
148+
149+
// ==================== MENTOR BADGES ====================
150+
151+
GUIDE(
152+
"Guide",
153+
"Created your mentor profile",
154+
"🧭",
155+
"Create a mentor profile",
156+
1
157+
),
158+
FIRST_MENTEE(
159+
"First Mentee",
160+
"Accepted your first mentee",
161+
"🤝",
162+
"Accept your first mentorship request",
163+
1
164+
),
165+
DEDICATED_MENTOR(
166+
"Dedicated Mentor",
167+
"Accepted 5 mentees",
168+
"🎓",
169+
"Accept 5 mentorship requests",
170+
5
171+
),
172+
173+
// ==================== MENTEE BADGES ====================
174+
175+
SEEKING_GUIDANCE(
176+
"Seeking Guidance",
177+
"Requested your first mentorship",
178+
"🙋",
179+
"Send your first mentorship request",
180+
1
181+
),
182+
MENTORED(
183+
"Mentored",
184+
"Got accepted by a mentor",
185+
"📚",
186+
"Get accepted by a mentor",
187+
1
188+
),
189+
FEEDBACK_GIVER(
190+
"Feedback Giver",
191+
"Left your first mentor review",
192+
"✍️",
193+
"Review a mentor",
194+
1
147195
);
148196

149197
private final String displayName;

apps/jobboard-backend/src/main/java/org/bounswe/jobboardbackend/badge/service/BadgeService.java

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
import org.bounswe.jobboardbackend.jobapplication.model.JobApplicationStatus;
1212
import org.bounswe.jobboardbackend.jobapplication.repository.JobApplicationRepository;
1313
import org.bounswe.jobboardbackend.jobpost.repository.JobPostRepository;
14+
import org.bounswe.jobboardbackend.mentorship.model.RequestStatus;
15+
import org.bounswe.jobboardbackend.mentorship.repository.MentorProfileRepository;
16+
import org.bounswe.jobboardbackend.mentorship.repository.MentorReviewRepository;
17+
import org.bounswe.jobboardbackend.mentorship.repository.MentorshipRequestRepository;
1418
import org.springframework.stereotype.Service;
1519
import org.springframework.transaction.annotation.Propagation;
1620
import org.springframework.transaction.annotation.Transactional;
@@ -30,6 +34,9 @@ public class BadgeService {
3034
private final ForumCommentUpvoteRepository forumCommentUpvoteRepository;
3135
private final JobPostRepository jobPostRepository;
3236
private final JobApplicationRepository jobApplicationRepository;
37+
private final MentorProfileRepository mentorProfileRepository;
38+
private final MentorshipRequestRepository mentorshipRequestRepository;
39+
private final MentorReviewRepository mentorReviewRepository;
3340

3441
/**
3542
* Award a badge to a user if they don't already have it.
@@ -190,5 +197,88 @@ public void checkJobAcceptanceBadges(Long jobSeekerId) {
190197
awardBadge(jobSeekerId, BadgeType.CAREER_STAR);
191198
}
192199
}
200+
201+
// ==================== MENTOR BADGES ====================
202+
203+
/**
204+
* Award GUIDE badge when a user creates a mentor profile.
205+
* Called after a user creates their mentor profile.
206+
*
207+
* @param mentorUserId The mentor's user ID
208+
*/
209+
@Transactional(propagation = Propagation.REQUIRES_NEW)
210+
public void checkMentorProfileBadge(Long mentorUserId) {
211+
// If mentor profile exists, award GUIDE badge
212+
if (mentorProfileRepository.existsById(mentorUserId)) {
213+
awardBadge(mentorUserId, BadgeType.GUIDE);
214+
}
215+
}
216+
217+
/**
218+
* Check if mentor qualifies for any mentee acceptance badges and award them.
219+
* Called after a mentor accepts a mentorship request.
220+
*
221+
* @param mentorUserId The mentor's user ID
222+
*/
223+
@Transactional(propagation = Propagation.REQUIRES_NEW)
224+
public void checkMentorAcceptanceBadges(Long mentorUserId) {
225+
long acceptedCount = mentorshipRequestRepository.countByMentorIdAndStatus(
226+
mentorUserId, RequestStatus.ACCEPTED);
227+
228+
if (acceptedCount >= BadgeType.FIRST_MENTEE.getThreshold()) {
229+
awardBadge(mentorUserId, BadgeType.FIRST_MENTEE);
230+
}
231+
if (acceptedCount >= BadgeType.DEDICATED_MENTOR.getThreshold()) {
232+
awardBadge(mentorUserId, BadgeType.DEDICATED_MENTOR);
233+
}
234+
}
235+
236+
// ==================== MENTEE BADGES ====================
237+
238+
/**
239+
* Check if mentee qualifies for mentorship request badges and award them.
240+
* Called after a mentee sends a mentorship request.
241+
*
242+
* @param menteeUserId The mentee's user ID
243+
*/
244+
@Transactional(propagation = Propagation.REQUIRES_NEW)
245+
public void checkMenteeRequestBadges(Long menteeUserId) {
246+
long requestCount = mentorshipRequestRepository.countByRequesterId(menteeUserId);
247+
248+
if (requestCount >= BadgeType.SEEKING_GUIDANCE.getThreshold()) {
249+
awardBadge(menteeUserId, BadgeType.SEEKING_GUIDANCE);
250+
}
251+
}
252+
253+
/**
254+
* Check if mentee qualifies for mentorship acceptance badges and award them.
255+
* Called after a mentee's request is accepted by a mentor.
256+
*
257+
* @param menteeUserId The mentee's user ID
258+
*/
259+
@Transactional(propagation = Propagation.REQUIRES_NEW)
260+
public void checkMenteeAcceptanceBadges(Long menteeUserId) {
261+
long acceptedCount = mentorshipRequestRepository.countByRequesterIdAndStatus(
262+
menteeUserId, RequestStatus.ACCEPTED);
263+
264+
if (acceptedCount >= BadgeType.MENTORED.getThreshold()) {
265+
awardBadge(menteeUserId, BadgeType.MENTORED);
266+
}
267+
}
268+
269+
/**
270+
* Check if mentee qualifies for feedback giver badges and award them.
271+
* Called after a mentee leaves a review for a mentor.
272+
*
273+
* @param reviewerUserId The reviewer's (mentee's) user ID
274+
*/
275+
@Transactional(propagation = Propagation.REQUIRES_NEW)
276+
public void checkFeedbackGiverBadges(Long reviewerUserId) {
277+
long reviewCount = mentorReviewRepository.countByReviewerId(reviewerUserId);
278+
279+
if (reviewCount >= BadgeType.FEEDBACK_GIVER.getThreshold()) {
280+
awardBadge(reviewerUserId, BadgeType.FEEDBACK_GIVER);
281+
}
282+
}
193283
}
194284

apps/jobboard-backend/src/main/java/org/bounswe/jobboardbackend/mentorship/repository/MentorReviewRepository.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,7 @@
66

77
@Repository
88
public interface MentorReviewRepository extends JpaRepository<MentorReview, Long> {
9+
10+
// Badge methods
11+
long countByReviewerId(Long reviewerId);
912
}

apps/jobboard-backend/src/main/java/org/bounswe/jobboardbackend/mentorship/repository/MentorshipRequestRepository.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,11 @@ public interface MentorshipRequestRepository extends JpaRepository<MentorshipReq
3131

3232
// Dashboard stats methods
3333
long countByStatus(RequestStatus status);
34+
35+
// Badge methods
36+
long countByMentorIdAndStatus(Long mentorId, RequestStatus status);
37+
38+
long countByRequesterId(Long requesterId);
39+
40+
long countByRequesterIdAndStatus(Long requesterId, RequestStatus status);
3441
}

0 commit comments

Comments
 (0)