Skip to content

Commit 61bb815

Browse files
committed
feat: Add Community Dashboard API
- Implemented public dashboard endpoint with scheduled caching for performance. - Added statistics for users, jobs, and applications. - Stats for mentorship and forum are mock for now
1 parent fc08485 commit 61bb815

File tree

7 files changed

+143
-0
lines changed

7 files changed

+143
-0
lines changed

apps/jobboard-backend/src/main/java/org/bounswe/jobboardbackend/JobboardBackendApplication.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
import org.springframework.boot.SpringApplication;
44
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
import org.springframework.scheduling.annotation.EnableScheduling;
56

67
@SpringBootApplication
8+
@EnableScheduling
79
public class JobboardBackendApplication {
810

911
public static void main(String[] args) {

apps/jobboard-backend/src/main/java/org/bounswe/jobboardbackend/auth/repository/UserRepository.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.bounswe.jobboardbackend.auth.repository;
22

3+
import org.bounswe.jobboardbackend.auth.model.Role;
34
import org.bounswe.jobboardbackend.auth.model.User;
45
import org.springframework.data.jpa.repository.JpaRepository;
56
import org.springframework.stereotype.Repository;
@@ -15,4 +16,6 @@ public interface UserRepository extends JpaRepository<User, Long> {
1516
Boolean existsByEmail(String email);
1617

1718
Optional<User> findByEmail(String email);
19+
20+
long countByRole(Role role);
1821
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package org.bounswe.jobboardbackend.dashboard.controller;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.bounswe.jobboardbackend.dashboard.dto.DashboardStatsResponse;
5+
import org.bounswe.jobboardbackend.dashboard.service.CommunityDashboardService;
6+
import org.springframework.http.ResponseEntity;
7+
import org.springframework.web.bind.annotation.GetMapping;
8+
import org.springframework.web.bind.annotation.RequestMapping;
9+
import org.springframework.web.bind.annotation.RestController;
10+
11+
@RestController
12+
@RequestMapping("/api/public/dashboard")
13+
@RequiredArgsConstructor
14+
public class CommunityDashboardController {
15+
16+
private final CommunityDashboardService dashboardService;
17+
18+
@GetMapping
19+
public ResponseEntity<DashboardStatsResponse> getDashboardStats() {
20+
return ResponseEntity.ok(dashboardService.getStats());
21+
}
22+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package org.bounswe.jobboardbackend.dashboard.dto;
2+
3+
import lombok.*;
4+
5+
@Data
6+
@NoArgsConstructor
7+
@AllArgsConstructor
8+
@Builder
9+
public class DashboardStatsResponse {
10+
11+
// user related stats
12+
private long totalUsers;
13+
private long totalEmployers;
14+
private long totalJobSeekers;
15+
16+
private long totalForumPosts; // mock for now
17+
private long currentMentorships; // mock for now
18+
19+
// job post related stats
20+
private long totalJobPosts;
21+
private long remoteJobsCount;
22+
private long inclusiveJobsCount;
23+
private long newJobsThisWeekCount;
24+
25+
// job application related stats
26+
private long totalApplications;
27+
private long totalPendingApplications;
28+
private long totalAcceptedApplications;
29+
private long totalRejectedApplications;
30+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package org.bounswe.jobboardbackend.dashboard.service;
2+
3+
import jakarta.annotation.PostConstruct;
4+
import lombok.RequiredArgsConstructor;
5+
import org.bounswe.jobboardbackend.auth.model.Role; // Role Enum importu
6+
import org.bounswe.jobboardbackend.auth.repository.UserRepository;
7+
import org.bounswe.jobboardbackend.dashboard.dto.DashboardStatsResponse;
8+
import org.bounswe.jobboardbackend.jobapplication.model.JobApplicationStatus;
9+
import org.bounswe.jobboardbackend.jobapplication.repository.JobApplicationRepository;
10+
import org.bounswe.jobboardbackend.jobpost.repository.JobPostRepository;
11+
import org.springframework.scheduling.annotation.Scheduled;
12+
import org.springframework.stereotype.Service;
13+
14+
import java.time.LocalDateTime;
15+
16+
@Service
17+
@RequiredArgsConstructor
18+
public class CommunityDashboardService {
19+
20+
private final UserRepository userRepository;
21+
private final JobPostRepository jobPostRepository;
22+
private final JobApplicationRepository jobApplicationRepository;
23+
24+
private DashboardStatsResponse cachedStats = new DashboardStatsResponse();
25+
26+
@PostConstruct
27+
public void init() {
28+
refreshStats();
29+
}
30+
31+
@Scheduled(fixedRate = 86400000)
32+
public void refreshStats() {
33+
// 1. User Stats
34+
long totalUsers = userRepository.count();
35+
long totalEmployers = userRepository.countByRole(Role.ROLE_EMPLOYER);
36+
long totalJobSeekers = userRepository.countByRole(Role.ROLE_JOBSEEKER);
37+
38+
// 2. Job Post Stats
39+
long totalJobPosts = jobPostRepository.count();
40+
long remoteJobs = jobPostRepository.countByRemoteTrue();
41+
long inclusiveJobs = jobPostRepository.countByInclusiveOpportunityTrue();
42+
long newJobs = jobPostRepository.countByPostedDateAfter(LocalDateTime.now().minusDays(7));
43+
44+
// 3. Job Application Stats
45+
long totalApps = jobApplicationRepository.count();
46+
long pendingApps = jobApplicationRepository.countByStatus(JobApplicationStatus.PENDING);
47+
long acceptedApps = jobApplicationRepository.countByStatus(JobApplicationStatus.APPROVED);
48+
long rejectedApps = jobApplicationRepository.countByStatus(JobApplicationStatus.REJECTED);
49+
50+
// mock for now
51+
long forumPosts = 0;
52+
long mentorships = 0;
53+
54+
55+
// update the cached stats
56+
this.cachedStats = DashboardStatsResponse.builder()
57+
.totalUsers(totalUsers)
58+
.totalEmployers(totalEmployers)
59+
.totalJobSeekers(totalJobSeekers)
60+
.totalJobPosts(totalJobPosts)
61+
.totalForumPosts(forumPosts)
62+
.currentMentorships(mentorships)
63+
.remoteJobsCount(remoteJobs)
64+
.inclusiveJobsCount(inclusiveJobs)
65+
.newJobsThisWeekCount(newJobs)
66+
.totalApplications(totalApps)
67+
.totalPendingApplications(pendingApps)
68+
.totalAcceptedApplications(acceptedApps)
69+
.totalRejectedApplications(rejectedApps)
70+
.build();
71+
}
72+
73+
public DashboardStatsResponse getStats() {
74+
return this.cachedStats;
75+
}
76+
}

apps/jobboard-backend/src/main/java/org/bounswe/jobboardbackend/jobapplication/repository/JobApplicationRepository.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,7 @@ public interface JobApplicationRepository extends JpaRepository<JobApplication,
1616
List<JobApplication> findByJobPost_Workplace_Id(Long workplaceId);
1717

1818
boolean existsByJobSeekerIdAndJobPostId(Long jobSeekerId, Long jobPostId);
19+
20+
// needed for stats
21+
long countByStatus(org.bounswe.jobboardbackend.jobapplication.model.JobApplicationStatus status);
1922
}

apps/jobboard-backend/src/main/java/org/bounswe/jobboardbackend/jobpost/repository/JobPostRepository.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,11 @@ List<JobPost> findFiltered(
3838
@Param("nonProfit") Boolean nonProfit
3939
);
4040

41+
// needed for stats
42+
long countByRemoteTrue();
43+
44+
long countByInclusiveOpportunityTrue();
45+
46+
long countByPostedDateAfter(java.time.LocalDateTime date);
47+
4148
}

0 commit comments

Comments
 (0)