From c6b0785e065c80d8e4f6999843ab0b82df6edc15 Mon Sep 17 00:00:00 2001 From: Emma Travljanin Date: Sun, 26 Apr 2026 23:47:42 +0200 Subject: [PATCH 01/14] security --- .../config/DataInitializer.java | 67 ++++++++++++++----- .../controller/ActivityLogController.java | 66 ++++++++++++------ .../projectbackend/dto/ApiResponse.java | 20 ++++++ .../security/CustomUserDetailsService.java | 33 +++++++++ .../security/SecurityConfig.java | 25 ++++--- 5 files changed, 161 insertions(+), 50 deletions(-) create mode 100644 src/main/java/org/group1/projectbackend/dto/ApiResponse.java create mode 100644 src/main/java/org/group1/projectbackend/security/CustomUserDetailsService.java diff --git a/src/main/java/org/group1/projectbackend/config/DataInitializer.java b/src/main/java/org/group1/projectbackend/config/DataInitializer.java index b580e33..ad15b7a 100644 --- a/src/main/java/org/group1/projectbackend/config/DataInitializer.java +++ b/src/main/java/org/group1/projectbackend/config/DataInitializer.java @@ -2,33 +2,66 @@ import org.springframework.boot.CommandLineRunner; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; @Component public class DataInitializer implements CommandLineRunner { private final JdbcTemplate jdbcTemplate; + private final PasswordEncoder passwordEncoder; - public DataInitializer(JdbcTemplate jdbcTemplate) { + public DataInitializer(JdbcTemplate jdbcTemplate, PasswordEncoder passwordEncoder) { this.jdbcTemplate = jdbcTemplate; + this.passwordEncoder = passwordEncoder; } @Override public void run(String... args) { - Integer userCount = jdbcTemplate.queryForObject( - "SELECT COUNT(*) FROM users WHERE id = 1", - Integer.class - ); - - if (userCount != null && userCount > 0) { - return; - } - - jdbcTemplate.update( - """ - INSERT INTO users (id, username, email, password, full_name, enabled, created_at) - VALUES (1, 'testuser', 'testuser@example.com', 'password123', 'Test User', true, CURRENT_TIMESTAMP) - """ - ); + + // 🔐 Kryptera lösenord + String userPassword = passwordEncoder.encode("password123"); + String adminPassword = passwordEncoder.encode("admin123"); + + // 👤 USER + jdbcTemplate.update(""" + INSERT INTO users (id, username, email, password, full_name, enabled, created_at) + VALUES (1, 'testuser', 'testuser@example.com', ?, 'Test User', true, CURRENT_TIMESTAMP) + ON CONFLICT (id) DO NOTHING + """, userPassword); + + // 👑 ADMIN USER + jdbcTemplate.update(""" + INSERT INTO users (id, username, email, password, full_name, enabled, created_at) + VALUES (2, 'admin', 'admin@example.com', ?, 'Admin User', true, CURRENT_TIMESTAMP) + ON CONFLICT (id) DO NOTHING + """, adminPassword); + + // 🧩 ROLLER + jdbcTemplate.update(""" + INSERT INTO roles (id, name) + VALUES (1, 'ROLE_USER') + ON CONFLICT (id) DO NOTHING + """); + + jdbcTemplate.update(""" + INSERT INTO roles (id, name) + VALUES (2, 'ROLE_ADMIN') + ON CONFLICT (id) DO NOTHING + """); + + // 🔗 Koppla USER → ROLE_USER + jdbcTemplate.update(""" + INSERT INTO user_roles (user_id, role_id) + VALUES (1, 1) + ON CONFLICT DO NOTHING + """); + + // 🔗 Koppla ADMIN → ROLE_ADMIN + jdbcTemplate.update(""" + INSERT INTO user_roles (user_id, role_id) + VALUES (2, 2) + ON CONFLICT DO NOTHING + """); } -} +} \ No newline at end of file diff --git a/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java b/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java index 35bd249..7c9a00c 100644 --- a/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java +++ b/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java @@ -1,6 +1,7 @@ package org.group1.projectbackend.controller; import jakarta.validation.Valid; +import org.group1.projectbackend.dto.ApiResponse; import org.group1.projectbackend.dto.activitylog.ActivityLogDto; import org.group1.projectbackend.dto.activitylog.CreateActivityLogDto; import org.group1.projectbackend.service.ActivityLogService; @@ -19,46 +20,67 @@ public ActivityLogController(ActivityLogService activityLogService) { this.activityLogService = activityLogService; } - // Create activity log + // ✅ Create activity log @PostMapping - public ResponseEntity createActivityLog(@Valid @RequestBody CreateActivityLogDto dto) { - ActivityLogDto createdActivityLog = activityLogService.createActivityLog(dto); - return ResponseEntity.status(201).body(createdActivityLog); + public ResponseEntity> createActivityLog( + @Valid @RequestBody CreateActivityLogDto dto) { + + ActivityLogDto created = activityLogService.createActivityLog(dto); + + return ResponseEntity.status(201) + .body(new ApiResponse<>("success", created)); } - // Get all activity logs + // ✅ Get ALL logs @GetMapping - public ResponseEntity> getAllActivityLogs() { - List activityLogs = activityLogService.getAllActivityLogs(); - return ResponseEntity.ok(activityLogs); + public ResponseEntity>> getAllActivityLogs() { + + List logs = activityLogService.getAllActivityLogs(); + + return ResponseEntity.ok( + new ApiResponse<>("success", logs) + ); } - // Get activity log by id + // ✅ Get by ID @GetMapping("/{activityLogId}") - public ResponseEntity getActivityLogById(@PathVariable Long activityLogId) { - ActivityLogDto activityLog = activityLogService.getActivityLogById(activityLogId); - return ResponseEntity.ok(activityLog); + public ResponseEntity> getActivityLogById( + @PathVariable Long activityLogId) { + + ActivityLogDto log = activityLogService.getActivityLogById(activityLogId); + + return ResponseEntity.ok( + new ApiResponse<>("success", log) + ); } - // Get activity logs by support ticket id + // ✅ Get by ticket @GetMapping("/ticket/{supportTicketId}") - public ResponseEntity> getActivityLogsBySupportTicketId( + public ResponseEntity>> getByTicket( @PathVariable Long supportTicketId, - @RequestParam(defaultValue = "asc") String sortDirection - ) { + @RequestParam(defaultValue = "asc") String sortDirection) { + + List logs = + activityLogService.getActivityLogsBySupportTicketId( + supportTicketId, sortDirection); + return ResponseEntity.ok( - activityLogService.getActivityLogsBySupportTicketId(supportTicketId, sortDirection) + new ApiResponse<>("success", logs) ); } - // Get activity logs by user id + // ✅ Get by user @GetMapping("/user/{userId}") - public ResponseEntity> getActivityLogsByUserId( + public ResponseEntity>> getByUser( @PathVariable Long userId, - @RequestParam(defaultValue = "asc") String sortDirection - ) { + @RequestParam(defaultValue = "asc") String sortDirection) { + + List logs = + activityLogService.getActivityLogsByUserId( + userId, sortDirection); + return ResponseEntity.ok( - activityLogService.getActivityLogsByUserId(userId, sortDirection) + new ApiResponse<>("success", logs) ); } } \ No newline at end of file diff --git a/src/main/java/org/group1/projectbackend/dto/ApiResponse.java b/src/main/java/org/group1/projectbackend/dto/ApiResponse.java new file mode 100644 index 0000000..5dc4dd0 --- /dev/null +++ b/src/main/java/org/group1/projectbackend/dto/ApiResponse.java @@ -0,0 +1,20 @@ +package org.group1.projectbackend.dto; + +import java.time.LocalDateTime; + +public class ApiResponse { + + private String status; + private T data; + private LocalDateTime timestamp; + + public ApiResponse(String status, T data) { + this.status = status; + this.data = data; + this.timestamp = LocalDateTime.now(); + } + + public String getStatus() { return status; } + public T getData() { return data; } + public LocalDateTime getTimestamp() { return timestamp; } +} \ No newline at end of file diff --git a/src/main/java/org/group1/projectbackend/security/CustomUserDetailsService.java b/src/main/java/org/group1/projectbackend/security/CustomUserDetailsService.java new file mode 100644 index 0000000..bc649ca --- /dev/null +++ b/src/main/java/org/group1/projectbackend/security/CustomUserDetailsService.java @@ -0,0 +1,33 @@ +package org.group1.projectbackend.security; + +import org.group1.projectbackend.entity.User; +import org.group1.projectbackend.repository.UserRepository; +import org.springframework.security.core.userdetails.*; +import org.springframework.stereotype.Service; + +@Service +public class CustomUserDetailsService implements UserDetailsService { + + private final UserRepository userRepository; + + public CustomUserDetailsService(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + + User user = userRepository.findByUsername(username) + .orElseThrow(() -> new UsernameNotFoundException("User not found")); + + return org.springframework.security.core.userdetails.User.builder() + .username(user.getUsername()) + .password(user.getPassword()) + .roles( + user.getRoles().stream() + .map(role -> role.getName().name().replace("ROLE_", "")) + .toArray(String[]::new) + ) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/org/group1/projectbackend/security/SecurityConfig.java b/src/main/java/org/group1/projectbackend/security/SecurityConfig.java index 14d4518..a54588c 100644 --- a/src/main/java/org/group1/projectbackend/security/SecurityConfig.java +++ b/src/main/java/org/group1/projectbackend/security/SecurityConfig.java @@ -4,32 +4,35 @@ import org.springframework.context.annotation.Configuration; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; @Configuration public class SecurityConfig { + private final CustomUserDetailsService userDetailsService; + + public SecurityConfig(CustomUserDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; + } + @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http .csrf(csrf -> csrf.disable()) .httpBasic(Customizer.withDefaults()) .authorizeHttpRequests(auth -> auth + .requestMatchers("/activitylogs/**").hasRole("ADMIN") + .requestMatchers("/comments/**").authenticated() + .requestMatchers("/api/tickets/**").authenticated() .anyRequest().authenticated() ) .build(); } @Bean - public UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager( - User.withUsername("user") - .password("{noop}password") - .roles("USER") - .build() - ); + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); } -} +} \ No newline at end of file From 88e3f4c5f9160584d67611165d458e69c3390e60 Mon Sep 17 00:00:00 2001 From: Emma Travljanin Date: Tue, 28 Apr 2026 09:17:10 +0200 Subject: [PATCH 02/14] before --- .../projectbackend/config/DataInitializer.java | 12 ++++++------ .../controller/ActivityLogController.java | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/group1/projectbackend/config/DataInitializer.java b/src/main/java/org/group1/projectbackend/config/DataInitializer.java index ad15b7a..25e8be7 100644 --- a/src/main/java/org/group1/projectbackend/config/DataInitializer.java +++ b/src/main/java/org/group1/projectbackend/config/DataInitializer.java @@ -19,25 +19,25 @@ public DataInitializer(JdbcTemplate jdbcTemplate, PasswordEncoder passwordEncode @Override public void run(String... args) { - // 🔐 Kryptera lösenord + // Kryptera lösenord String userPassword = passwordEncoder.encode("password123"); String adminPassword = passwordEncoder.encode("admin123"); - // 👤 USER + // USER jdbcTemplate.update(""" INSERT INTO users (id, username, email, password, full_name, enabled, created_at) VALUES (1, 'testuser', 'testuser@example.com', ?, 'Test User', true, CURRENT_TIMESTAMP) ON CONFLICT (id) DO NOTHING """, userPassword); - // 👑 ADMIN USER + // ADMIN USER jdbcTemplate.update(""" INSERT INTO users (id, username, email, password, full_name, enabled, created_at) VALUES (2, 'admin', 'admin@example.com', ?, 'Admin User', true, CURRENT_TIMESTAMP) ON CONFLICT (id) DO NOTHING """, adminPassword); - // 🧩 ROLLER + // ROLLER jdbcTemplate.update(""" INSERT INTO roles (id, name) VALUES (1, 'ROLE_USER') @@ -50,14 +50,14 @@ INSERT INTO roles (id, name) ON CONFLICT (id) DO NOTHING """); - // 🔗 Koppla USER → ROLE_USER + // Koppla USER → ROLE_USER jdbcTemplate.update(""" INSERT INTO user_roles (user_id, role_id) VALUES (1, 1) ON CONFLICT DO NOTHING """); - // 🔗 Koppla ADMIN → ROLE_ADMIN + // Koppla ADMIN → ROLE_ADMIN jdbcTemplate.update(""" INSERT INTO user_roles (user_id, role_id) VALUES (2, 2) diff --git a/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java b/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java index 7c9a00c..ef19b60 100644 --- a/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java +++ b/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java @@ -20,7 +20,7 @@ public ActivityLogController(ActivityLogService activityLogService) { this.activityLogService = activityLogService; } - // ✅ Create activity log + // Create activity log @PostMapping public ResponseEntity> createActivityLog( @Valid @RequestBody CreateActivityLogDto dto) { @@ -31,7 +31,7 @@ public ResponseEntity> createActivityLog( .body(new ApiResponse<>("success", created)); } - // ✅ Get ALL logs + // Get ALL logs @GetMapping public ResponseEntity>> getAllActivityLogs() { @@ -42,7 +42,7 @@ public ResponseEntity>> getAllActivityLogs() { ); } - // ✅ Get by ID + // Get by ID @GetMapping("/{activityLogId}") public ResponseEntity> getActivityLogById( @PathVariable Long activityLogId) { @@ -54,7 +54,7 @@ public ResponseEntity> getActivityLogById( ); } - // ✅ Get by ticket + // Get by ticket @GetMapping("/ticket/{supportTicketId}") public ResponseEntity>> getByTicket( @PathVariable Long supportTicketId, @@ -69,7 +69,7 @@ public ResponseEntity>> getByTicket( ); } - // ✅ Get by user + // Get by user @GetMapping("/user/{userId}") public ResponseEntity>> getByUser( @PathVariable Long userId, From 397ed989b0c167b17d29ae198a66d82fce6cb7fb Mon Sep 17 00:00:00 2001 From: Emma Travljanin Date: Tue, 28 Apr 2026 09:57:17 +0200 Subject: [PATCH 03/14] fix login + security config --- .../projectbackend/security/SecurityConfig.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/group1/projectbackend/security/SecurityConfig.java b/src/main/java/org/group1/projectbackend/security/SecurityConfig.java index a54588c..a04844a 100644 --- a/src/main/java/org/group1/projectbackend/security/SecurityConfig.java +++ b/src/main/java/org/group1/projectbackend/security/SecurityConfig.java @@ -2,7 +2,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @@ -21,13 +20,23 @@ public SecurityConfig(CustomUserDetailsService userDetailsService) { public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http .csrf(csrf -> csrf.disable()) - .httpBasic(Customizer.withDefaults()) + + // använd Spring default login + .formLogin(form -> form + .defaultSuccessUrl("/", true) + .permitAll() + ) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/", "/css/**", "/js/**").permitAll() .requestMatchers("/activitylogs/**").hasRole("ADMIN") .requestMatchers("/comments/**").authenticated() .requestMatchers("/api/tickets/**").authenticated() - .anyRequest().authenticated() + .anyRequest().permitAll() ) + + .userDetailsService(userDetailsService) + .build(); } From 754c24e1c6522e017f1ec26d0ad3acb9638d5171 Mon Sep 17 00:00:00 2001 From: Emma Travljanin Date: Tue, 28 Apr 2026 10:01:25 +0200 Subject: [PATCH 04/14] fix conflict ActivityLogController --- .../controller/ActivityLogController.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java b/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java index ef19b60..23d5809 100644 --- a/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java +++ b/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java @@ -20,7 +20,7 @@ public ActivityLogController(ActivityLogService activityLogService) { this.activityLogService = activityLogService; } - // Create activity log + // Create activity log @PostMapping public ResponseEntity> createActivityLog( @Valid @RequestBody CreateActivityLogDto dto) { @@ -31,7 +31,7 @@ public ResponseEntity> createActivityLog( .body(new ApiResponse<>("success", created)); } - // Get ALL logs + // Get ALL logs @GetMapping public ResponseEntity>> getAllActivityLogs() { @@ -42,7 +42,7 @@ public ResponseEntity>> getAllActivityLogs() { ); } - // Get by ID + // Get by ID @GetMapping("/{activityLogId}") public ResponseEntity> getActivityLogById( @PathVariable Long activityLogId) { @@ -54,7 +54,7 @@ public ResponseEntity> getActivityLogById( ); } - // Get by ticket + // Get by ticket @GetMapping("/ticket/{supportTicketId}") public ResponseEntity>> getByTicket( @PathVariable Long supportTicketId, @@ -69,7 +69,7 @@ public ResponseEntity>> getByTicket( ); } - // Get by user + // Get by user @GetMapping("/user/{userId}") public ResponseEntity>> getByUser( @PathVariable Long userId, From eccdfcea38db888b90a0118e8bcbe51fcea3e355 Mon Sep 17 00:00:00 2001 From: Emma Travljanin Date: Tue, 28 Apr 2026 10:13:08 +0200 Subject: [PATCH 05/14] fix tests remove ApiResponse --- .../controller/ActivityLogController.java | 47 +++++-------------- 1 file changed, 12 insertions(+), 35 deletions(-) diff --git a/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java b/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java index acfd3a0..c7f09da 100644 --- a/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java +++ b/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java @@ -1,7 +1,6 @@ package org.group1.projectbackend.controller; import jakarta.validation.Valid; -import org.group1.projectbackend.dto.ApiResponse; import org.group1.projectbackend.dto.activitylog.ActivityLogDto; import org.group1.projectbackend.dto.activitylog.CreateActivityLogDto; import org.group1.projectbackend.service.ActivityLogService; @@ -20,67 +19,45 @@ public ActivityLogController(ActivityLogService activityLogService) { this.activityLogService = activityLogService; } - // Create activity log @PostMapping - public ResponseEntity> createActivityLog( + public ResponseEntity createActivityLog( @Valid @RequestBody CreateActivityLogDto dto) { - ActivityLogDto created = activityLogService.createActivityLog(dto); - return ResponseEntity.status(201) - .body(new ApiResponse<>("success", created)); + .body(activityLogService.createActivityLog(dto)); } - // Get ALL logs @GetMapping - public ResponseEntity>> getAllActivityLogs() { - - List logs = activityLogService.getAllActivityLogs(); - - return ResponseEntity.ok( - new ApiResponse<>("success", logs) - ); + public ResponseEntity> getAllActivityLogs() { + return ResponseEntity.ok(activityLogService.getAllActivityLogs()); } - // Get by ID @GetMapping("/{activityLogId}") - public ResponseEntity> getActivityLogById( + public ResponseEntity getActivityLogById( @PathVariable Long activityLogId) { - ActivityLogDto log = activityLogService.getActivityLogById(activityLogId); - - return ResponseEntity.ok( - new ApiResponse<>("success", log) - ); + return ResponseEntity.ok(activityLogService.getActivityLogById(activityLogId)); } - // Get by ticket @GetMapping("/ticket/{supportTicketId}") - public ResponseEntity>> getByTicket( + public ResponseEntity> getByTicket( @PathVariable Long supportTicketId, @RequestParam(defaultValue = "asc") String sortDirection) { - List logs = - activityLogService.getActivityLogsBySupportTicketId( - supportTicketId, sortDirection); - return ResponseEntity.ok( - new ApiResponse<>("success", logs) + activityLogService.getActivityLogsBySupportTicketId( + supportTicketId, sortDirection) ); } - // Get by user @GetMapping("/user/{userId}") - public ResponseEntity>> getByUser( + public ResponseEntity> getByUser( @PathVariable Long userId, @RequestParam(defaultValue = "asc") String sortDirection) { - List logs = - activityLogService.getActivityLogsByUserId( - userId, sortDirection); - return ResponseEntity.ok( - new ApiResponse<>("success", logs) + activityLogService.getActivityLogsByUserId( + userId, sortDirection) ); } } From 4aea5c24d470891e50945141c4b732f5444245e0 Mon Sep 17 00:00:00 2001 From: Emma Travljanin Date: Tue, 28 Apr 2026 10:19:04 +0200 Subject: [PATCH 06/14] fix ticket endpoint --- .../controller/ActivityLogController.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java b/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java index c7f09da..48a2a57 100644 --- a/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java +++ b/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java @@ -44,10 +44,15 @@ public ResponseEntity> getByTicket( @PathVariable Long supportTicketId, @RequestParam(defaultValue = "asc") String sortDirection) { - return ResponseEntity.ok( + List logs = activityLogService.getActivityLogsBySupportTicketId( - supportTicketId, sortDirection) - ); + supportTicketId, sortDirection); + + if (logs.isEmpty()) { + return ResponseEntity.notFound().build(); + } + + return ResponseEntity.ok(logs); } @GetMapping("/user/{userId}") @@ -56,8 +61,7 @@ public ResponseEntity> getByUser( @RequestParam(defaultValue = "asc") String sortDirection) { return ResponseEntity.ok( - activityLogService.getActivityLogsByUserId( - userId, sortDirection) + activityLogService.getActivityLogsByUserId(userId, sortDirection) ); } -} +} \ No newline at end of file From 26c4bc5f969cd4028a001910a22daa1ecb04ffd5 Mon Sep 17 00:00:00 2001 From: Emma Travljanin Date: Tue, 28 Apr 2026 10:22:01 +0200 Subject: [PATCH 07/14] final fix controller --- .../controller/ActivityLogController.java | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java b/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java index 48a2a57..d40615c 100644 --- a/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java +++ b/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java @@ -44,15 +44,10 @@ public ResponseEntity> getByTicket( @PathVariable Long supportTicketId, @RequestParam(defaultValue = "asc") String sortDirection) { - List logs = + return ResponseEntity.ok( activityLogService.getActivityLogsBySupportTicketId( - supportTicketId, sortDirection); - - if (logs.isEmpty()) { - return ResponseEntity.notFound().build(); - } - - return ResponseEntity.ok(logs); + supportTicketId, sortDirection) + ); } @GetMapping("/user/{userId}") @@ -61,7 +56,8 @@ public ResponseEntity> getByUser( @RequestParam(defaultValue = "asc") String sortDirection) { return ResponseEntity.ok( - activityLogService.getActivityLogsByUserId(userId, sortDirection) + activityLogService.getActivityLogsByUserId( + userId, sortDirection) ); } } \ No newline at end of file From f5260d7b0745299b51a046ea96ecaa5921eaad6a Mon Sep 17 00:00:00 2001 From: Emma Travljanin Date: Tue, 28 Apr 2026 10:25:33 +0200 Subject: [PATCH 08/14] final fix tests --- .../controller/ActivityLogController.java | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java b/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java index d40615c..ee3f62b 100644 --- a/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java +++ b/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java @@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.*; import java.util.List; +import java.util.Map; @RestController @RequestMapping("/activitylogs") @@ -39,15 +40,23 @@ public ResponseEntity getActivityLogById( return ResponseEntity.ok(activityLogService.getActivityLogById(activityLogId)); } + @GetMapping("/ticket/{supportTicketId}") - public ResponseEntity> getByTicket( + public ResponseEntity getByTicket( @PathVariable Long supportTicketId, @RequestParam(defaultValue = "asc") String sortDirection) { - return ResponseEntity.ok( + List logs = activityLogService.getActivityLogsBySupportTicketId( - supportTicketId, sortDirection) - ); + supportTicketId, sortDirection); + + + if (logs == null || logs.isEmpty()) { + return ResponseEntity.status(404) + .body(Map.of("message", "No logs found")); + } + + return ResponseEntity.ok(logs); } @GetMapping("/user/{userId}") From f7522c9b5fb5aebe31ed92694b60d3cd034c3d3f Mon Sep 17 00:00:00 2001 From: Emma Travljanin Date: Tue, 28 Apr 2026 10:30:47 +0200 Subject: [PATCH 09/14] fix controller logic --- .../controller/ActivityLogController.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java b/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java index ee3f62b..1a517b8 100644 --- a/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java +++ b/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java @@ -37,7 +37,9 @@ public ResponseEntity> getAllActivityLogs() { public ResponseEntity getActivityLogById( @PathVariable Long activityLogId) { - return ResponseEntity.ok(activityLogService.getActivityLogById(activityLogId)); + return ResponseEntity.ok( + activityLogService.getActivityLogById(activityLogId) + ); } @@ -51,11 +53,12 @@ public ResponseEntity getByTicket( supportTicketId, sortDirection); - if (logs == null || logs.isEmpty()) { + if (logs == null) { return ResponseEntity.status(404) - .body(Map.of("message", "No logs found")); + .body(Map.of("message", "Ticket not found")); } + return ResponseEntity.ok(logs); } @@ -66,7 +69,8 @@ public ResponseEntity> getByUser( return ResponseEntity.ok( activityLogService.getActivityLogsByUserId( - userId, sortDirection) + userId, sortDirection + ) ); } } \ No newline at end of file From 985da07a7bdd142b3d8a21062063123c5d4a15c5 Mon Sep 17 00:00:00 2001 From: Emma Travljanin Date: Tue, 28 Apr 2026 10:37:45 +0200 Subject: [PATCH 10/14] FINAL FIX --- .../projectbackend/service/ActivityLogService.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/group1/projectbackend/service/ActivityLogService.java b/src/main/java/org/group1/projectbackend/service/ActivityLogService.java index b940fad..5efc085 100644 --- a/src/main/java/org/group1/projectbackend/service/ActivityLogService.java +++ b/src/main/java/org/group1/projectbackend/service/ActivityLogService.java @@ -67,16 +67,22 @@ public ActivityLogDto getActivityLogById(Long id) { } public List getActivityLogsBySupportTicketId(Long supportTicketId, String sortDirection) { + if (!supportTicketRepository.existsById(supportTicketId)) { - throw new ResourceNotFoundException("Support ticket not found with id: " + supportTicketId); + return null; } Sort sort = "desc".equalsIgnoreCase(sortDirection) ? Sort.by("createdAt").descending() : Sort.by("createdAt").ascending(); - return activityLogRepository.findBySupportTicketId(supportTicketId, sort) - .stream() + var logs = activityLogRepository.findBySupportTicketId(supportTicketId, sort); + + if (logs == null || logs.isEmpty()) { + return List.of(); + } + + return logs.stream() .map(activityLogMapper::toDto) .toList(); } @@ -91,4 +97,4 @@ public List getActivityLogsByUserId(Long userId, String sortDire .map(activityLogMapper::toDto) .toList(); } -} +} \ No newline at end of file From b7755619b8aefcc6ba7d20c161586bccd67d713c Mon Sep 17 00:00:00 2001 From: Emma Travljanin Date: Tue, 28 Apr 2026 10:42:10 +0200 Subject: [PATCH 11/14] FINAL FIX ALL TESTS --- .../controller/ActivityLogController.java | 19 +++++-------------- .../service/ActivityLogService.java | 12 ++++-------- 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java b/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java index 1a517b8..639f842 100644 --- a/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java +++ b/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java @@ -8,7 +8,6 @@ import org.springframework.web.bind.annotation.*; import java.util.List; -import java.util.Map; @RestController @RequestMapping("/activitylogs") @@ -42,24 +41,16 @@ public ResponseEntity getActivityLogById( ); } - @GetMapping("/ticket/{supportTicketId}") - public ResponseEntity getByTicket( + public ResponseEntity> getByTicket( @PathVariable Long supportTicketId, @RequestParam(defaultValue = "asc") String sortDirection) { - List logs = + return ResponseEntity.ok( activityLogService.getActivityLogsBySupportTicketId( - supportTicketId, sortDirection); - - - if (logs == null) { - return ResponseEntity.status(404) - .body(Map.of("message", "Ticket not found")); - } - - - return ResponseEntity.ok(logs); + supportTicketId, sortDirection + ) + ); } @GetMapping("/user/{userId}") diff --git a/src/main/java/org/group1/projectbackend/service/ActivityLogService.java b/src/main/java/org/group1/projectbackend/service/ActivityLogService.java index 5efc085..6d8dbfa 100644 --- a/src/main/java/org/group1/projectbackend/service/ActivityLogService.java +++ b/src/main/java/org/group1/projectbackend/service/ActivityLogService.java @@ -66,23 +66,19 @@ public ActivityLogDto getActivityLogById(Long id) { return activityLogMapper.toDto(activityLog); } + public List getActivityLogsBySupportTicketId(Long supportTicketId, String sortDirection) { if (!supportTicketRepository.existsById(supportTicketId)) { - return null; + throw new ResourceNotFoundException("Support ticket not found with id: " + supportTicketId); } Sort sort = "desc".equalsIgnoreCase(sortDirection) ? Sort.by("createdAt").descending() : Sort.by("createdAt").ascending(); - var logs = activityLogRepository.findBySupportTicketId(supportTicketId, sort); - - if (logs == null || logs.isEmpty()) { - return List.of(); - } - - return logs.stream() + return activityLogRepository.findBySupportTicketId(supportTicketId, sort) + .stream() .map(activityLogMapper::toDto) .toList(); } From 990f0257633d9b405d9aff03a863d7df9adc7a9d Mon Sep 17 00:00:00 2001 From: Emma Travljanin Date: Tue, 28 Apr 2026 10:50:52 +0200 Subject: [PATCH 12/14] final fix controller exception handling --- .../controller/ActivityLogController.java | 39 ++++++++----------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java b/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java index 639f842..11cbdb7 100644 --- a/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java +++ b/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java @@ -3,6 +3,7 @@ import jakarta.validation.Valid; import org.group1.projectbackend.dto.activitylog.ActivityLogDto; import org.group1.projectbackend.dto.activitylog.CreateActivityLogDto; +import org.group1.projectbackend.exception.ResourceNotFoundException; import org.group1.projectbackend.service.ActivityLogService; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -20,11 +21,8 @@ public ActivityLogController(ActivityLogService activityLogService) { } @PostMapping - public ResponseEntity createActivityLog( - @Valid @RequestBody CreateActivityLogDto dto) { - - return ResponseEntity.status(201) - .body(activityLogService.createActivityLog(dto)); + public ResponseEntity createActivityLog(@Valid @RequestBody CreateActivityLogDto dto) { + return ResponseEntity.status(201).body(activityLogService.createActivityLog(dto)); } @GetMapping @@ -33,35 +31,30 @@ public ResponseEntity> getAllActivityLogs() { } @GetMapping("/{activityLogId}") - public ResponseEntity getActivityLogById( - @PathVariable Long activityLogId) { - - return ResponseEntity.ok( - activityLogService.getActivityLogById(activityLogId) - ); + public ResponseEntity getActivityLogById(@PathVariable Long activityLogId) { + try { + return ResponseEntity.ok(activityLogService.getActivityLogById(activityLogId)); + } catch (ResourceNotFoundException e) { + return ResponseEntity.notFound().build(); + } } @GetMapping("/ticket/{supportTicketId}") public ResponseEntity> getByTicket( @PathVariable Long supportTicketId, @RequestParam(defaultValue = "asc") String sortDirection) { - - return ResponseEntity.ok( - activityLogService.getActivityLogsBySupportTicketId( - supportTicketId, sortDirection - ) - ); + try { + List logs = activityLogService.getActivityLogsBySupportTicketId(supportTicketId, sortDirection); + return ResponseEntity.ok(logs); + } catch (ResourceNotFoundException e) { + return ResponseEntity.notFound().build(); + } } @GetMapping("/user/{userId}") public ResponseEntity> getByUser( @PathVariable Long userId, @RequestParam(defaultValue = "asc") String sortDirection) { - - return ResponseEntity.ok( - activityLogService.getActivityLogsByUserId( - userId, sortDirection - ) - ); + return ResponseEntity.ok(activityLogService.getActivityLogsByUserId(userId, sortDirection)); } } \ No newline at end of file From 6f80bcd3b0012e710017fc74b5e50f8d75ebd309 Mon Sep 17 00:00:00 2001 From: Adam Majava Date: Tue, 28 Apr 2026 11:07:16 +0200 Subject: [PATCH 13/14] feat: add unauthorized access tests for document upload and ticket creation --- .../projectbackend/security/SecurityConfig.java | 15 ++++++--------- .../controller/DocumentControllerTest.java | 16 ++++++++++++++++ .../controller/SupportTicketControllerTest.java | 15 +++++++++++++++ 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/group1/projectbackend/security/SecurityConfig.java b/src/main/java/org/group1/projectbackend/security/SecurityConfig.java index a04844a..b28a0d8 100644 --- a/src/main/java/org/group1/projectbackend/security/SecurityConfig.java +++ b/src/main/java/org/group1/projectbackend/security/SecurityConfig.java @@ -19,24 +19,21 @@ public SecurityConfig(CustomUserDetailsService userDetailsService) { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http - .csrf(csrf -> csrf.disable()) - - // använd Spring default login .formLogin(form -> form .defaultSuccessUrl("/", true) .permitAll() ) - .authorizeHttpRequests(auth -> auth - .requestMatchers("/", "/css/**", "/js/**").permitAll() + .requestMatchers("/", "/login", "/error", "/css/**", "/js/**").permitAll() .requestMatchers("/activitylogs/**").hasRole("ADMIN") .requestMatchers("/comments/**").authenticated() + .requestMatchers("/api/documents/**").authenticated() .requestMatchers("/api/tickets/**").authenticated() - .anyRequest().permitAll() + .requestMatchers("/documents/**").authenticated() + .requestMatchers("/tickets/**").authenticated() + .anyRequest().authenticated() ) - .userDetailsService(userDetailsService) - .build(); } @@ -44,4 +41,4 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } -} \ No newline at end of file +} diff --git a/src/test/java/org/group1/projectbackend/controller/DocumentControllerTest.java b/src/test/java/org/group1/projectbackend/controller/DocumentControllerTest.java index 917a507..934b411 100644 --- a/src/test/java/org/group1/projectbackend/controller/DocumentControllerTest.java +++ b/src/test/java/org/group1/projectbackend/controller/DocumentControllerTest.java @@ -85,6 +85,22 @@ void shouldUploadDocumentForTicket() throws Exception { .andExpect(jsonPath("$.uploadedById").value(1)); } + @Test + void shouldReturnUnauthorizedWhenUploadingDocumentWithoutAuthentication() throws Exception { + MockMultipartFile file = new MockMultipartFile( + "file", + "setup-guide.pdf", + "application/pdf", + "file-content".getBytes() + ); + + mockMvc.perform(multipart("/api/tickets/10/documents") + .file(file) + .param("uploadedByUserId", "1") + .with(csrf())) + .andExpect(status().isUnauthorized()); + } + @Test @WithMockUser void shouldReturnBadRequestWhenUploadedFileIsEmpty() throws Exception { diff --git a/src/test/java/org/group1/projectbackend/controller/SupportTicketControllerTest.java b/src/test/java/org/group1/projectbackend/controller/SupportTicketControllerTest.java index 9e122c4..7e9c3e5 100644 --- a/src/test/java/org/group1/projectbackend/controller/SupportTicketControllerTest.java +++ b/src/test/java/org/group1/projectbackend/controller/SupportTicketControllerTest.java @@ -74,6 +74,21 @@ void shouldCreateTicket() throws Exception { .andExpect(jsonPath("$.title").value("VPN access issue")); } + @Test + void shouldReturnUnauthorizedWhenCreatingTicketWithoutAuthentication() throws Exception { + mockMvc.perform(post("/api/tickets") + .with(SecurityMockMvcRequestPostProcessors.csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "title": "VPN access issue", + "description": "Cannot connect to the company VPN from home.", + "priority": "HIGH" + } + """)) + .andExpect(status().isUnauthorized()); + } + @Test @WithMockUser void shouldGetTicketById() throws Exception { From 07109b5c0fe853853b86eda9c255d29d3189817a Mon Sep 17 00:00:00 2001 From: Adam Majava Date: Tue, 28 Apr 2026 11:15:02 +0200 Subject: [PATCH 14/14] feat: simplify activity log retrieval by removing exception handling for not found cases --- .../controller/ActivityLogController.java | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java b/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java index 11cbdb7..144fc57 100644 --- a/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java +++ b/src/main/java/org/group1/projectbackend/controller/ActivityLogController.java @@ -3,7 +3,6 @@ import jakarta.validation.Valid; import org.group1.projectbackend.dto.activitylog.ActivityLogDto; import org.group1.projectbackend.dto.activitylog.CreateActivityLogDto; -import org.group1.projectbackend.exception.ResourceNotFoundException; import org.group1.projectbackend.service.ActivityLogService; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -32,23 +31,15 @@ public ResponseEntity> getAllActivityLogs() { @GetMapping("/{activityLogId}") public ResponseEntity getActivityLogById(@PathVariable Long activityLogId) { - try { - return ResponseEntity.ok(activityLogService.getActivityLogById(activityLogId)); - } catch (ResourceNotFoundException e) { - return ResponseEntity.notFound().build(); - } + return ResponseEntity.ok(activityLogService.getActivityLogById(activityLogId)); } @GetMapping("/ticket/{supportTicketId}") public ResponseEntity> getByTicket( @PathVariable Long supportTicketId, - @RequestParam(defaultValue = "asc") String sortDirection) { - try { - List logs = activityLogService.getActivityLogsBySupportTicketId(supportTicketId, sortDirection); - return ResponseEntity.ok(logs); - } catch (ResourceNotFoundException e) { - return ResponseEntity.notFound().build(); - } + @RequestParam(defaultValue = "desc") String sortDirection) { + List logs = activityLogService.getActivityLogsBySupportTicketId(supportTicketId, sortDirection); + return ResponseEntity.ok(logs); } @GetMapping("/user/{userId}") @@ -57,4 +48,4 @@ public ResponseEntity> getByUser( @RequestParam(defaultValue = "asc") String sortDirection) { return ResponseEntity.ok(activityLogService.getActivityLogsByUserId(userId, sortDirection)); } -} \ No newline at end of file +}