Content is user-generated and unverified.
// 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; }
Content is user-generated and unverified.
    Spring Boot Backend - Plan-Based Access Control | Claude