Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 47 additions & 17 deletions src/main/java/org/group1/projectbackend/config/DataInitializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +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;
}
// 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, 'user', 'user@example.com', 'password', 'user', true, CURRENT_TIMESTAMP),
(2, 'adamaj01', 'adamaj@example.com', 'password', 'Adam', true, CURRENT_TIMESTAMP),
(3, 'emmtra01', 'emmtra@example.com', 'password', 'Emma', true, CURRENT_TIMESTAMP),
(4, 'erifa101', 'erifa1@example.com', 'password', 'Erika', true, CURRENT_TIMESTAMP),
(5, 'johjan01', 'johjan@example.com', 'password', 'Johan', true, CURRENT_TIMESTAMP)
""");
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
""");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,46 +19,33 @@ public ActivityLogController(ActivityLogService activityLogService) {
this.activityLogService = activityLogService;
}

// Create activity log
@PostMapping
public ResponseEntity<ActivityLogDto> createActivityLog(@Valid @RequestBody CreateActivityLogDto dto) {
ActivityLogDto createdActivityLog = activityLogService.createActivityLog(dto);
return ResponseEntity.status(201).body(createdActivityLog);
return ResponseEntity.status(201).body(activityLogService.createActivityLog(dto));
}

// Get all activity logs
@GetMapping
public ResponseEntity<List<ActivityLogDto>> getAllActivityLogs() {
List<ActivityLogDto> activityLogs = activityLogService.getAllActivityLogs();
return ResponseEntity.ok(activityLogs);
return ResponseEntity.ok(activityLogService.getAllActivityLogs());
}

// Get activity log by id
@GetMapping("/{activityLogId}")
public ResponseEntity<ActivityLogDto> getActivityLogById(@PathVariable Long activityLogId) {
ActivityLogDto activityLog = activityLogService.getActivityLogById(activityLogId);
return ResponseEntity.ok(activityLog);
return ResponseEntity.ok(activityLogService.getActivityLogById(activityLogId));
}

// Get activity logs by support ticket id
@GetMapping("/ticket/{supportTicketId}")
public ResponseEntity<List<ActivityLogDto>> getActivityLogsBySupportTicketId(
public ResponseEntity<List<ActivityLogDto>> getByTicket(
@PathVariable Long supportTicketId,
@RequestParam(defaultValue = "desc") String sortDirection
) {
return ResponseEntity.ok(
activityLogService.getActivityLogsBySupportTicketId(supportTicketId, sortDirection)
);
@RequestParam(defaultValue = "desc") String sortDirection) {
List<ActivityLogDto> logs = activityLogService.getActivityLogsBySupportTicketId(supportTicketId, sortDirection);
return ResponseEntity.ok(logs);
}

// Get activity logs by user id
@GetMapping("/user/{userId}")
public ResponseEntity<List<ActivityLogDto>> getActivityLogsByUserId(
public ResponseEntity<List<ActivityLogDto>> getByUser(
@PathVariable Long userId,
@RequestParam(defaultValue = "asc") String sortDirection
) {
return ResponseEntity.ok(
activityLogService.getActivityLogsByUserId(userId, sortDirection)
);
@RequestParam(defaultValue = "asc") String sortDirection) {
return ResponseEntity.ok(activityLogService.getActivityLogsByUserId(userId, sortDirection));
}
}
20 changes: 20 additions & 0 deletions src/main/java/org/group1/projectbackend/dto/ApiResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.group1.projectbackend.dto;

import java.time.LocalDateTime;

public class ApiResponse<T> {

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; }
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,43 @@

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.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())
.formLogin(form -> form
.defaultSuccessUrl("/", true)
.permitAll()
)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/login", "/error", "/css/**", "/js/**").permitAll()
.requestMatchers("/activitylogs/**").hasRole("ADMIN")
.requestMatchers("/comments/**").authenticated()
.requestMatchers("/api/documents/**").authenticated()
.requestMatchers("/api/tickets/**").authenticated()
.requestMatchers("/documents/**").authenticated()
.requestMatchers("/tickets/**").authenticated()
.anyRequest().authenticated()
)
.userDetailsService(userDetailsService)
.build();
}

@Bean
public UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager(
User.withUsername("user")
.password("{noop}password")
.roles("USER")
.build()
);
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ public ActivityLogDto getActivityLogById(Long id) {
return activityLogMapper.toDto(activityLog);
}


public List<ActivityLogDto> getActivityLogsBySupportTicketId(Long supportTicketId, String sortDirection) {

if (!supportTicketRepository.existsById(supportTicketId)) {
throw new ResourceNotFoundException("Support ticket not found with id: " + supportTicketId);
}
Expand All @@ -91,4 +93,4 @@ public List<ActivityLogDto> getActivityLogsByUserId(Long userId, String sortDire
.map(activityLogMapper::toDto)
.toList();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down