diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 0000000..3271bbf --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,32 @@ +name: CD - Build Artifact + +on: + push: + branches: [ main ] + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Set up JDK 25 + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: '25' + cache: 'maven' + check-latest: true + + - name: Build JAR + run: mvn package -DskipTests + + - name: Upload JAR artifact + uses: actions/upload-artifact@v7 + with: + name: team6-backend + path: target/*.jar + retention-days: 7 \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b998d4f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,36 @@ +name: CI - Build and Test + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Set up JDK 25 + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: '25' + cache: 'maven' + check-latest: true + + - name: Check code formatting with Spotless + run: mvn spotless:check + + - name: Run tests with Maven + run: mvn test + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v7 + with: + name: test-reports + path: target/surefire-reports/ \ No newline at end of file diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml new file mode 100644 index 0000000..b630d91 --- /dev/null +++ b/.github/workflows/pr-checks.yml @@ -0,0 +1,78 @@ +name: PR - Quality Checks + +on: + pull_request: + branches: [ main ] + types: [ opened, synchronize, reopened, ready_for_review ] + +jobs: + quality-checks: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Set up JDK 25 + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: '25' + cache: 'maven' + check-latest: true + + - name: Check code formatting with Spotless + run: mvn spotless:check + + - name: Run static code analysis with PMD + run: mvn pmd:check + continue-on-error: true + + # - name: Check for vulnerabilities + # run: mvn dependency-check:check + # continue-on-error: true + + - name: Run all tests with coverage + run: mvn test jacoco:report + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v7 + with: + name: test-reports + path: target/surefire-reports/ + + - name: Upload coverage report + if: always() + uses: actions/upload-artifact@v7 + with: + name: coverage-report + path: target/site/jacoco/ + + - name: Comment PR with coverage summary + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + try { + const coverage = fs.readFileSync('target/site/jacoco/index.html', 'utf8'); + const match = coverage.match(/Total<\/td>]*>(\d+)%<\/td>/); + if (match) { + const coveragePercent = match[1]; + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + body: `## Test Coverage Report\n\n**Coverage: ${coveragePercent}%**\n\nQuality checks completed.\n\nFull coverage report available in workflow artifacts.` + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + body: `## Test Coverage Report\n\nCoverage report generated but could not parse percentage. Check artifacts for details.` + }); + } + } catch(e) { + console.log('Could not parse coverage report:', e.message); + } \ No newline at end of file diff --git a/src/main/java/org/example/team6backend/Team6BackendApplication.java b/src/main/java/org/example/team6backend/Team6BackendApplication.java index 953000c..84b2e4f 100644 --- a/src/main/java/org/example/team6backend/Team6BackendApplication.java +++ b/src/main/java/org/example/team6backend/Team6BackendApplication.java @@ -6,7 +6,7 @@ @SpringBootApplication public class Team6BackendApplication { - public static void main(String[] args) { - SpringApplication.run(Team6BackendApplication.class, args); - } -} \ No newline at end of file + public static void main(String[] args) { + SpringApplication.run(Team6BackendApplication.class, args); + } +} diff --git a/src/main/java/org/example/team6backend/admin/AdminController.java b/src/main/java/org/example/team6backend/admin/AdminController.java index 3160cc7..e0b7979 100644 --- a/src/main/java/org/example/team6backend/admin/AdminController.java +++ b/src/main/java/org/example/team6backend/admin/AdminController.java @@ -29,122 +29,104 @@ @Slf4j public class AdminController { - private final UserService userService; - private final UserMapper userMapper; - - @GetMapping("/users") - public ResponseEntity> getUsers( - @RequestParam(required = false) String email, - @RequestParam(required = false) String name, - @RequestParam(required = false) UserRole role, - @RequestParam(required = false) Boolean active, - @RequestParam(required = false) String search, - @PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable) { - - Page users = resolveUsers(email, name, role, active, search, pageable); - return ResponseEntity.ok(userMapper.toResponsePage(users)); - } - - @GetMapping("/users/pending") - public ResponseEntity> getPendingUsers( - @PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable) { - return ResponseEntity.ok( - userMapper.toResponsePage(userService.getUsersByRolePaginated(UserRole.PENDING, pageable)) - ); - } - - @GetMapping("/users/{userId}") - public ResponseEntity getUser(@PathVariable String userId) { - return ResponseEntity.ok(userMapper.toResponse(userService.getUserById(userId))); - } - - @PostMapping("/users/{userId}/approve") - public ResponseEntity approveUser(@PathVariable String userId) { - AppUser approvedUser = userService.approvePendingUser(userId); - return ResponseEntity.ok(userMapper.toResponse(approvedUser)); - } - - @PatchMapping("/users/{userId}/role") - public ResponseEntity updateUserRole( - @PathVariable String userId, - @Valid @RequestBody UpdateUserRoleRequest request, - @AuthenticationPrincipal CustomUserDetails currentUser) { - - if (currentUser.getUser().getId().equals(userId)) { - throw new IllegalStateException("You cannot change your own role"); - } - - if (request.role() != UserRole.ADMIN) { - AppUser targetUser = userService.getUserById(userId); - if (targetUser.getRole() == UserRole.ADMIN) { - long adminCount = userService.getAllUsers().stream() - .filter(u -> u.getRole() == UserRole.ADMIN) - .count(); - if (adminCount <= 1) { - throw new IllegalStateException("Cannot remove the last admin user"); - } - } - } - - AppUser updatedUser = userService.updateUserRole(userId, request.role()); - return ResponseEntity.ok(userMapper.toResponse(updatedUser)); - } - - @PatchMapping("/users/{userId}/status") - public ResponseEntity updateUserStatus( - @PathVariable String userId, - @Valid @RequestBody UpdateUserStatusRequest request, - @AuthenticationPrincipal CustomUserDetails currentUser) { - - if (currentUser.getUser().getId().equals(userId) && !request.active()) { - throw new IllegalStateException("You cannot deactivate your own account"); - } - - AppUser updatedUser = userService.updateUserActiveStatus(userId, request.active()); - return ResponseEntity.ok(userMapper.toResponse(updatedUser)); - } - - @DeleteMapping("/users/{userId}") - public ResponseEntity deleteUser( - @PathVariable String userId, - @AuthenticationPrincipal CustomUserDetails currentUser) { - - if (currentUser.getUser().getId().equals(userId)) { - throw new IllegalStateException("You cannot delete your own account"); - } - - userService.deleteUser(userId); - return ResponseEntity.noContent().build(); - } - - @GetMapping("/stats") - public ResponseEntity> getStats() { - Map stats = Map.of( - "totalUsers", (long) userService.getAllUsers().size(), - "pendingUsers", (long) userService.getUsersByRole(UserRole.PENDING).size(), - "residents", (long) userService.getUsersByRole(UserRole.RESIDENT).size(), - "handlers", (long) userService.getUsersByRole(UserRole.HANDLER).size(), - "admins", (long) userService.getUsersByRole(UserRole.ADMIN).size() - ); - return ResponseEntity.ok(stats); - } - - private Page resolveUsers( - String email, - String name, - UserRole role, - Boolean active, - String search, - Pageable pageable - ) { - if (search != null && !search.trim().isEmpty()) { - return userService.searchUsers(search, pageable); - } - - if (email != null || name != null || role != null || active != null) { - return userService.getUsersWithFilters(email, name, role, active, pageable); - } - - return userService.getAllUsersPaginated(pageable); - } -} \ No newline at end of file + private final UserService userService; + private final UserMapper userMapper; + + @GetMapping("/users") + public ResponseEntity> getUsers(@RequestParam(required = false) String email, + @RequestParam(required = false) String name, @RequestParam(required = false) UserRole role, + @RequestParam(required = false) Boolean active, @RequestParam(required = false) String search, + @PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable) { + + Page users = resolveUsers(email, name, role, active, search, pageable); + return ResponseEntity.ok(userMapper.toResponsePage(users)); + } + + @GetMapping("/users/pending") + public ResponseEntity> getPendingUsers( + @PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable) { + return ResponseEntity + .ok(userMapper.toResponsePage(userService.getUsersByRolePaginated(UserRole.PENDING, pageable))); + } + + @GetMapping("/users/{userId}") + public ResponseEntity getUser(@PathVariable String userId) { + return ResponseEntity.ok(userMapper.toResponse(userService.getUserById(userId))); + } + + @PostMapping("/users/{userId}/approve") + public ResponseEntity approveUser(@PathVariable String userId) { + AppUser approvedUser = userService.approvePendingUser(userId); + return ResponseEntity.ok(userMapper.toResponse(approvedUser)); + } + + @PatchMapping("/users/{userId}/role") + public ResponseEntity updateUserRole(@PathVariable String userId, + @Valid @RequestBody UpdateUserRoleRequest request, @AuthenticationPrincipal CustomUserDetails currentUser) { + + if (currentUser.getUser().getId().equals(userId)) { + throw new IllegalStateException("You cannot change your own role"); + } + + if (request.role() != UserRole.ADMIN) { + AppUser targetUser = userService.getUserById(userId); + if (targetUser.getRole() == UserRole.ADMIN) { + long adminCount = userService.getAllUsers().stream().filter(u -> u.getRole() == UserRole.ADMIN).count(); + if (adminCount <= 1) { + throw new IllegalStateException("Cannot remove the last admin user"); + } + } + } + + AppUser updatedUser = userService.updateUserRole(userId, request.role()); + return ResponseEntity.ok(userMapper.toResponse(updatedUser)); + } + + @PatchMapping("/users/{userId}/status") + public ResponseEntity updateUserStatus(@PathVariable String userId, + @Valid @RequestBody UpdateUserStatusRequest request, + @AuthenticationPrincipal CustomUserDetails currentUser) { + + if (currentUser.getUser().getId().equals(userId) && !request.active()) { + throw new IllegalStateException("You cannot deactivate your own account"); + } + + AppUser updatedUser = userService.updateUserActiveStatus(userId, request.active()); + return ResponseEntity.ok(userMapper.toResponse(updatedUser)); + } + + @DeleteMapping("/users/{userId}") + public ResponseEntity deleteUser(@PathVariable String userId, + @AuthenticationPrincipal CustomUserDetails currentUser) { + + if (currentUser.getUser().getId().equals(userId)) { + throw new IllegalStateException("You cannot delete your own account"); + } + + userService.deleteUser(userId); + return ResponseEntity.noContent().build(); + } + + @GetMapping("/stats") + public ResponseEntity> getStats() { + Map stats = Map.of("totalUsers", (long) userService.getAllUsers().size(), "pendingUsers", + (long) userService.getUsersByRole(UserRole.PENDING).size(), "residents", + (long) userService.getUsersByRole(UserRole.RESIDENT).size(), "handlers", + (long) userService.getUsersByRole(UserRole.HANDLER).size(), "admins", + (long) userService.getUsersByRole(UserRole.ADMIN).size()); + return ResponseEntity.ok(stats); + } + + private Page resolveUsers(String email, String name, UserRole role, Boolean active, String search, + Pageable pageable) { + if (search != null && !search.trim().isEmpty()) { + return userService.searchUsers(search, pageable); + } + + if (email != null || name != null || role != null || active != null) { + return userService.getUsersWithFilters(email, name, role, active, pageable); + } + + return userService.getAllUsersPaginated(pageable); + } +} diff --git a/src/main/java/org/example/team6backend/admin/dto/UpdateUserRoleRequest.java b/src/main/java/org/example/team6backend/admin/dto/UpdateUserRoleRequest.java index c5ff8a6..1dc0468 100644 --- a/src/main/java/org/example/team6backend/admin/dto/UpdateUserRoleRequest.java +++ b/src/main/java/org/example/team6backend/admin/dto/UpdateUserRoleRequest.java @@ -3,8 +3,5 @@ import jakarta.validation.constraints.NotNull; import org.example.team6backend.user.entity.UserRole; -public record UpdateUserRoleRequest( - @NotNull(message = "Role is required") - UserRole role -) { -} \ No newline at end of file +public record UpdateUserRoleRequest(@NotNull(message = "Role is required") UserRole role) { +} diff --git a/src/main/java/org/example/team6backend/admin/dto/UpdateUserStatusRequest.java b/src/main/java/org/example/team6backend/admin/dto/UpdateUserStatusRequest.java index 8dfc34d..39104d2 100644 --- a/src/main/java/org/example/team6backend/admin/dto/UpdateUserStatusRequest.java +++ b/src/main/java/org/example/team6backend/admin/dto/UpdateUserStatusRequest.java @@ -2,8 +2,5 @@ import jakarta.validation.constraints.NotNull; -public record UpdateUserStatusRequest( - @NotNull(message = "Active status is required") - Boolean active -) { -} \ No newline at end of file +public record UpdateUserStatusRequest(@NotNull(message = "Active status is required") Boolean active) { +} diff --git a/src/main/java/org/example/team6backend/comment/controller/CommentController.java b/src/main/java/org/example/team6backend/comment/controller/CommentController.java index 10e199a..3ca92bc 100644 --- a/src/main/java/org/example/team6backend/comment/controller/CommentController.java +++ b/src/main/java/org/example/team6backend/comment/controller/CommentController.java @@ -13,25 +13,22 @@ @RequestMapping("/comments") public class CommentController { - private final CommentService commentService; + private final CommentService commentService; - public CommentController(CommentService commentService) { - this.commentService = commentService; - } + public CommentController(CommentService commentService) { + this.commentService = commentService; + } - @GetMapping("/incident/{incidentId}") - public ResponseEntity> getCommentByIncidentId(@PathVariable Long incidentId) { - List comments = commentService.getCommentByIncidentId(incidentId); - return ResponseEntity.ok(comments); - } + @GetMapping("/incident/{incidentId}") + public ResponseEntity> getCommentByIncidentId(@PathVariable Long incidentId) { + List comments = commentService.getCommentByIncidentId(incidentId); + return ResponseEntity.ok(comments); + } - @PostMapping - public ResponseEntity createComment(@Valid @RequestBody CommentRequest request) { - Comment saveComment = commentService.createComment( - request.getIncidentId(), - request.getUserId(), - request.getMessage() - ); - return ResponseEntity.ok(saveComment); - } + @PostMapping + public ResponseEntity createComment(@Valid @RequestBody CommentRequest request) { + Comment saveComment = commentService.createComment(request.getIncidentId(), request.getUserId(), + request.getMessage()); + return ResponseEntity.ok(saveComment); + } } diff --git a/src/main/java/org/example/team6backend/comment/dto/CommentRequest.java b/src/main/java/org/example/team6backend/comment/dto/CommentRequest.java index 8a7e2f5..981a8b6 100644 --- a/src/main/java/org/example/team6backend/comment/dto/CommentRequest.java +++ b/src/main/java/org/example/team6backend/comment/dto/CommentRequest.java @@ -5,34 +5,34 @@ public class CommentRequest { - @NotNull - private Long incidentId; - @NotBlank - private String userId; - @NotBlank - private String message; - - public Long getIncidentId() { - return incidentId; - } - - public void setIncidentId(Long incidentId) { - this.incidentId = incidentId; - } - - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } - - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } + @NotNull + private Long incidentId; + @NotBlank + private String userId; + @NotBlank + private String message; + + public Long getIncidentId() { + return incidentId; + } + + public void setIncidentId(Long incidentId) { + this.incidentId = incidentId; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } } diff --git a/src/main/java/org/example/team6backend/comment/entity/Comment.java b/src/main/java/org/example/team6backend/comment/entity/Comment.java index 374dfb7..17d2b2b 100644 --- a/src/main/java/org/example/team6backend/comment/entity/Comment.java +++ b/src/main/java/org/example/team6backend/comment/entity/Comment.java @@ -14,27 +14,27 @@ @NoArgsConstructor public class Comment { - @Id - @GeneratedValue(strategy = GenerationType.UUID) - private String id; + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private String id; - @Column(nullable = false, columnDefinition = "TEXT") - private String message; + @Column(nullable = false, columnDefinition = "TEXT") + private String message; - @ManyToOne - @JoinColumn(name = "incident_id", nullable = false) - private Incident incident; + @ManyToOne + @JoinColumn(name = "incident_id", nullable = false) + private Incident incident; - @ManyToOne - @JoinColumn(name = "user_id", nullable = false) - private AppUser user; + @ManyToOne + @JoinColumn(name = "user_id", nullable = false) + private AppUser user; - @Column(name = "created_at", nullable = false) - private LocalDateTime createAt; + @Column(name = "created_at", nullable = false) + private LocalDateTime createAt; - @PrePersist - protected void onCreate(){ - createAt = LocalDateTime.now(); - } + @PrePersist + protected void onCreate() { + createAt = LocalDateTime.now(); + } } diff --git a/src/main/java/org/example/team6backend/comment/repository/CommentRepository.java b/src/main/java/org/example/team6backend/comment/repository/CommentRepository.java index 746181f..a92b5b8 100644 --- a/src/main/java/org/example/team6backend/comment/repository/CommentRepository.java +++ b/src/main/java/org/example/team6backend/comment/repository/CommentRepository.java @@ -7,5 +7,5 @@ public interface CommentRepository extends JpaRepository { - List findByIncidentId(Long incidentId); + List findByIncidentId(Long incidentId); } diff --git a/src/main/java/org/example/team6backend/comment/service/CommentService.java b/src/main/java/org/example/team6backend/comment/service/CommentService.java index 6da412e..e0c822c 100644 --- a/src/main/java/org/example/team6backend/comment/service/CommentService.java +++ b/src/main/java/org/example/team6backend/comment/service/CommentService.java @@ -14,34 +14,33 @@ @Service public class CommentService { - private final CommentRepository commentRepository; - private final IncidentRepository incidentRepository; - private final AppUserRepository appUserRepository; - - public CommentService(CommentRepository commentRepository, - IncidentRepository incidentRepository, - AppUserRepository appUserRepository) { - this.commentRepository = commentRepository; - this.incidentRepository = incidentRepository; - this.appUserRepository = appUserRepository; - } - - public List getCommentByIncidentId(Long incidentId) { - return commentRepository.findByIncidentId(incidentId); - } - - public Comment createComment(Long incidentId, String userId, String message) { - Incident incident = incidentRepository.findById(incidentId) - .orElseThrow(() -> new ResourceNotFoundException("Incident not found")); - - AppUser user = appUserRepository.findById(userId) - .orElseThrow(() -> new ResourceNotFoundException("User not found")); - - Comment comment = new Comment(); - comment.setIncident(incident); - comment.setUser(user); - comment.setMessage(message); - - return commentRepository.save(comment); - } + private final CommentRepository commentRepository; + private final IncidentRepository incidentRepository; + private final AppUserRepository appUserRepository; + + public CommentService(CommentRepository commentRepository, IncidentRepository incidentRepository, + AppUserRepository appUserRepository) { + this.commentRepository = commentRepository; + this.incidentRepository = incidentRepository; + this.appUserRepository = appUserRepository; + } + + public List getCommentByIncidentId(Long incidentId) { + return commentRepository.findByIncidentId(incidentId); + } + + public Comment createComment(Long incidentId, String userId, String message) { + Incident incident = incidentRepository.findById(incidentId) + .orElseThrow(() -> new ResourceNotFoundException("Incident not found")); + + AppUser user = appUserRepository.findById(userId) + .orElseThrow(() -> new ResourceNotFoundException("User not found")); + + Comment comment = new Comment(); + comment.setIncident(incident); + comment.setUser(user); + comment.setMessage(message); + + return commentRepository.save(comment); + } } diff --git a/src/main/java/org/example/team6backend/config/SecurityConfig.java b/src/main/java/org/example/team6backend/config/SecurityConfig.java index 02755d5..16c79aa 100644 --- a/src/main/java/org/example/team6backend/config/SecurityConfig.java +++ b/src/main/java/org/example/team6backend/config/SecurityConfig.java @@ -15,53 +15,24 @@ @RequiredArgsConstructor public class SecurityConfig { - private final CustomOAuth2UserService customOAuth2UserService; + private final CustomOAuth2UserService customOAuth2UserService; - @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - http - .authorizeHttpRequests(auth -> auth - .requestMatchers( - "/", - "/index", - "/demo", - "/error", - "/login/**", - "/oauth2/**" - ).permitAll() - .requestMatchers( - "/dashboard", - "/profile" - ).authenticated() - .requestMatchers( - "/incidents", - "/api/incidents/**" - ).hasAnyRole("RESIDENT", "HANDLER", "ADMIN") - .requestMatchers( - "/admin", - "/api/admin/**" - ).hasRole("ADMIN") - .requestMatchers( - "/swagger-ui.html", - "/swagger-ui/**", - "/v3/api-docs/**" - ).hasRole("ADMIN") - .anyRequest().authenticated() - ) - .oauth2Login(oauth2 -> oauth2 - .userInfoEndpoint(userInfo -> userInfo.userService(customOAuth2UserService)) - .defaultSuccessUrl("/dashboard", true) - ) - .logout(logout -> logout - .logoutSuccessUrl("/") - .invalidateHttpSession(true) - .clearAuthentication(true) - .deleteCookies("JSESSIONID") - ) - .csrf(csrf -> csrf - .ignoringRequestMatchers("/api/admin/**") - ); + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http.authorizeHttpRequests( + auth -> auth.requestMatchers("/", "/index", "/demo", "/error", "/login/**", "/oauth2/**").permitAll() + .requestMatchers("/dashboard", "/profile").authenticated() + .requestMatchers("/incidents", "/api/incidents/**").hasAnyRole("RESIDENT", "HANDLER", "ADMIN") + .requestMatchers("/admin", "/api/admin/**").hasRole("ADMIN") + .requestMatchers("/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**").hasRole("ADMIN") + .anyRequest().authenticated()) + .oauth2Login( + oauth2 -> oauth2.userInfoEndpoint(userInfo -> userInfo.userService(customOAuth2UserService)) + .defaultSuccessUrl("/dashboard", true)) + .logout(logout -> logout.logoutSuccessUrl("/").invalidateHttpSession(true).clearAuthentication(true) + .deleteCookies("JSESSIONID")) + .csrf(csrf -> csrf.ignoringRequestMatchers("/api/admin/**")); - return http.build(); - } -} \ No newline at end of file + return http.build(); + } +} diff --git a/src/main/java/org/example/team6backend/exception/ErrorResponse.java b/src/main/java/org/example/team6backend/exception/ErrorResponse.java index 525fb8f..8055428 100644 --- a/src/main/java/org/example/team6backend/exception/ErrorResponse.java +++ b/src/main/java/org/example/team6backend/exception/ErrorResponse.java @@ -3,23 +3,23 @@ public class ErrorResponse { - private int status; - private String message; - private LocalDateTime timestamp; + private int status; + private String message; + private LocalDateTime timestamp; - public ErrorResponse(int status, String message, LocalDateTime timestamp) { - this.status = status; - this.message = message; - this.timestamp = timestamp; - } - public int getStatus() { - return status; - } + public ErrorResponse(int status, String message, LocalDateTime timestamp) { + this.status = status; + this.message = message; + this.timestamp = timestamp; + } + public int getStatus() { + return status; + } - public String getMessage() { - return message; - } - public LocalDateTime getTimestamp() { - return timestamp; - } + public String getMessage() { + return message; + } + public LocalDateTime getTimestamp() { + return timestamp; + } } diff --git a/src/main/java/org/example/team6backend/exception/GlobalExceptionHandler.java b/src/main/java/org/example/team6backend/exception/GlobalExceptionHandler.java index dda35ee..62fca04 100644 --- a/src/main/java/org/example/team6backend/exception/GlobalExceptionHandler.java +++ b/src/main/java/org/example/team6backend/exception/GlobalExceptionHandler.java @@ -11,54 +11,36 @@ @RestControllerAdvice public class GlobalExceptionHandler { - @ExceptionHandler(AccessDeniedException.class) - public ResponseEntity handleAccessDeniedException(AccessDeniedException ex) { - ErrorResponse error = new ErrorResponse( - HttpStatus.FORBIDDEN.value(), - "You do not have permission to access this resource!", - LocalDateTime.now() - ); - return new ResponseEntity<>(error, HttpStatus.FORBIDDEN); - } - - @ExceptionHandler(ResourceNotFoundException.class) - public ResponseEntity handleNotFound(ResourceNotFoundException ex) { - ErrorResponse error = new ErrorResponse( - HttpStatus.NOT_FOUND.value(), - ex.getMessage(), - LocalDateTime.now() - ); - return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); - } - - @ExceptionHandler(IllegalArgumentException.class) - public ResponseEntity handleIllegalArgument(IllegalArgumentException ex) { - ErrorResponse error = new ErrorResponse( - HttpStatus.BAD_REQUEST.value(), - ex.getMessage(), - LocalDateTime.now() - ); - return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); - } - - @ExceptionHandler(IllegalStateException.class) - public ResponseEntity handleIllegalState(IllegalStateException ex) { - ErrorResponse error = new ErrorResponse( - HttpStatus.BAD_REQUEST.value(), - ex.getMessage(), - LocalDateTime.now() - ); - return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); - } - - @ExceptionHandler(Exception.class) - public ResponseEntity handleGeneral(Exception ex) { - ex.printStackTrace(); - ErrorResponse error = new ErrorResponse( - HttpStatus.INTERNAL_SERVER_ERROR.value(), - "Something went wrong!", - LocalDateTime.now() - ); - return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); - } -} \ No newline at end of file + @ExceptionHandler(AccessDeniedException.class) + public ResponseEntity handleAccessDeniedException(AccessDeniedException ex) { + ErrorResponse error = new ErrorResponse(HttpStatus.FORBIDDEN.value(), + "You do not have permission to access this resource!", LocalDateTime.now()); + return new ResponseEntity<>(error, HttpStatus.FORBIDDEN); + } + + @ExceptionHandler(ResourceNotFoundException.class) + public ResponseEntity handleNotFound(ResourceNotFoundException ex) { + ErrorResponse error = new ErrorResponse(HttpStatus.NOT_FOUND.value(), ex.getMessage(), LocalDateTime.now()); + return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); + } + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleIllegalArgument(IllegalArgumentException ex) { + ErrorResponse error = new ErrorResponse(HttpStatus.BAD_REQUEST.value(), ex.getMessage(), LocalDateTime.now()); + return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(IllegalStateException.class) + public ResponseEntity handleIllegalState(IllegalStateException ex) { + ErrorResponse error = new ErrorResponse(HttpStatus.BAD_REQUEST.value(), ex.getMessage(), LocalDateTime.now()); + return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleGeneral(Exception ex) { + ex.printStackTrace(); + ErrorResponse error = new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Something went wrong!", + LocalDateTime.now()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } +} diff --git a/src/main/java/org/example/team6backend/exception/ResourceNotFoundException.java b/src/main/java/org/example/team6backend/exception/ResourceNotFoundException.java index 8d0b05b..5117c66 100644 --- a/src/main/java/org/example/team6backend/exception/ResourceNotFoundException.java +++ b/src/main/java/org/example/team6backend/exception/ResourceNotFoundException.java @@ -1,7 +1,7 @@ package org.example.team6backend.exception; -public class ResourceNotFoundException extends RuntimeException{ - public ResourceNotFoundException(String message) { - super(message); - } +public class ResourceNotFoundException extends RuntimeException { + public ResourceNotFoundException(String message) { + super(message); + } } diff --git a/src/main/java/org/example/team6backend/exception/UserNotFoundException.java b/src/main/java/org/example/team6backend/exception/UserNotFoundException.java index 540cd3f..3d273a1 100644 --- a/src/main/java/org/example/team6backend/exception/UserNotFoundException.java +++ b/src/main/java/org/example/team6backend/exception/UserNotFoundException.java @@ -2,7 +2,7 @@ public class UserNotFoundException extends RuntimeException { - public UserNotFoundException(String userId) { - super("User not found with id: " + userId); - } -} \ No newline at end of file + public UserNotFoundException(String userId) { + super("User not found with id: " + userId); + } +} diff --git a/src/main/java/org/example/team6backend/incident/controller/IncidentController.java b/src/main/java/org/example/team6backend/incident/controller/IncidentController.java index 787dacf..d995f1f 100644 --- a/src/main/java/org/example/team6backend/incident/controller/IncidentController.java +++ b/src/main/java/org/example/team6backend/incident/controller/IncidentController.java @@ -10,49 +10,45 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; - @RestController @RequestMapping("/api/incidents") public class IncidentController { - private final IncidentService incidentService; - - public IncidentController(IncidentService incidentService) { - this.incidentService = incidentService; - } - - /**Create new incident*/ - @PostMapping - @PreAuthorize("hasAnyRole('RESIDENT', 'ADMIN')") - public IncidentResponse createIncident(@Valid @RequestBody IncidentRequest incidentRequest) { - Incident incident = new Incident(); - incident.setSubject(incidentRequest.getSubject()); - incident.setDescription(incidentRequest.getDescription()); - incident.setIncidentCategory(incidentRequest.getIncidentCategory()); - - Incident saved = incidentService.createIncident(incident); - return IncidentResponse.fromEntity(saved); - } - - /**Get my incidents(user)*/ - @PreAuthorize("hasRole('RESIDENT')") - @GetMapping ("/my") - public Page getMyIncidents(Pageable pageable){ - return incidentService.findByCreatedBy(pageable) - .map(IncidentResponse::fromEntity); - } - - @PreAuthorize("hasRole('HANDLER')") - @GetMapping ("/assigned") - public Page getAssignedIncidents(Pageable pageable){ - return incidentService.findByAssignedTo(pageable) - .map(IncidentResponse::fromEntity); - } - - @PreAuthorize("hasRole('ADMIN')") - @GetMapping("/all") - public Page getAllIncidents(Pageable pageable) { - return incidentService.findAll(pageable) - .map(IncidentResponse::fromEntity); - } + private final IncidentService incidentService; + + public IncidentController(IncidentService incidentService) { + this.incidentService = incidentService; + } + + /** Create new incident */ + @PostMapping + @PreAuthorize("hasAnyRole('RESIDENT', 'ADMIN')") + public IncidentResponse createIncident(@Valid @RequestBody IncidentRequest incidentRequest) { + Incident incident = new Incident(); + incident.setSubject(incidentRequest.getSubject()); + incident.setDescription(incidentRequest.getDescription()); + incident.setIncidentCategory(incidentRequest.getIncidentCategory()); + + Incident saved = incidentService.createIncident(incident); + return IncidentResponse.fromEntity(saved); + } + + /** Get my incidents(user) */ + @PreAuthorize("hasRole('RESIDENT')") + @GetMapping("/my") + public Page getMyIncidents(Pageable pageable) { + return incidentService.findByCreatedBy(pageable).map(IncidentResponse::fromEntity); + } + + @PreAuthorize("hasRole('HANDLER')") + @GetMapping("/assigned") + public Page getAssignedIncidents(Pageable pageable) { + return incidentService.findByAssignedTo(pageable).map(IncidentResponse::fromEntity); + } + + @PreAuthorize("hasRole('ADMIN')") + @GetMapping("/all") + public Page getAllIncidents(Pageable pageable) { + return incidentService.findAll(pageable).map(IncidentResponse::fromEntity); + } } diff --git a/src/main/java/org/example/team6backend/incident/dto/IncidentRequest.java b/src/main/java/org/example/team6backend/incident/dto/IncidentRequest.java index 51d3cc4..a00eac9 100644 --- a/src/main/java/org/example/team6backend/incident/dto/IncidentRequest.java +++ b/src/main/java/org/example/team6backend/incident/dto/IncidentRequest.java @@ -8,9 +8,9 @@ @Data public class IncidentRequest { - @NotBlank (message="Subject is required") - private String subject; - private String description; - @NotNull(message="Incident category is required") - private IncidentCategory incidentCategory; + @NotBlank(message = "Subject is required") + private String subject; + private String description; + @NotNull(message = "Incident category is required") + private IncidentCategory incidentCategory; } diff --git a/src/main/java/org/example/team6backend/incident/dto/IncidentResponse.java b/src/main/java/org/example/team6backend/incident/dto/IncidentResponse.java index 22daf16..8a608c7 100644 --- a/src/main/java/org/example/team6backend/incident/dto/IncidentResponse.java +++ b/src/main/java/org/example/team6backend/incident/dto/IncidentResponse.java @@ -10,34 +10,30 @@ @Data public class IncidentResponse { - private Long id; - private String subject; - private String description; - private IncidentStatus incidentStatus; - private IncidentCategory incidentCategory; - - private String createdBy; - private String assignedTo; - - private LocalDateTime createdAt; - - public static IncidentResponse fromEntity(Incident incident) { - IncidentResponse response = new IncidentResponse(); - - response.setId(incident.getId()); - response.setSubject(incident.getSubject()); - response.setDescription(incident.getDescription()); - response.setIncidentStatus(incident.getIncidentStatus()); - response.setIncidentCategory(incident.getIncidentCategory()); - response.setCreatedAt(incident.getCreatedAt()); - - response.setCreatedBy( - incident.getCreatedBy() != null ? incident.getCreatedBy().getEmail() : null - ); - - response.setAssignedTo( - incident.getAssignedTo() != null ? incident.getAssignedTo().getEmail() : null - ); - return response; - } + private Long id; + private String subject; + private String description; + private IncidentStatus incidentStatus; + private IncidentCategory incidentCategory; + + private String createdBy; + private String assignedTo; + + private LocalDateTime createdAt; + + public static IncidentResponse fromEntity(Incident incident) { + IncidentResponse response = new IncidentResponse(); + + response.setId(incident.getId()); + response.setSubject(incident.getSubject()); + response.setDescription(incident.getDescription()); + response.setIncidentStatus(incident.getIncidentStatus()); + response.setIncidentCategory(incident.getIncidentCategory()); + response.setCreatedAt(incident.getCreatedAt()); + + response.setCreatedBy(incident.getCreatedBy() != null ? incident.getCreatedBy().getEmail() : null); + + response.setAssignedTo(incident.getAssignedTo() != null ? incident.getAssignedTo().getEmail() : null); + return response; + } } diff --git a/src/main/java/org/example/team6backend/incident/entity/Incident.java b/src/main/java/org/example/team6backend/incident/entity/Incident.java index 1df8209..959e9f0 100644 --- a/src/main/java/org/example/team6backend/incident/entity/Incident.java +++ b/src/main/java/org/example/team6backend/incident/entity/Incident.java @@ -7,127 +7,127 @@ @Entity public class Incident { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; - private String subject; - private String description; + private String subject; + private String description; - @Column(name = "incident_category") - @Enumerated(EnumType.STRING) - private IncidentCategory incidentCategory; + @Column(name = "incident_category") + @Enumerated(EnumType.STRING) + private IncidentCategory incidentCategory; - @Column(name = "incident_status") - @Enumerated(EnumType.STRING) - private IncidentStatus incidentStatus; + @Column(name = "incident_status") + @Enumerated(EnumType.STRING) + private IncidentStatus incidentStatus; - @ManyToOne - @JoinColumn(name = "created_by_id") - private AppUser createdBy; + @ManyToOne + @JoinColumn(name = "created_by_id") + private AppUser createdBy; - @ManyToOne - @JoinColumn(name = "modified_by_id") - private AppUser modifiedBy; + @ManyToOne + @JoinColumn(name = "modified_by_id") + private AppUser modifiedBy; - @ManyToOne - @JoinColumn(name = "assigned_to_id") - private AppUser assignedTo; + @ManyToOne + @JoinColumn(name = "assigned_to_id") + private AppUser assignedTo; - @Column(name = "created_at") - private LocalDateTime createdAt; + @Column(name = "created_at") + private LocalDateTime createdAt; - @Column(name = "updated_at") - private LocalDateTime updatedAt; + @Column(name = "updated_at") + private LocalDateTime updatedAt; - @PrePersist - protected void onCreate() { - createdAt = LocalDateTime.now(); - updatedAt = LocalDateTime.now(); - } + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + updatedAt = LocalDateTime.now(); + } - @PreUpdate - protected void onUpdate() { - updatedAt = LocalDateTime.now(); - } + @PreUpdate + protected void onUpdate() { + updatedAt = LocalDateTime.now(); + } - public Long getId() { - return id; - } + public Long getId() { + return id; + } - public String getSubject() { - return subject; - } + public String getSubject() { + return subject; + } - public String getDescription() { - return description; - } + public String getDescription() { + return description; + } - public IncidentCategory getIncidentCategory() { - return incidentCategory; - } + public IncidentCategory getIncidentCategory() { + return incidentCategory; + } - public IncidentStatus getIncidentStatus() { - return incidentStatus; - } + public IncidentStatus getIncidentStatus() { + return incidentStatus; + } - public AppUser getCreatedBy() { - return createdBy; - } + public AppUser getCreatedBy() { + return createdBy; + } - public AppUser getModifiedBy() { - return modifiedBy; - } + public AppUser getModifiedBy() { + return modifiedBy; + } - public AppUser getAssignedTo() { - return assignedTo; - } + public AppUser getAssignedTo() { + return assignedTo; + } - public LocalDateTime getCreatedAt() { - return createdAt; - } + public LocalDateTime getCreatedAt() { + return createdAt; + } - public LocalDateTime getUpdatedAt() { - return updatedAt; - } + public LocalDateTime getUpdatedAt() { + return updatedAt; + } - public void setId(Long id) { - this.id = id; - } + public void setId(Long id) { + this.id = id; + } - public void setSubject(String subject) { - this.subject = subject; - } + public void setSubject(String subject) { + this.subject = subject; + } - public void setDescription(String description) { - this.description = description; - } + public void setDescription(String description) { + this.description = description; + } - public void setIncidentCategory(IncidentCategory incidentCategory) { - this.incidentCategory = incidentCategory; - } + public void setIncidentCategory(IncidentCategory incidentCategory) { + this.incidentCategory = incidentCategory; + } - public void setIncidentStatus(IncidentStatus incidentStatus) { - this.incidentStatus = incidentStatus; - } + public void setIncidentStatus(IncidentStatus incidentStatus) { + this.incidentStatus = incidentStatus; + } - public void setCreatedBy(AppUser createdBy) { - this.createdBy = createdBy; - } - - public void setModifiedBy(AppUser modifiedBy) { - this.modifiedBy = modifiedBy; - } - - public void setAssignedTo(AppUser assignedTo) { - this.assignedTo = assignedTo; - } - - public void setCreatedAt(LocalDateTime createdAt) { - this.createdAt = createdAt; - } - - public void setUpdatedAt(LocalDateTime updatedAt) { - this.updatedAt = updatedAt; - } + public void setCreatedBy(AppUser createdBy) { + this.createdBy = createdBy; + } + + public void setModifiedBy(AppUser modifiedBy) { + this.modifiedBy = modifiedBy; + } + + public void setAssignedTo(AppUser assignedTo) { + this.assignedTo = assignedTo; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } } diff --git a/src/main/java/org/example/team6backend/incident/entity/IncidentCategory.java b/src/main/java/org/example/team6backend/incident/entity/IncidentCategory.java index 23ff34b..833b01a 100644 --- a/src/main/java/org/example/team6backend/incident/entity/IncidentCategory.java +++ b/src/main/java/org/example/team6backend/incident/entity/IncidentCategory.java @@ -1,18 +1,15 @@ package org.example.team6backend.incident.entity; public enum IncidentCategory { - LAUNDRY_ROOM("Laundry Room"), - NOISE_DISTURBANCE("Noise Disturbance"), - DAMAGE("Damage"), - OTHER("Other"); + LAUNDRY_ROOM("Laundry Room"), NOISE_DISTURBANCE("Noise Disturbance"), DAMAGE("Damage"), OTHER("Other"); - private final String displayName; + private final String displayName; - IncidentCategory(String displayName){ - this.displayName = displayName; - } + IncidentCategory(String displayName) { + this.displayName = displayName; + } - public String getDisplayName(){ - return displayName; - } + public String getDisplayName() { + return displayName; + } } diff --git a/src/main/java/org/example/team6backend/incident/entity/IncidentStatus.java b/src/main/java/org/example/team6backend/incident/entity/IncidentStatus.java index a4b08d4..677d330 100644 --- a/src/main/java/org/example/team6backend/incident/entity/IncidentStatus.java +++ b/src/main/java/org/example/team6backend/incident/entity/IncidentStatus.java @@ -1,24 +1,22 @@ package org.example.team6backend.incident.entity; public enum IncidentStatus { - OPEN("Open", "Incident has been reported"), - IN_PROGRESS("In Progress", "Work is ongoing"), - RESOLVED("Resolved", "Issue has been fixed"), - CLOSED("Closed", "Incident is closed"); + OPEN("Open", "Incident has been reported"), IN_PROGRESS("In Progress", "Work is ongoing"), RESOLVED("Resolved", + "Issue has been fixed"), CLOSED("Closed", "Incident is closed"); - private final String displayName; - private final String description; + private final String displayName; + private final String description; - IncidentStatus(String displayName, String description){ - this.displayName = displayName; - this.description = description; - } + IncidentStatus(String displayName, String description) { + this.displayName = displayName; + this.description = description; + } - public String getDisplayName(){ - return displayName; - } + public String getDisplayName() { + return displayName; + } - public String getDescription(){ - return description; - } + public String getDescription() { + return description; + } } diff --git a/src/main/java/org/example/team6backend/incident/repository/IncidentRepository.java b/src/main/java/org/example/team6backend/incident/repository/IncidentRepository.java index d68cee6..0bb78f3 100644 --- a/src/main/java/org/example/team6backend/incident/repository/IncidentRepository.java +++ b/src/main/java/org/example/team6backend/incident/repository/IncidentRepository.java @@ -6,10 +6,8 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; -import java.util.List; - public interface IncidentRepository extends JpaRepository { - Page findByCreatedBy(AppUser user, Pageable pageable); - Page findByAssignedTo(AppUser user, Pageable pageable); + Page findByCreatedBy(AppUser user, Pageable pageable); + Page findByAssignedTo(AppUser user, Pageable pageable); } diff --git a/src/main/java/org/example/team6backend/incident/service/IncidentService.java b/src/main/java/org/example/team6backend/incident/service/IncidentService.java index 33cb163..ae43b1e 100644 --- a/src/main/java/org/example/team6backend/incident/service/IncidentService.java +++ b/src/main/java/org/example/team6backend/incident/service/IncidentService.java @@ -14,63 +14,58 @@ import org.springframework.stereotype.Service; import java.time.LocalDateTime; -import java.util.List; @Service public class IncidentService { - private final IncidentRepository incidentRepository; + private final IncidentRepository incidentRepository; - public IncidentService(IncidentRepository incidentRepository) { - this.incidentRepository = incidentRepository; - } + public IncidentService(IncidentRepository incidentRepository) { + this.incidentRepository = incidentRepository; + } - /**Help-method for sorting**/ - private Pageable withDefaultSort(Pageable pageable) { - if (pageable.isUnpaged() || pageable.getSort().isSorted()) { - return pageable; - } - return PageRequest.of( - pageable.getPageNumber(), - pageable.getPageSize(), - Sort.by(Sort.Direction.DESC, "id") - ); - } + /** Help-method for sorting **/ + private Pageable withDefaultSort(Pageable pageable) { + if (pageable.isUnpaged() || pageable.getSort().isSorted()) { + return pageable; + } + return PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(Sort.Direction.DESC, "id")); + } - /**Create incident**/ - public Incident createIncident(Incident incident){ - Authentication auth = SecurityContextHolder.getContext().getAuthentication(); - CustomUserDetails userDetails = (CustomUserDetails) auth.getPrincipal(); - AppUser appUser = userDetails.getUser(); + /** Create incident **/ + public Incident createIncident(Incident incident) { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + CustomUserDetails userDetails = (CustomUserDetails) auth.getPrincipal(); + AppUser appUser = userDetails.getUser(); - incident.setCreatedBy(appUser); - incident.setIncidentStatus(IncidentStatus.OPEN); - incident.setCreatedAt(LocalDateTime.now()); - incident.setUpdatedAt(LocalDateTime.now()); + incident.setCreatedBy(appUser); + incident.setIncidentStatus(IncidentStatus.OPEN); + incident.setCreatedAt(LocalDateTime.now()); + incident.setUpdatedAt(LocalDateTime.now()); - return incidentRepository.save(incident); - } + return incidentRepository.save(incident); + } - /**Find all incidents (Admin)**/ - public Page findAll(Pageable pageable){ - return incidentRepository.findAll(withDefaultSort(pageable)); - } + /** Find all incidents (Admin) **/ + public Page findAll(Pageable pageable) { + return incidentRepository.findAll(withDefaultSort(pageable)); + } - /**Find your own incidents (user)**/ - public Page findByCreatedBy(Pageable pageable){ - Authentication auth = SecurityContextHolder.getContext().getAuthentication(); - CustomUserDetails userDetails = (CustomUserDetails) auth.getPrincipal(); - AppUser user = userDetails.getUser(); + /** Find your own incidents (user) **/ + public Page findByCreatedBy(Pageable pageable) { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + CustomUserDetails userDetails = (CustomUserDetails) auth.getPrincipal(); + AppUser user = userDetails.getUser(); - return incidentRepository.findByCreatedBy(user, withDefaultSort(pageable)); - } + return incidentRepository.findByCreatedBy(user, withDefaultSort(pageable)); + } - /**Find assigned incidents per HANDLER**/ - public Page findByAssignedTo(Pageable pageable){ - Authentication auth = SecurityContextHolder.getContext().getAuthentication(); - CustomUserDetails userDetails = (CustomUserDetails) auth.getPrincipal(); - AppUser user = userDetails.getUser(); + /** Find assigned incidents per HANDLER **/ + public Page findByAssignedTo(Pageable pageable) { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + CustomUserDetails userDetails = (CustomUserDetails) auth.getPrincipal(); + AppUser user = userDetails.getUser(); - return incidentRepository.findByAssignedTo(user, withDefaultSort(pageable)); - } + return incidentRepository.findByAssignedTo(user, withDefaultSort(pageable)); + } } diff --git a/src/main/java/org/example/team6backend/page/PageController.java b/src/main/java/org/example/team6backend/page/PageController.java index bf0083c..66a8266 100644 --- a/src/main/java/org/example/team6backend/page/PageController.java +++ b/src/main/java/org/example/team6backend/page/PageController.java @@ -10,67 +10,67 @@ @Controller public class PageController { - private final UserService userService; + private final UserService userService; - public PageController(UserService userService) { - this.userService = userService; - } + public PageController(UserService userService) { + this.userService = userService; + } - @GetMapping("/") - public String index() { - return "index"; - } + @GetMapping("/") + public String index() { + return "index"; + } - @GetMapping("/dashboard") - public String dashboard(@AuthenticationPrincipal CustomUserDetails userDetails, Model model) { - AppUser user = userDetails.getUser(); - model.addAttribute("user", user); - model.addAttribute("role", user.getRole().name()); - return "dashboard"; - } + @GetMapping("/dashboard") + public String dashboard(@AuthenticationPrincipal CustomUserDetails userDetails, Model model) { + AppUser user = userDetails.getUser(); + model.addAttribute("user", user); + model.addAttribute("role", user.getRole().name()); + return "dashboard"; + } - @GetMapping("/incidents") - public String incidents(@AuthenticationPrincipal CustomUserDetails userDetails, Model model) { - model.addAttribute("role", userDetails.getUser().getRole().name()); - return "incidents"; - } + @GetMapping("/incidents") + public String incidents(@AuthenticationPrincipal CustomUserDetails userDetails, Model model) { + model.addAttribute("role", userDetails.getUser().getRole().name()); + return "incidents"; + } - @GetMapping("/create-incident") - public String createIncident(@AuthenticationPrincipal CustomUserDetails userDetails, Model model) { - AppUser user = userDetails.getUser(); + @GetMapping("/create-incident") + public String createIncident(@AuthenticationPrincipal CustomUserDetails userDetails, Model model) { + AppUser user = userDetails.getUser(); - String role = user.getRole().name(); - if (role.equals("RESIDENT") || role.equals("ADMIN")) { - model.addAttribute("role", role); - model.addAttribute("user", user); - return "createincident"; - } - return "redirect:/dashboard"; - } + String role = user.getRole().name(); + if (role.equals("RESIDENT") || role.equals("ADMIN")) { + model.addAttribute("role", role); + model.addAttribute("user", user); + return "createincident"; + } + return "redirect:/dashboard"; + } - @GetMapping("/profile") - public String profile(@AuthenticationPrincipal CustomUserDetails userDetails, Model model) { - AppUser user = userDetails.getUser(); - model.addAttribute("user", user); - model.addAttribute("role", user.getRole().name()); - return "profile"; - } + @GetMapping("/profile") + public String profile(@AuthenticationPrincipal CustomUserDetails userDetails, Model model) { + AppUser user = userDetails.getUser(); + model.addAttribute("user", user); + model.addAttribute("role", user.getRole().name()); + return "profile"; + } - @GetMapping("/admin") - public String admin(@AuthenticationPrincipal CustomUserDetails userDetails, Model model) { - AppUser user = userDetails.getUser(); + @GetMapping("/admin") + public String admin(@AuthenticationPrincipal CustomUserDetails userDetails, Model model) { + AppUser user = userDetails.getUser(); - if (user.getRole().name().equals("ADMIN")) { - model.addAttribute("user", user); - model.addAttribute("role", user.getRole().name()); - return "admin"; - } + if (user.getRole().name().equals("ADMIN")) { + model.addAttribute("user", user); + model.addAttribute("role", user.getRole().name()); + return "admin"; + } - return "redirect:/dashboard"; - } + return "redirect:/dashboard"; + } - @GetMapping("/demo") - public String demo() { - return "demo"; - } -} \ No newline at end of file + @GetMapping("/demo") + public String demo() { + return "demo"; + } +} diff --git a/src/main/java/org/example/team6backend/security/CustomOAuth2UserService.java b/src/main/java/org/example/team6backend/security/CustomOAuth2UserService.java index 92c6847..9ba68ad 100644 --- a/src/main/java/org/example/team6backend/security/CustomOAuth2UserService.java +++ b/src/main/java/org/example/team6backend/security/CustomOAuth2UserService.java @@ -17,19 +17,19 @@ @Slf4j public class CustomOAuth2UserService extends DefaultOAuth2UserService { - private final UserService userService; + private final UserService userService; - @Override - public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { - OAuth2User oauth2User = super.loadUser(userRequest); - Map attributes = oauth2User.getAttributes(); + @Override + public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { + OAuth2User oauth2User = super.loadUser(userRequest); + Map attributes = oauth2User.getAttributes(); - log.info("Received OAuth2 user from provider. login={}, id={}", attributes.get("login"), attributes.get("id")); + log.info("Received OAuth2 user from provider. login={}, id={}", attributes.get("login"), attributes.get("id")); - AppUser user = userService.createOrUpdateUser(attributes); + AppUser user = userService.createOrUpdateUser(attributes); - log.info("OAuth2 login completed for userId={}, role={}", user.getId(), user.getRole()); + log.info("OAuth2 login completed for userId={}, role={}", user.getId(), user.getRole()); - return new CustomUserDetails(user, attributes); - } -} \ No newline at end of file + return new CustomUserDetails(user, attributes); + } +} diff --git a/src/main/java/org/example/team6backend/security/CustomUserDetails.java b/src/main/java/org/example/team6backend/security/CustomUserDetails.java index ac09f11..678dcec 100644 --- a/src/main/java/org/example/team6backend/security/CustomUserDetails.java +++ b/src/main/java/org/example/team6backend/security/CustomUserDetails.java @@ -9,30 +9,30 @@ public class CustomUserDetails implements OAuth2User { - private final AppUser user; - private final Map attributes; - - public CustomUserDetails(AppUser user, Map attributes) { - this.user = user; - this.attributes = attributes; - } - - @Override - public Map getAttributes() { - return attributes; - } - - @Override - public Collection getAuthorities() { - return user.getAuthorities(); - } - - @Override - public String getName() { - return user.getName(); - } - - public AppUser getUser() { - return user; - } -} \ No newline at end of file + private final AppUser user; + private final Map attributes; + + public CustomUserDetails(AppUser user, Map attributes) { + this.user = user; + this.attributes = attributes; + } + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public Collection getAuthorities() { + return user.getAuthorities(); + } + + @Override + public String getName() { + return user.getName(); + } + + public AppUser getUser() { + return user; + } +} diff --git a/src/main/java/org/example/team6backend/test/TestController.java b/src/main/java/org/example/team6backend/test/TestController.java index 1fdecfa..3eda52e 100644 --- a/src/main/java/org/example/team6backend/test/TestController.java +++ b/src/main/java/org/example/team6backend/test/TestController.java @@ -6,8 +6,8 @@ @RestController public class TestController { - @GetMapping("/api/test") - public String apiTest() { - return "API fungerar"; - } -} \ No newline at end of file + @GetMapping("/api/test") + public String apiTest() { + return "API fungerar"; + } +} diff --git a/src/main/java/org/example/team6backend/user/controller/UserController.java b/src/main/java/org/example/team6backend/user/controller/UserController.java index 86a712f..8fa0be6 100644 --- a/src/main/java/org/example/team6backend/user/controller/UserController.java +++ b/src/main/java/org/example/team6backend/user/controller/UserController.java @@ -14,10 +14,10 @@ @RequiredArgsConstructor public class UserController { - private final UserMapper userMapper; + private final UserMapper userMapper; - @GetMapping("/me") - public UserResponse getCurrentUser(@AuthenticationPrincipal CustomUserDetails userDetails) { - return userMapper.toResponse(userDetails.getUser()); - } -} \ No newline at end of file + @GetMapping("/me") + public UserResponse getCurrentUser(@AuthenticationPrincipal CustomUserDetails userDetails) { + return userMapper.toResponse(userDetails.getUser()); + } +} diff --git a/src/main/java/org/example/team6backend/user/dto/UserResponse.java b/src/main/java/org/example/team6backend/user/dto/UserResponse.java index 8b671a7..578c97d 100644 --- a/src/main/java/org/example/team6backend/user/dto/UserResponse.java +++ b/src/main/java/org/example/team6backend/user/dto/UserResponse.java @@ -4,16 +4,6 @@ import java.time.LocalDateTime; -public record UserResponse( - String id, - String githubId, - String githubLogin, - String email, - String name, - UserRole role, - String avatarUrl, - boolean active, - LocalDateTime createdAt, - LocalDateTime updatedAt -) { -} \ No newline at end of file +public record UserResponse(String id, String githubId, String githubLogin, String email, String name, UserRole role, + String avatarUrl, boolean active, LocalDateTime createdAt, LocalDateTime updatedAt) { +} diff --git a/src/main/java/org/example/team6backend/user/entity/AppUser.java b/src/main/java/org/example/team6backend/user/entity/AppUser.java index dc909d5..aa1326e 100644 --- a/src/main/java/org/example/team6backend/user/entity/AppUser.java +++ b/src/main/java/org/example/team6backend/user/entity/AppUser.java @@ -23,95 +23,90 @@ import java.util.List; @Entity -@Table( - name = "app_user", - indexes = { - @Index(name = "idx_app_user_email", columnList = "email"), - @Index(name = "idx_app_user_role", columnList = "role"), - @Index(name = "idx_app_user_github_id", columnList = "github_id") - } -) +@Table(name = "app_user", indexes = {@Index(name = "idx_app_user_email", columnList = "email"), + @Index(name = "idx_app_user_role", columnList = "role"), + @Index(name = "idx_app_user_github_id", columnList = "github_id")}) @Getter @Setter @NoArgsConstructor public class AppUser implements UserDetails { - @Id - @GeneratedValue(strategy = GenerationType.UUID) - private String id; - - @Column(name = "github_id", nullable = false, unique = true, updatable = false) - private String githubId; - - @Column(name = "github_login", nullable = false, unique = true) - private String githubLogin; - - @Column(unique = true) - private String email; - - @Column(nullable = false) - private String name; - - @Enumerated(EnumType.STRING) - @Column(nullable = false) - private UserRole role = UserRole.PENDING; - - @Column(name = "avatar_url") - private String avatarUrl; - - @Column(name = "is_active", nullable = false) - private boolean active = true; - - @Column(name = "created_at", nullable = false, updatable = false) - private LocalDateTime createdAt; - - @Column(name = "updated_at", nullable = false) - private LocalDateTime updatedAt; - - @PrePersist - protected void onCreate() { - LocalDateTime now = LocalDateTime.now(); - this.createdAt = now; - this.updatedAt = now; - } - - @PreUpdate - protected void onUpdate() { - this.updatedAt = LocalDateTime.now(); - } - - @Override - public Collection getAuthorities() { - return List.of(new SimpleGrantedAuthority("ROLE_" + role.name())); - } - - @Override - public String getPassword() { - return null; - } - - @Override - public String getUsername() { - return email != null && !email.isBlank() ? email : githubLogin; - } - - @Override - public boolean isAccountNonExpired() { - return true; - } - - @Override - public boolean isAccountNonLocked() { - return active; - } - - @Override - public boolean isCredentialsNonExpired() { - return true; - } - - @Override - public boolean isEnabled() { - return active; - } -} \ No newline at end of file + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private String id; + + @Column(name = "github_id", nullable = false, unique = true, updatable = false) + private String githubId; + + @Column(name = "github_login", nullable = false, unique = true) + private String githubLogin; + + @Column(unique = true) + private String email; + + @Column(nullable = false) + private String name; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private UserRole role = UserRole.PENDING; + + @Column(name = "avatar_url") + private String avatarUrl; + + @Column(name = "is_active", nullable = false) + private boolean active = true; + + @Column(name = "created_at", nullable = false, updatable = false) + private LocalDateTime createdAt; + + @Column(name = "updated_at", nullable = false) + private LocalDateTime updatedAt; + + @PrePersist + protected void onCreate() { + LocalDateTime now = LocalDateTime.now(); + this.createdAt = now; + this.updatedAt = now; + } + + @PreUpdate + protected void onUpdate() { + this.updatedAt = LocalDateTime.now(); + } + + @Override + public Collection getAuthorities() { + return List.of(new SimpleGrantedAuthority("ROLE_" + role.name())); + } + + @Override + public String getPassword() { + return null; + } + + @Override + public String getUsername() { + return email != null && !email.isBlank() ? email : githubLogin; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return active; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return active; + } +} diff --git a/src/main/java/org/example/team6backend/user/entity/UserRole.java b/src/main/java/org/example/team6backend/user/entity/UserRole.java index 26ae977..c677516 100644 --- a/src/main/java/org/example/team6backend/user/entity/UserRole.java +++ b/src/main/java/org/example/team6backend/user/entity/UserRole.java @@ -1,25 +1,23 @@ package org.example.team6backend.user.entity; public enum UserRole { - PENDING("Pending", "Awaiting approval, cannot access system"), - RESIDENT("Resident", "Can create and view own incidents"), - HANDLER("Handler", "Can manage assigned incidents"), - ADMIN("Admin", "Full access to the system"); + PENDING("Pending", "Awaiting approval, cannot access system"), RESIDENT("Resident", + "Can create and view own incidents"), HANDLER("Handler", + "Can manage assigned incidents"), ADMIN("Admin", "Full access to the system"); - private final String displayName; - private final String description; + private final String displayName; + private final String description; + UserRole(String displayName, String description) { + this.displayName = displayName; + this.description = description; + } - UserRole(String displayName, String description){ - this.displayName = displayName; - this.description = description; - } + public String getDisplayName() { + return displayName; + } - public String getDisplayName(){ - return displayName; - } - - public String getDescription(){ - return description; - } + public String getDescription() { + return description; + } } diff --git a/src/main/java/org/example/team6backend/user/mapper/UserMapper.java b/src/main/java/org/example/team6backend/user/mapper/UserMapper.java index 8f3f3ff..8b233d3 100644 --- a/src/main/java/org/example/team6backend/user/mapper/UserMapper.java +++ b/src/main/java/org/example/team6backend/user/mapper/UserMapper.java @@ -8,22 +8,13 @@ @Component public class UserMapper { - public UserResponse toResponse(AppUser user) { - return new UserResponse( - user.getId(), - user.getGithubId(), - user.getGithubLogin(), - user.getEmail(), - user.getName(), - user.getRole(), - user.getAvatarUrl(), - user.isActive(), - user.getCreatedAt(), - user.getUpdatedAt() - ); - } + public UserResponse toResponse(AppUser user) { + return new UserResponse(user.getId(), user.getGithubId(), user.getGithubLogin(), user.getEmail(), + user.getName(), user.getRole(), user.getAvatarUrl(), user.isActive(), user.getCreatedAt(), + user.getUpdatedAt()); + } - public Page toResponsePage(Page users) { - return users.map(this::toResponse); - } -} \ No newline at end of file + public Page toResponsePage(Page users) { + return users.map(this::toResponse); + } +} diff --git a/src/main/java/org/example/team6backend/user/repository/AppUserRepository.java b/src/main/java/org/example/team6backend/user/repository/AppUserRepository.java index cef2b67..8f551fd 100644 --- a/src/main/java/org/example/team6backend/user/repository/AppUserRepository.java +++ b/src/main/java/org/example/team6backend/user/repository/AppUserRepository.java @@ -13,43 +13,38 @@ public interface AppUserRepository extends JpaRepository { - Optional findByGithubId(String githubId); - - Optional findByEmail(String email); - - List findByRole(UserRole role); - - long countByRole(UserRole role); - - long countByRoleAndActiveTrue(UserRole role); - - Page findByRole(UserRole role, Pageable pageable); - - Page findByActive(boolean active, Pageable pageable); - - @Query(""" - SELECT u - FROM AppUser u - WHERE (:email IS NULL OR LOWER(COALESCE(u.email, '')) LIKE LOWER(CONCAT('%', :email, '%'))) - AND (:name IS NULL OR LOWER(COALESCE(u.name, '')) LIKE LOWER(CONCAT('%', :name, '%'))) - AND (:role IS NULL OR u.role = :role) - AND (:active IS NULL OR u.active = :active) - """) - Page findAllWithFilters( - @Param("email") String email, - @Param("name") String name, - @Param("role") UserRole role, - @Param("active") Boolean active, - Pageable pageable - ); - - @Query(""" - SELECT u - FROM AppUser u - WHERE LOWER(COALESCE(u.email, '')) LIKE LOWER(CONCAT('%', :search, '%')) - OR LOWER(COALESCE(u.name, '')) LIKE LOWER(CONCAT('%', :search, '%')) - OR LOWER(COALESCE(u.githubLogin, '')) LIKE LOWER(CONCAT('%', :search, '%')) - OR LOWER(COALESCE(u.githubId, '')) LIKE LOWER(CONCAT('%', :search, '%')) - """) - Page searchUsers(@Param("search") String search, Pageable pageable); -} \ No newline at end of file + Optional findByGithubId(String githubId); + + Optional findByEmail(String email); + + List findByRole(UserRole role); + + long countByRole(UserRole role); + + long countByRoleAndActiveTrue(UserRole role); + + Page findByRole(UserRole role, Pageable pageable); + + Page findByActive(boolean active, Pageable pageable); + + @Query(""" + SELECT u + FROM AppUser u + WHERE (:email IS NULL OR LOWER(COALESCE(u.email, '')) LIKE LOWER(CONCAT('%', :email, '%'))) + AND (:name IS NULL OR LOWER(COALESCE(u.name, '')) LIKE LOWER(CONCAT('%', :name, '%'))) + AND (:role IS NULL OR u.role = :role) + AND (:active IS NULL OR u.active = :active) + """) + Page findAllWithFilters(@Param("email") String email, @Param("name") String name, + @Param("role") UserRole role, @Param("active") Boolean active, Pageable pageable); + + @Query(""" + SELECT u + FROM AppUser u + WHERE LOWER(COALESCE(u.email, '')) LIKE LOWER(CONCAT('%', :search, '%')) + OR LOWER(COALESCE(u.name, '')) LIKE LOWER(CONCAT('%', :search, '%')) + OR LOWER(COALESCE(u.githubLogin, '')) LIKE LOWER(CONCAT('%', :search, '%')) + OR LOWER(COALESCE(u.githubId, '')) LIKE LOWER(CONCAT('%', :search, '%')) + """) + Page searchUsers(@Param("search") String search, Pageable pageable); +} diff --git a/src/main/java/org/example/team6backend/user/service/UserService.java b/src/main/java/org/example/team6backend/user/service/UserService.java index 9264a19..d751771 100644 --- a/src/main/java/org/example/team6backend/user/service/UserService.java +++ b/src/main/java/org/example/team6backend/user/service/UserService.java @@ -20,191 +20,177 @@ @Transactional(readOnly = true) public class UserService { - private final AppUserRepository userRepository; - - @Transactional - public AppUser createOrUpdateUser(Map attributes) { - OAuthUserInfo oauthUserInfo = extractOAuthUserInfo(attributes); - - log.info("Processing GitHub login. githubId={}, githubLogin={}", oauthUserInfo.githubId(), oauthUserInfo.githubLogin()); - - return userRepository.findByGithubId(oauthUserInfo.githubId()) - .map(existingUser -> updateExistingUser(existingUser, oauthUserInfo)) - .orElseGet(() -> createNewPendingUser(oauthUserInfo)); - } - - public AppUser getUserById(String id) { - return userRepository.findById(id) - .orElseThrow(() -> new UserNotFoundException(id)); - } - - public List getAllUsers() { - return userRepository.findAll(); - } - - public List getUsersByRole(UserRole role) { - return userRepository.findByRole(role); - } - - public Page getAllUsersPaginated(Pageable pageable) { - return userRepository.findAll(pageable); - } - - public Page getUsersByRolePaginated(UserRole role, Pageable pageable) { - return userRepository.findByRole(role, pageable); - } - - public Page getUsersWithFilters(String email, String name, UserRole role, Boolean active, Pageable pageable) { - return userRepository.findAllWithFilters(email, name, role, active, pageable); - } - - public Page searchUsers(String search, Pageable pageable) { - if (search == null || search.trim().isEmpty()) { - return userRepository.findAll(pageable); - } - - return userRepository.searchUsers(search.trim(), pageable); - } - - @Transactional - public AppUser updateUserRole(String userId, UserRole newRole) { - AppUser user = getUserById(userId); - UserRole oldRole = user.getRole(); - - user.setRole(newRole); - AppUser savedUser = userRepository.save(user); - - log.info("Updated role for userId={} from {} to {}", userId, oldRole, newRole); - return savedUser; - } - - @Transactional - public AppUser updateUserActiveStatus(String userId, boolean active) { - AppUser user = getUserById(userId); - - if (!active && user.getRole() == UserRole.ADMIN) { - long activeAdminCount = userRepository.countByRoleAndActiveTrue(UserRole.ADMIN); - if (activeAdminCount <= 1) { - throw new IllegalStateException("Cannot deactivate the last active admin user"); - } - } - - boolean oldStatus = user.isActive(); - user.setActive(active); - AppUser savedUser = userRepository.save(user); - - log.info("Updated active status for userId={} from {} to {}", userId, oldStatus, active); - return savedUser; - } - - @Transactional - public AppUser approvePendingUser(String userId) { - AppUser user = getUserById(userId); - - if (user.getRole() != UserRole.PENDING) { - throw new IllegalStateException("User is not pending approval. Current role: " + user.getRole()); - } - - user.setRole(UserRole.RESIDENT); - user.setActive(true); - - AppUser savedUser = userRepository.save(user); - log.info("Approved pending user: userId={}, githubLogin={}", userId, user.getGithubLogin()); - return savedUser; - } - - @Transactional - public void deleteUser(String userId) { - AppUser user = getUserById(userId); - - if (user.getRole() == UserRole.ADMIN) { - long adminCount = userRepository.countByRole(UserRole.ADMIN); - if (adminCount <= 1) { - throw new IllegalStateException("Cannot delete the last admin user"); - } - } - - userRepository.delete(user); - log.info("Deleted user: userId={}, githubLogin={}", userId, user.getGithubLogin()); - } - - private AppUser updateExistingUser(AppUser existingUser, OAuthUserInfo oauthUserInfo) { - existingUser.setGithubLogin(oauthUserInfo.githubLogin()); - existingUser.setEmail(oauthUserInfo.email()); - existingUser.setName(oauthUserInfo.name()); - existingUser.setAvatarUrl(oauthUserInfo.avatarUrl()); - - AppUser savedUser = userRepository.save(existingUser); - - log.info( - "Updated existing user. userId={}, githubId={}, role={}, active={}", - savedUser.getId(), - savedUser.getGithubId(), - savedUser.getRole(), - savedUser.isActive() - ); - - return savedUser; - } - - private AppUser createNewPendingUser(OAuthUserInfo oauthUserInfo) { - AppUser newUser = new AppUser(); - newUser.setGithubId(oauthUserInfo.githubId()); - newUser.setGithubLogin(oauthUserInfo.githubLogin()); - newUser.setEmail(oauthUserInfo.email()); - newUser.setName(oauthUserInfo.name()); - newUser.setAvatarUrl(oauthUserInfo.avatarUrl()); - newUser.setRole(UserRole.PENDING); - newUser.setActive(true); - - AppUser savedUser = userRepository.save(newUser); - - log.info( - "Created new user. userId={}, githubId={}, role={}", - savedUser.getId(), - savedUser.getGithubId(), - savedUser.getRole() - ); - - return savedUser; - } - - private OAuthUserInfo extractOAuthUserInfo(Map attributes) { - String githubId = extractRequiredAttribute(attributes, "id"); - String githubLogin = extractRequiredAttribute(attributes, "login"); - String email = extractOptionalAttribute(attributes, "email"); - String name = resolveDisplayName(attributes, githubLogin); - String avatarUrl = extractOptionalAttribute(attributes, "avatar_url"); - - return new OAuthUserInfo(githubId, githubLogin, email, name, avatarUrl); - } - - private String resolveDisplayName(Map attributes, String githubLogin) { - String name = extractOptionalAttribute(attributes, "name"); - return (name == null || name.isBlank()) ? githubLogin : name; - } - - private String extractRequiredAttribute(Map attributes, String key) { - String value = extractOptionalAttribute(attributes, key); - - if (value == null || value.isBlank()) { - throw new IllegalArgumentException("Missing required OAuth attribute: " + key); - } - - return value; - } - - private String extractOptionalAttribute(Map attributes, String key) { - Object value = attributes.get(key); - return value != null ? String.valueOf(value) : null; - } - - private record OAuthUserInfo( - String githubId, - String githubLogin, - String email, - String name, - String avatarUrl - ) { - } -} \ No newline at end of file + private final AppUserRepository userRepository; + + @Transactional + public AppUser createOrUpdateUser(Map attributes) { + OAuthUserInfo oauthUserInfo = extractOAuthUserInfo(attributes); + + log.info("Processing GitHub login. githubId={}, githubLogin={}", oauthUserInfo.githubId(), + oauthUserInfo.githubLogin()); + + return userRepository.findByGithubId(oauthUserInfo.githubId()) + .map(existingUser -> updateExistingUser(existingUser, oauthUserInfo)) + .orElseGet(() -> createNewPendingUser(oauthUserInfo)); + } + + public AppUser getUserById(String id) { + return userRepository.findById(id).orElseThrow(() -> new UserNotFoundException(id)); + } + + public List getAllUsers() { + return userRepository.findAll(); + } + + public List getUsersByRole(UserRole role) { + return userRepository.findByRole(role); + } + + public Page getAllUsersPaginated(Pageable pageable) { + return userRepository.findAll(pageable); + } + + public Page getUsersByRolePaginated(UserRole role, Pageable pageable) { + return userRepository.findByRole(role, pageable); + } + + public Page getUsersWithFilters(String email, String name, UserRole role, Boolean active, + Pageable pageable) { + return userRepository.findAllWithFilters(email, name, role, active, pageable); + } + + public Page searchUsers(String search, Pageable pageable) { + if (search == null || search.trim().isEmpty()) { + return userRepository.findAll(pageable); + } + + return userRepository.searchUsers(search.trim(), pageable); + } + + @Transactional + public AppUser updateUserRole(String userId, UserRole newRole) { + AppUser user = getUserById(userId); + UserRole oldRole = user.getRole(); + + user.setRole(newRole); + AppUser savedUser = userRepository.save(user); + + log.info("Updated role for userId={} from {} to {}", userId, oldRole, newRole); + return savedUser; + } + + @Transactional + public AppUser updateUserActiveStatus(String userId, boolean active) { + AppUser user = getUserById(userId); + + if (!active && user.getRole() == UserRole.ADMIN) { + long activeAdminCount = userRepository.countByRoleAndActiveTrue(UserRole.ADMIN); + if (activeAdminCount <= 1) { + throw new IllegalStateException("Cannot deactivate the last active admin user"); + } + } + + boolean oldStatus = user.isActive(); + user.setActive(active); + AppUser savedUser = userRepository.save(user); + + log.info("Updated active status for userId={} from {} to {}", userId, oldStatus, active); + return savedUser; + } + + @Transactional + public AppUser approvePendingUser(String userId) { + AppUser user = getUserById(userId); + + if (user.getRole() != UserRole.PENDING) { + throw new IllegalStateException("User is not pending approval. Current role: " + user.getRole()); + } + + user.setRole(UserRole.RESIDENT); + user.setActive(true); + + AppUser savedUser = userRepository.save(user); + log.info("Approved pending user: userId={}, githubLogin={}", userId, user.getGithubLogin()); + return savedUser; + } + + @Transactional + public void deleteUser(String userId) { + AppUser user = getUserById(userId); + + if (user.getRole() == UserRole.ADMIN) { + long adminCount = userRepository.countByRole(UserRole.ADMIN); + if (adminCount <= 1) { + throw new IllegalStateException("Cannot delete the last admin user"); + } + } + + userRepository.delete(user); + log.info("Deleted user: userId={}, githubLogin={}", userId, user.getGithubLogin()); + } + + private AppUser updateExistingUser(AppUser existingUser, OAuthUserInfo oauthUserInfo) { + existingUser.setGithubLogin(oauthUserInfo.githubLogin()); + existingUser.setEmail(oauthUserInfo.email()); + existingUser.setName(oauthUserInfo.name()); + existingUser.setAvatarUrl(oauthUserInfo.avatarUrl()); + + AppUser savedUser = userRepository.save(existingUser); + + log.info("Updated existing user. userId={}, githubId={}, role={}, active={}", savedUser.getId(), + savedUser.getGithubId(), savedUser.getRole(), savedUser.isActive()); + + return savedUser; + } + + private AppUser createNewPendingUser(OAuthUserInfo oauthUserInfo) { + AppUser newUser = new AppUser(); + newUser.setGithubId(oauthUserInfo.githubId()); + newUser.setGithubLogin(oauthUserInfo.githubLogin()); + newUser.setEmail(oauthUserInfo.email()); + newUser.setName(oauthUserInfo.name()); + newUser.setAvatarUrl(oauthUserInfo.avatarUrl()); + newUser.setRole(UserRole.PENDING); + newUser.setActive(true); + + AppUser savedUser = userRepository.save(newUser); + + log.info("Created new user. userId={}, githubId={}, role={}", savedUser.getId(), savedUser.getGithubId(), + savedUser.getRole()); + + return savedUser; + } + + private OAuthUserInfo extractOAuthUserInfo(Map attributes) { + String githubId = extractRequiredAttribute(attributes, "id"); + String githubLogin = extractRequiredAttribute(attributes, "login"); + String email = extractOptionalAttribute(attributes, "email"); + String name = resolveDisplayName(attributes, githubLogin); + String avatarUrl = extractOptionalAttribute(attributes, "avatar_url"); + + return new OAuthUserInfo(githubId, githubLogin, email, name, avatarUrl); + } + + private String resolveDisplayName(Map attributes, String githubLogin) { + String name = extractOptionalAttribute(attributes, "name"); + return (name == null || name.isBlank()) ? githubLogin : name; + } + + private String extractRequiredAttribute(Map attributes, String key) { + String value = extractOptionalAttribute(attributes, key); + + if (value == null || value.isBlank()) { + throw new IllegalArgumentException("Missing required OAuth attribute: " + key); + } + + return value; + } + + private String extractOptionalAttribute(Map attributes, String key) { + Object value = attributes.get(key); + return value != null ? String.valueOf(value) : null; + } + + private record OAuthUserInfo(String githubId, String githubLogin, String email, String name, String avatarUrl) { + } +} diff --git a/src/test/java/org/example/team6backend/SanityCheckTest.java b/src/test/java/org/example/team6backend/SanityCheckTest.java new file mode 100644 index 0000000..25583be --- /dev/null +++ b/src/test/java/org/example/team6backend/SanityCheckTest.java @@ -0,0 +1,20 @@ +package org.example.team6backend; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class SanityCheckTest { + + @Test + void testJUnitWorks() { + assertTrue(true); + assertEquals(1, 1); + } + + @Test + void testStringOperations() { + String message = "CI/CD pipeline works"; + assertNotNull(message); + assertTrue(message.contains("works")); + } +} diff --git a/src/test/java/org/example/team6backend/Team6BackendApplicationTest.java b/src/test/java/org/example/team6backend/Team6BackendApplicationTest.java index 8da2f54..bf3129d 100644 --- a/src/test/java/org/example/team6backend/Team6BackendApplicationTest.java +++ b/src/test/java/org/example/team6backend/Team6BackendApplicationTest.java @@ -8,7 +8,7 @@ @ActiveProfiles("test") class Team6BackendApplicationTests { - @Test - void contextLoads() { - } -} \ No newline at end of file + @Test + void contextLoads() { + } +} diff --git a/src/test/java/org/example/team6backend/user/service/UserServiceTest.java b/src/test/java/org/example/team6backend/user/service/UserServiceTest.java index dedd005..ebefe60 100644 --- a/src/test/java/org/example/team6backend/user/service/UserServiceTest.java +++ b/src/test/java/org/example/team6backend/user/service/UserServiceTest.java @@ -15,23 +15,23 @@ @DisplayName("UserService Tests") class UserServiceTest { - @Mock - private AppUserRepository userRepository; + @Mock + private AppUserRepository userRepository; - @InjectMocks - private UserService userService; + @InjectMocks + private UserService userService; - private AppUser createTestUser() { - AppUser user = new AppUser(); - user.setId("123e4567-e89b-12d3-a456-426614174000"); - user.setGithubId("12345678"); - user.setGithubLogin("testuser"); - user.setEmail("test@example.com"); - user.setName("Test User"); - user.setRole(UserRole.RESIDENT); - user.setActive(true); - user.setCreatedAt(LocalDateTime.now()); - user.setUpdatedAt(LocalDateTime.now()); - return user; - } -} \ No newline at end of file + private AppUser createTestUser() { + AppUser user = new AppUser(); + user.setId("123e4567-e89b-12d3-a456-426614174000"); + user.setGithubId("12345678"); + user.setGithubLogin("testuser"); + user.setEmail("test@example.com"); + user.setName("Test User"); + user.setRole(UserRole.RESIDENT); + user.setActive(true); + user.setCreatedAt(LocalDateTime.now()); + user.setUpdatedAt(LocalDateTime.now()); + return user; + } +}