// 1. ENTITIES
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String email;
private String name;
@Enumerated(EnumType.STRING)
private SubscriptionPlan plan;
private LocalDateTime planExpiryDate;
// Getters and setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public SubscriptionPlan getPlan() { return plan; }
public void setPlan(SubscriptionPlan plan) { this.plan = plan; }
public LocalDateTime getPlanExpiryDate() { return planExpiryDate; }
public void setPlanExpiryDate(LocalDateTime planExpiryDate) { this.planExpiryDate = planExpiryDate; }
}
@Entity
@Table(name = "projects")
public class Project {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
@ManyToOne
@JoinColumn(name = "owner_id")
private User owner;
@OneToMany(mappedBy = "project", cascade = CascadeType.ALL)
private List<Task> tasks = new ArrayList<>();
private LocalDateTime createdAt;
// Getters and setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public User getOwner() { return owner; }
public void setOwner(User owner) { this.owner = owner; }
public List<Task> getTasks() { return tasks; }
public void setTasks(List<Task> tasks) { this.tasks = tasks; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
}
// 2. ENUMS
public enum SubscriptionPlan {
BASIC(3, 10, false, false),
PREMIUM(10, 50, true, false),
ENTERPRISE(Integer.MAX_VALUE, Integer.MAX_VALUE, true, true);
private final int maxProjects;
private final int maxTasksPerProject;
private final boolean hasAdvancedReports;
private final boolean hasApiAccess;
SubscriptionPlan(int maxProjects, int maxTasksPerProject, boolean hasAdvancedReports, boolean hasApiAccess) {
this.maxProjects = maxProjects;
this.maxTasksPerProject = maxTasksPerProject;
this.hasAdvancedReports = hasAdvancedReports;
this.hasApiAccess = hasApiAccess;
}
public int getMaxProjects() { return maxProjects; }
public int getMaxTasksPerProject() { return maxTasksPerProject; }
public boolean hasAdvancedReports() { return hasAdvancedReports; }
public boolean hasApiAccess() { return hasApiAccess; }
}
// 3. CUSTOM ANNOTATIONS
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPlan {
SubscriptionPlan[] value();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresFeature {
String value();
}
// 4. ASPECT FOR PLAN CHECKING
@Aspect
@Component
public class PlanAccessAspect {
@Autowired
private UserService userService;
@Around("@annotation(requiresPlan)")
public Object checkPlanAccess(ProceedingJoinPoint joinPoint, RequiresPlan requiresPlan) throws Throwable {
User currentUser = userService.getCurrentUser();
if (currentUser == null) {
throw new UnauthorizedException("User not authenticated");
}
if (!Arrays.asList(requiresPlan.value()).contains(currentUser.getPlan())) {
throw new InsufficientPlanException("Your current plan doesn't support this feature. Please upgrade.");
}
return joinPoint.proceed();
}
@Around("@annotation(requiresFeature)")
public Object checkFeatureAccess(ProceedingJoinPoint joinPoint, RequiresFeature requiresFeature) throws Throwable {
User currentUser = userService.getCurrentUser();
if (currentUser == null) {
throw new UnauthorizedException("User not authenticated");
}
boolean hasFeature = checkFeatureAvailability(currentUser.getPlan(), requiresFeature.value());
if (!hasFeature) {
throw new InsufficientPlanException("Feature '" + requiresFeature.value() + "' is not available in your plan");
}
return joinPoint.proceed();
}
private boolean checkFeatureAvailability(SubscriptionPlan plan, String feature) {
switch (feature) {
case "advanced_reports":
return plan.hasAdvancedReports();
case "api_access":
return plan.hasApiAccess();
default:
return true;
}
}
}
// 5. SERVICES
@Service
@Transactional
public class ProjectService {
@Autowired
private ProjectRepository projectRepository;
@Autowired
private UserService userService;
public List<Project> getUserProjects() {
User currentUser = userService.getCurrentUser();
return projectRepository.findByOwner(currentUser);
}
public Project createProject(CreateProjectRequest request) {
User currentUser = userService.getCurrentUser();
// Check project limit based on plan
long currentProjectCount = projectRepository.countByOwner(currentUser);
if (currentProjectCount >= currentUser.getPlan().getMaxProjects()) {
throw new PlanLimitExceededException("You've reached the maximum number of projects for your plan");
}
Project project = new Project();
project.setName(request.getName());
project.setDescription(request.getDescription());
project.setOwner(currentUser);
project.setCreatedAt(LocalDateTime.now());
return projectRepository.save(project);
}
@RequiresFeature("advanced_reports")
public ProjectAnalytics getAdvancedAnalytics(Long projectId) {
Project project = projectRepository.findById(projectId)
.orElseThrow(() -> new ResourceNotFoundException("Project not found"));
// Generate advanced analytics
return ProjectAnalytics.builder()
.projectId(projectId)
.completionRate(calculateCompletionRate(project))
.productivityScore(calculateProductivityScore(project))
.timeEstimateAccuracy(calculateTimeAccuracy(project))
.build();
}
private double calculateCompletionRate(Project project) {
// Implementation for completion rate calculation
return 0.75; // Placeholder
}
private double calculateProductivityScore(Project project) {
// Implementation for productivity score calculation
return 0.85; // Placeholder
}
private double calculateTimeAccuracy(Project project) {
// Implementation for time accuracy calculation
return 0.92; // Placeholder
}
}
// 6. CONTROLLERS
@RestController
@RequestMapping("/api/projects")
@CrossOrigin(origins = "http://localhost:3000")
public class ProjectController {
@Autowired
private ProjectService projectService;
@GetMapping
public ResponseEntity<ApiResponse<List<Project>>> getProjects() {
List<Project> projects = projectService.getUserProjects();
ApiResponse<List<Project>> response = ApiResponse.<List<Project>>builder()
.data(projects)
.userContext(buildUserContext())
.build();
return ResponseEntity.ok(response);
}
@PostMapping
public ResponseEntity<ApiResponse<Project>> createProject(@RequestBody CreateProjectRequest request) {
Project project = projectService.createProject(request);
ApiResponse<Project> response = ApiResponse.<Project>builder()
.data(project)
.userContext(buildUserContext())
.build();
return ResponseEntity.ok(response);
}
@GetMapping("/{projectId}/analytics")
@RequiresFeature("advanced_reports")
public ResponseEntity<ApiResponse<ProjectAnalytics>> getAnalytics(@PathVariable Long projectId) {
ProjectAnalytics analytics = projectService.getAdvancedAnalytics(projectId);
ApiResponse<ProjectAnalytics> response = ApiResponse.<ProjectAnalytics>builder()
.data(analytics)
.userContext(buildUserContext())
.build();
return ResponseEntity.ok(response);
}
private UserContext buildUserContext() {
User currentUser = userService.getCurrentUser();
SubscriptionPlan plan = currentUser.getPlan();
return UserContext.builder()
.plan(plan.name())
.planLimitations(PlanLimitations.builder()
.maxProjects(plan.getMaxProjects())
.maxTasksPerProject(plan.getMaxTasksPerProject())
.currentProjects(projectService.getUserProjects().size())
.build())
.availableFeatures(getAvailableFeatures(plan))
.build();
}
private List<String> getAvailableFeatures(SubscriptionPlan plan) {
List<String> features = new ArrayList<>();
features.add("basic_dashboard");
if (plan.hasAdvancedReports()) {
features.add("advanced_reports");
}
if (plan.hasApiAccess()) {
features.add("api_access");
}
return features;
}
}
// 7. RESPONSE CLASSES
@Data
@Builder
public class ApiResponse<T> {
private T data;
private UserContext userContext;
private String message;
private boolean success = true;
}
@Data
@Builder
public class UserContext {
private String plan;
private List<String> availableFeatures;
private PlanLimitations planLimitations;
}
@Data
@Builder
public class PlanLimitations {
private int maxProjects;
private int maxTasksPerProject;
private int currentProjects;
private int currentTasks;
}
@Data
@Builder
public class ProjectAnalytics {
private Long projectId;
private double completionRate;
private double productivityScore;
private double timeEstimateAccuracy;
}
// 8. EXCEPTION HANDLING
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(InsufficientPlanException.class)
public ResponseEntity<ErrorResponse> handleInsufficientPlan(InsufficientPlanException ex) {
ErrorResponse error = ErrorResponse.builder()
.message(ex.getMessage())
.code("INSUFFICIENT_PLAN")
.upgradeRequired(true)
.build();
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(error);
}
@ExceptionHandler(PlanLimitExceededException.class)
public ResponseEntity<ErrorResponse> handlePlanLimitExceeded(PlanLimitExceededException ex) {
ErrorResponse error = ErrorResponse.builder()
.message(ex.getMessage())
.code("PLAN_LIMIT_EXCEEDED")
.upgradeRequired(true)
.build();
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(error);
}
}
@Data
@Builder
public class ErrorResponse {
private String message;
private String code;
private boolean upgradeRequired;
}