feat(reporting): compose role dashboards from feature services

This commit is contained in:
sechmachine
2026-08-15 01:30:18 +07:00
parent 3f2f789905
commit b1c6b170d0
9 changed files with 540 additions and 2 deletions
@@ -0,0 +1,40 @@
package com.lab.labtimesheet.feature.reporting.controller;
import com.lab.labtimesheet.feature.reporting.exception.DashboardAccessDeniedException;
import com.lab.labtimesheet.feature.reporting.service.DashboardService;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class DashboardController {
private final DashboardService dashboardService;
public DashboardController(DashboardService dashboardService) {
this.dashboardService = dashboardService;
}
@GetMapping("/dashboard")
public String dashboard(Authentication authentication, Model model) {
String email = authentication.getName();
if (hasRole(authentication, "ROLE_ADMIN")) {
model.addAttribute("dashboard", dashboardService.admin(email));
return "dashboard/admin";
}
if (hasRole(authentication, "ROLE_MENTOR")) {
model.addAttribute("dashboard", dashboardService.mentor(email));
return "dashboard/mentor";
}
if (hasRole(authentication, "ROLE_INTERN")) {
model.addAttribute("dashboard", dashboardService.intern(email));
return "dashboard/intern";
}
throw new DashboardAccessDeniedException("Dashboard access requires a supported global role");
}
private boolean hasRole(Authentication authentication, String role) {
return authentication.getAuthorities().stream().anyMatch(authority -> authority.getAuthority().equals(role));
}
}
@@ -0,0 +1,13 @@
package com.lab.labtimesheet.feature.reporting.exception;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.FORBIDDEN)
public class DashboardAccessDeniedException extends AccessDeniedException {
public DashboardAccessDeniedException(String message) {
super(message);
}
}
@@ -0,0 +1,97 @@
package com.lab.labtimesheet.feature.reporting.service;
import com.lab.labtimesheet.feature.account.model.AccountStatus;
import com.lab.labtimesheet.feature.account.model.GlobalRole;
import com.lab.labtimesheet.feature.account.model.dto.AccountIdentity;
import com.lab.labtimesheet.feature.account.model.dto.AccountSummary;
import com.lab.labtimesheet.feature.account.service.AccountService;
import com.lab.labtimesheet.feature.attendance.exception.AttendanceException;
import com.lab.labtimesheet.feature.attendance.model.dto.AttendanceCurrentState;
import com.lab.labtimesheet.feature.attendance.service.AttendanceApplicationService;
import com.lab.labtimesheet.feature.reporting.exception.DashboardAccessDeniedException;
import com.lab.labtimesheet.feature.reporting.model.dto.DashboardView;
import com.lab.labtimesheet.feature.reporting.model.dto.DashboardView.AssignedTask;
import com.lab.labtimesheet.feature.reporting.model.dto.DashboardView.AttendanceState;
import com.lab.labtimesheet.feature.project.model.dto.ProjectDashboardSummary;
import com.lab.labtimesheet.feature.project.service.ProjectQueryService;
import com.lab.labtimesheet.feature.task.model.dto.TaskDashboardView;
import com.lab.labtimesheet.feature.task.service.TaskDashboardService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional(readOnly = true)
public class DashboardService {
private final AccountService accounts;
private final ProjectQueryService projects;
private final TaskDashboardService tasks;
private final AttendanceApplicationService attendance;
public DashboardService(
AccountService accounts,
ProjectQueryService projects,
TaskDashboardService tasks,
AttendanceApplicationService attendance) {
this.accounts = accounts;
this.projects = projects;
this.tasks = tasks;
this.attendance = attendance;
}
public DashboardView.Admin admin(String email) {
AccountIdentity admin = activeAccount(email, GlobalRole.ADMIN);
AccountSummary accountSummary = accounts.summary();
ProjectDashboardSummary projectSummary = projects.dashboardSummary(admin.id());
return new DashboardView.Admin(
accountSummary.activeAccounts(),
accountSummary.pendingActivations(),
accountSummary.activeInternships(),
projectSummary.activeProjectCount());
}
public DashboardView.Mentor mentor(String email) {
AccountIdentity mentor = activeAccount(email, GlobalRole.MENTOR);
ProjectDashboardSummary projectSummary = projects.dashboardSummary(mentor.id());
TaskDashboardView taskSummary = tasks.dashboard(email);
return new DashboardView.Mentor(
mentor.displayName(),
projectSummary.activeProjectCount(),
projectSummary.distinctActiveMemberCount(),
taskSummary.blockedTaskCount());
}
public DashboardView.Intern intern(String email) {
AccountIdentity intern = activeAccount(email, GlobalRole.INTERN);
AttendanceCurrentState attendanceState;
try {
attendanceState = attendance.currentState(intern.id());
} catch (AttendanceException exception) {
throw new DashboardAccessDeniedException("Active Intern account and internship required");
}
ProjectDashboardSummary projectSummary = projects.dashboardSummary(intern.id());
TaskDashboardView taskSummary = tasks.dashboard(email);
return new DashboardView.Intern(
intern.displayName(),
AttendanceState.valueOf(attendanceState.name()),
projectSummary.activeProjectCount(),
taskSummary.assignedTaskCount(),
taskSummary.priorityTasks().stream()
.map(task -> new AssignedTask(
task.title(), task.projectName(), task.status().name(), task.dueDate()))
.toList());
}
private AccountIdentity activeAccount(String email, GlobalRole role) {
AccountIdentity account;
try {
account = accounts.requireIdentityByEmail(email);
} catch (IllegalArgumentException exception) {
throw new DashboardAccessDeniedException("Active " + role + " account required");
}
if (account.status() != AccountStatus.ACTIVE || account.role() != role) {
throw new DashboardAccessDeniedException("Active " + role + " account required");
}
return account;
}
}