autocomit

This commit is contained in:
2026-02-07 17:51:17 +01:00
parent ff2eb62174
commit b752e26526
68 changed files with 8291 additions and 0 deletions
@@ -0,0 +1,20 @@
package com.ldpv2;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
/**
* Main entry point for LDPv2 Backend Application
*
* @author LDPv2 Team
* @version 1.0.0
*/
@SpringBootApplication
@EnableJpaAuditing
public class LdpV2Application {
public static void main(String[] args) {
SpringApplication.run(LdpV2Application.class, args);
}
}
@@ -0,0 +1,37 @@
package com.ldpv2.config;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI ldpV2OpenAPI() {
return new OpenAPI()
.info(new Info()
.title("LDPv2 API")
.description("Lifecycle Data Platform v2 - Application Management API")
.version("1.0.0")
.contact(new Contact()
.name("LDPv2 Team")
.email("team@ldpv2.com"))
.license(new License()
.name("Proprietary")
.url("https://ldpv2.com/license")))
.addSecurityItem(new SecurityRequirement().addList("bearerAuth"))
.components(new Components()
.addSecuritySchemes("bearerAuth",
new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")));
}
}
@@ -0,0 +1,86 @@
package com.ldpv2.config;
import com.ldpv2.security.JwtAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**", "/swagger-ui.html").permitAll()
.anyRequest().authenticated()
)
.authenticationProvider(authenticationProvider())
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://localhost:4200", "http://localhost:3000"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
configuration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
}
@@ -0,0 +1,36 @@
package com.ldpv2.controller;
import com.ldpv2.dto.request.LoginRequest;
import com.ldpv2.dto.request.RegisterRequest;
import com.ldpv2.dto.response.AuthResponse;
import com.ldpv2.service.AuthService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/auth")
@Tag(name = "Authentication", description = "Authentication and registration endpoints")
public class AuthController {
@Autowired
private AuthService authService;
@PostMapping("/register")
@Operation(summary = "Register new user", description = "Create a new user account")
public ResponseEntity<AuthResponse> register(@Valid @RequestBody RegisterRequest request) {
AuthResponse response = authService.register(request);
return new ResponseEntity<>(response, HttpStatus.CREATED);
}
@PostMapping("/login")
@Operation(summary = "Login", description = "Authenticate and receive JWT token")
public ResponseEntity<AuthResponse> login(@Valid @RequestBody LoginRequest request) {
AuthResponse response = authService.login(request);
return ResponseEntity.ok(response);
}
}
@@ -0,0 +1,89 @@
package com.ldpv2.controller;
import com.ldpv2.dto.request.CreateEnvironmentRequest;
import com.ldpv2.dto.request.UpdateEnvironmentRequest;
import com.ldpv2.dto.response.EnvironmentResponse;
import com.ldpv2.service.EnvironmentService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.UUID;
@RestController
@RequestMapping("/environments")
@Tag(name = "Environments", description = "Environment management endpoints")
@SecurityRequirement(name = "bearerAuth")
public class EnvironmentController {
@Autowired
private EnvironmentService environmentService;
@PostMapping
@Operation(summary = "Create environment", description = "Create a new environment")
public ResponseEntity<EnvironmentResponse> create(@Valid @RequestBody CreateEnvironmentRequest request) {
EnvironmentResponse response = environmentService.create(request);
return new ResponseEntity<>(response, HttpStatus.CREATED);
}
@PutMapping("/{id}")
@Operation(summary = "Update environment", description = "Update an existing environment")
public ResponseEntity<EnvironmentResponse> update(
@PathVariable UUID id,
@Valid @RequestBody UpdateEnvironmentRequest request) {
EnvironmentResponse response = environmentService.update(id, request);
return ResponseEntity.ok(response);
}
@GetMapping("/{id}")
@Operation(summary = "Get environment", description = "Get environment by ID")
public ResponseEntity<EnvironmentResponse> getById(@PathVariable UUID id) {
EnvironmentResponse response = environmentService.findById(id);
return ResponseEntity.ok(response);
}
@GetMapping
@Operation(summary = "List environments", description = "Get paginated list of environments")
public ResponseEntity<Page<EnvironmentResponse>> getAll(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(defaultValue = "name") String sortBy,
@RequestParam(defaultValue = "asc") String sortDirection) {
Sort sort = sortDirection.equalsIgnoreCase("desc")
? Sort.by(sortBy).descending()
: Sort.by(sortBy).ascending();
Pageable pageable = PageRequest.of(page, size, sort);
Page<EnvironmentResponse> response = environmentService.findAll(pageable);
return ResponseEntity.ok(response);
}
@GetMapping("/search")
@Operation(summary = "Search environments", description = "Search environments by name")
public ResponseEntity<Page<EnvironmentResponse>> search(
@RequestParam String query,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
Pageable pageable = PageRequest.of(page, size);
Page<EnvironmentResponse> response = environmentService.search(query, pageable);
return ResponseEntity.ok(response);
}
@DeleteMapping("/{id}")
@Operation(summary = "Delete environment", description = "Delete an environment")
public ResponseEntity<Void> delete(@PathVariable UUID id) {
environmentService.delete(id);
return ResponseEntity.noContent().build();
}
}
@@ -0,0 +1,33 @@
package com.ldpv2.domain.entity;
import jakarta.persistence.*;
import lombok.Data;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
import java.util.UUID;
/**
* Base entity class with common fields for all entities
* Provides automatic UUID generation and audit timestamps
*/
@Data
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
@Column(name = "id", updatable = false, nullable = false)
private UUID id;
@CreatedDate
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
@Column(name = "updated_at", nullable = false)
private LocalDateTime updatedAt;
}
@@ -0,0 +1,31 @@
package com.ldpv2.domain.entity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* Environment entity representing deployment targets
*/
@Data
@Entity
@Table(name = "environment")
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class Environment extends BaseEntity {
@Column(nullable = false, unique = true, length = 100)
private String name;
@Column(columnDefinition = "TEXT")
private String description;
@Column(name = "is_production", nullable = false)
private Boolean isProduction = false;
@Column(name = "criticality_level")
private Integer criticalityLevel;
}
@@ -0,0 +1,31 @@
package com.ldpv2.domain.entity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* User entity for authentication and authorization
*/
@Data
@Entity
@Table(name = "users")
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class User extends BaseEntity {
@Column(nullable = false, unique = true, length = 50)
private String username;
@Column(nullable = false)
private String password;
@Column(nullable = false, unique = true, length = 255)
private String email;
@Column(nullable = false, length = 20)
private String role; // ADMIN, USER
}
@@ -0,0 +1,27 @@
package com.ldpv2.dto.request;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CreateEnvironmentRequest {
@NotBlank(message = "Name is required")
@Size(max = 100, message = "Name must not exceed 100 characters")
private String name;
private String description;
private Boolean isProduction = false;
@Min(value = 1, message = "Criticality level must be between 1 and 5")
@Max(value = 5, message = "Criticality level must be between 1 and 5")
private Integer criticalityLevel;
}
@@ -0,0 +1,18 @@
package com.ldpv2.dto.request;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginRequest {
@NotBlank(message = "Username is required")
private String username;
@NotBlank(message = "Password is required")
private String password;
}
@@ -0,0 +1,26 @@
package com.ldpv2.dto.request;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RegisterRequest {
@NotBlank(message = "Username is required")
@Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters")
private String username;
@NotBlank(message = "Email is required")
@Email(message = "Email must be valid")
private String email;
@NotBlank(message = "Password is required")
@Size(min = 6, message = "Password must be at least 6 characters")
private String password;
}
@@ -0,0 +1,25 @@
package com.ldpv2.dto.request;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UpdateEnvironmentRequest {
@Size(max = 100, message = "Name must not exceed 100 characters")
private String name;
private String description;
private Boolean isProduction;
@Min(value = 1, message = "Criticality level must be between 1 and 5")
@Max(value = 5, message = "Criticality level must be between 1 and 5")
private Integer criticalityLevel;
}
@@ -0,0 +1,19 @@
package com.ldpv2.dto.response;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AuthResponse {
private String token;
private String type = "Bearer";
private UserResponse user;
public AuthResponse(String token, UserResponse user) {
this.token = token;
this.user = user;
}
}
@@ -0,0 +1,21 @@
package com.ldpv2.dto.response;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.UUID;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class EnvironmentResponse {
private UUID id;
private String name;
private String description;
private Boolean isProduction;
private Integer criticalityLevel;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
@@ -0,0 +1,20 @@
package com.ldpv2.dto.response;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.UUID;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserResponse {
private UUID id;
private String username;
private String email;
private String role;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
@@ -0,0 +1,7 @@
package com.ldpv2.exception;
public class BadRequestException extends RuntimeException {
public BadRequestException(String message) {
super(message);
}
}
@@ -0,0 +1,72 @@
package com.ldpv2.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<Map<String, Object>> handleResourceNotFound(ResourceNotFoundException ex) {
Map<String, Object> error = new HashMap<>();
error.put("timestamp", LocalDateTime.now());
error.put("status", HttpStatus.NOT_FOUND.value());
error.put("message", ex.getMessage());
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(BadRequestException.class)
public ResponseEntity<Map<String, Object>> handleBadRequest(BadRequestException ex) {
Map<String, Object> error = new HashMap<>();
error.put("timestamp", LocalDateTime.now());
error.put("status", HttpStatus.BAD_REQUEST.value());
error.put("message", ex.getMessage());
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, Object>> handleValidationExceptions(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
Map<String, Object> response = new HashMap<>();
response.put("timestamp", LocalDateTime.now());
response.put("status", HttpStatus.BAD_REQUEST.value());
response.put("message", "Validation failed");
response.put("errors", errors);
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(BadCredentialsException.class)
public ResponseEntity<Map<String, Object>> handleBadCredentials(BadCredentialsException ex) {
Map<String, Object> error = new HashMap<>();
error.put("timestamp", LocalDateTime.now());
error.put("status", HttpStatus.UNAUTHORIZED.value());
error.put("message", "Invalid username or password");
return new ResponseEntity<>(error, HttpStatus.UNAUTHORIZED);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<Map<String, Object>> handleGlobalException(Exception ex) {
Map<String, Object> error = new HashMap<>();
error.put("timestamp", LocalDateTime.now());
error.put("status", HttpStatus.INTERNAL_SERVER_ERROR.value());
error.put("message", "An unexpected error occurred");
error.put("details", ex.getMessage());
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@@ -0,0 +1,7 @@
package com.ldpv2.exception;
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
@@ -0,0 +1,17 @@
package com.ldpv2.repository;
import com.ldpv2.domain.entity.Environment;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
import java.util.UUID;
@Repository
public interface EnvironmentRepository extends JpaRepository<Environment, UUID> {
Optional<Environment> findByName(String name);
boolean existsByName(String name);
Page<Environment> findByNameContainingIgnoreCase(String name, Pageable pageable);
}
@@ -0,0 +1,16 @@
package com.ldpv2.repository;
import com.ldpv2.domain.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
import java.util.UUID;
@Repository
public interface UserRepository extends JpaRepository<User, UUID> {
Optional<User> findByUsername(String username);
Optional<User> findByEmail(String email);
boolean existsByUsername(String username);
boolean existsByEmail(String email);
}
@@ -0,0 +1,58 @@
package com.ldpv2.security;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenProvider tokenProvider;
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try {
String jwt = getJwtFromRequest(request);
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
String username = tokenProvider.getUsernameFromToken(jwt);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception ex) {
logger.error("Could not set user authentication in security context", ex);
}
filterChain.doFilter(request, response);
}
private String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
@@ -0,0 +1,60 @@
package com.ldpv2.security;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;
@Component
public class JwtTokenProvider {
@Value("${jwt.secret}")
private String jwtSecret;
@Value("${jwt.expiration}")
private long jwtExpiration;
private SecretKey getSigningKey() {
return Keys.hmacShaKeyFor(jwtSecret.getBytes(StandardCharsets.UTF_8));
}
public String generateToken(Authentication authentication) {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
Date now = new Date();
Date expiryDate = new Date(now.getTime() + jwtExpiration);
return Jwts.builder()
.setSubject(userDetails.getUsername())
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(getSigningKey(), SignatureAlgorithm.HS512)
.compact();
}
public String getUsernameFromToken(String token) {
Claims claims = Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
return claims.getSubject();
}
public boolean validateToken(String token) {
try {
Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException ex) {
return false;
}
}
}
@@ -0,0 +1,31 @@
package com.ldpv2.security;
import com.ldpv2.domain.entity.User;
import com.ldpv2.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.Collections;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
return org.springframework.security.core.userdetails.User.builder()
.username(user.getUsername())
.password(user.getPassword())
.authorities(Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + user.getRole())))
.build();
}
}
@@ -0,0 +1,91 @@
package com.ldpv2.service;
import com.ldpv2.domain.entity.User;
import com.ldpv2.dto.request.LoginRequest;
import com.ldpv2.dto.request.RegisterRequest;
import com.ldpv2.dto.response.AuthResponse;
import com.ldpv2.dto.response.UserResponse;
import com.ldpv2.exception.BadRequestException;
import com.ldpv2.repository.UserRepository;
import com.ldpv2.security.JwtTokenProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class AuthService {
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenProvider tokenProvider;
@Transactional
public AuthResponse register(RegisterRequest request) {
// Check if username exists
if (userRepository.existsByUsername(request.getUsername())) {
throw new BadRequestException("Username already exists");
}
// Check if email exists
if (userRepository.existsByEmail(request.getEmail())) {
throw new BadRequestException("Email already exists");
}
// Create new user
User user = new User();
user.setUsername(request.getUsername());
user.setEmail(request.getEmail());
user.setPassword(passwordEncoder.encode(request.getPassword()));
user.setRole("USER");
user = userRepository.save(user);
// Authenticate the user
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())
);
SecurityContextHolder.getContext().setAuthentication(authentication);
String token = tokenProvider.generateToken(authentication);
return new AuthResponse(token, mapToUserResponse(user));
}
public AuthResponse login(LoginRequest request) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())
);
SecurityContextHolder.getContext().setAuthentication(authentication);
String token = tokenProvider.generateToken(authentication);
User user = userRepository.findByUsername(request.getUsername())
.orElseThrow(() -> new BadRequestException("User not found"));
return new AuthResponse(token, mapToUserResponse(user));
}
private UserResponse mapToUserResponse(User user) {
return new UserResponse(
user.getId(),
user.getUsername(),
user.getEmail(),
user.getRole(),
user.getCreatedAt(),
user.getUpdatedAt()
);
}
}
@@ -0,0 +1,104 @@
package com.ldpv2.service;
import com.ldpv2.domain.entity.Environment;
import com.ldpv2.dto.request.CreateEnvironmentRequest;
import com.ldpv2.dto.request.UpdateEnvironmentRequest;
import com.ldpv2.dto.response.EnvironmentResponse;
import com.ldpv2.exception.BadRequestException;
import com.ldpv2.exception.ResourceNotFoundException;
import com.ldpv2.repository.EnvironmentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.UUID;
@Service
public class EnvironmentService {
@Autowired
private EnvironmentRepository environmentRepository;
@Transactional
public EnvironmentResponse create(CreateEnvironmentRequest request) {
// Check if name already exists
if (environmentRepository.existsByName(request.getName())) {
throw new BadRequestException("Environment with name '" + request.getName() + "' already exists");
}
Environment environment = new Environment();
environment.setName(request.getName());
environment.setDescription(request.getDescription());
environment.setIsProduction(request.getIsProduction() != null ? request.getIsProduction() : false);
environment.setCriticalityLevel(request.getCriticalityLevel());
environment = environmentRepository.save(environment);
return mapToResponse(environment);
}
@Transactional
public EnvironmentResponse update(UUID id, UpdateEnvironmentRequest request) {
Environment environment = environmentRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Environment not found with id: " + id));
// Check if new name already exists (excluding current environment)
if (request.getName() != null && !request.getName().equals(environment.getName())) {
if (environmentRepository.existsByName(request.getName())) {
throw new BadRequestException("Environment with name '" + request.getName() + "' already exists");
}
environment.setName(request.getName());
}
if (request.getDescription() != null) {
environment.setDescription(request.getDescription());
}
if (request.getIsProduction() != null) {
environment.setIsProduction(request.getIsProduction());
}
if (request.getCriticalityLevel() != null) {
environment.setCriticalityLevel(request.getCriticalityLevel());
}
environment = environmentRepository.save(environment);
return mapToResponse(environment);
}
public EnvironmentResponse findById(UUID id) {
Environment environment = environmentRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Environment not found with id: " + id));
return mapToResponse(environment);
}
public Page<EnvironmentResponse> findAll(Pageable pageable) {
return environmentRepository.findAll(pageable).map(this::mapToResponse);
}
public Page<EnvironmentResponse> search(String query, Pageable pageable) {
return environmentRepository.findByNameContainingIgnoreCase(query, pageable)
.map(this::mapToResponse);
}
@Transactional
public void delete(UUID id) {
if (!environmentRepository.existsById(id)) {
throw new ResourceNotFoundException("Environment not found with id: " + id);
}
environmentRepository.deleteById(id);
}
private EnvironmentResponse mapToResponse(Environment environment) {
return new EnvironmentResponse(
environment.getId(),
environment.getName(),
environment.getDescription(),
environment.getIsProduction(),
environment.getCriticalityLevel(),
environment.getCreatedAt(),
environment.getUpdatedAt()
);
}
}
@@ -0,0 +1,45 @@
spring:
application:
name: ldpv2-backend
datasource:
url: jdbc:postgresql://${DB_HOST:localhost}:5432/ldpv2
username: ${DB_USERNAME:ldpv2_user}
password: ${DB_PASSWORD:ldpv2_password}
driver-class-name: org.postgresql.Driver
hikari:
maximum-pool-size: 10
minimum-idle: 5
jpa:
hibernate:
ddl-auto: validate
show-sql: true
properties:
hibernate:
format_sql: true
dialect: org.hibernate.dialect.PostgreSQLDialect
liquibase:
change-log: classpath:db/changelog/db.changelog-master.xml
enabled: true
jwt:
secret: ${JWT_SECRET:your-secret-key-change-in-production-minimum-512-bits-for-hs512-algorithm}
expiration: 3600000
server:
port: 8080
servlet:
context-path: /api
springdoc:
api-docs:
path: /v3/api-docs
swagger-ui:
path: /swagger-ui.html
logging:
level:
com.ldpv2: DEBUG
org.springframework.security: DEBUG
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="initial-data" author="ldpv2-team">
<!-- Insert default admin user -->
<!-- Password: admin123 (hashed with BCrypt) -->
<insert tableName="users">
<column name="username" value="admin"/>
<column name="password" value="$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy"/>
<column name="email" value="admin@ldpv2.com"/>
<column name="role" value="ADMIN"/>
</insert>
<!-- Insert sample environments -->
<insert tableName="environment">
<column name="name" value="PROD-EU"/>
<column name="description" value="Production environment for Europe"/>
<column name="is_production" valueBoolean="true"/>
<column name="criticality_level" valueNumeric="5"/>
</insert>
<insert tableName="environment">
<column name="name" value="PROD-US"/>
<column name="description" value="Production environment for United States"/>
<column name="is_production" valueBoolean="true"/>
<column name="criticality_level" valueNumeric="5"/>
</insert>
<insert tableName="environment">
<column name="name" value="INT"/>
<column name="description" value="Integration testing environment"/>
<column name="is_production" valueBoolean="false"/>
<column name="criticality_level" valueNumeric="3"/>
</insert>
<insert tableName="environment">
<column name="name" value="DEV"/>
<column name="description" value="Development environment"/>
<column name="is_production" valueBoolean="false"/>
<column name="criticality_level" valueNumeric="1"/>
</insert>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<!-- Story 0: Foundation -->
<include file="db/changelog/v1.0/001-create-user-table.xml"/>
<include file="db/changelog/v1.0/002-create-environment-table.xml"/>
<include file="db/changelog/data/initial-data.xml"/>
</databaseChangeLog>
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="001-create-user-table" author="ldpv2-team">
<!-- Enable UUID extension -->
<sql>CREATE EXTENSION IF NOT EXISTS "uuid-ossp";</sql>
<!-- Create users table -->
<createTable tableName="users">
<column name="id" type="UUID" defaultValueComputed="uuid_generate_v4()">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="username" type="VARCHAR(50)">
<constraints nullable="false" unique="true"/>
</column>
<column name="password" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="email" type="VARCHAR(255)">
<constraints nullable="false" unique="true"/>
</column>
<column name="role" type="VARCHAR(20)">
<constraints nullable="false"/>
</column>
<column name="created_at" type="TIMESTAMP" defaultValueComputed="CURRENT_TIMESTAMP">
<constraints nullable="false"/>
</column>
<column name="updated_at" type="TIMESTAMP" defaultValueComputed="CURRENT_TIMESTAMP">
<constraints nullable="false"/>
</column>
</createTable>
<!-- Create indexes -->
<createIndex tableName="users" indexName="idx_users_username">
<column name="username"/>
</createIndex>
<createIndex tableName="users" indexName="idx_users_email">
<column name="email"/>
</createIndex>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="002-create-environment-table" author="ldpv2-team">
<!-- Create environment table -->
<createTable tableName="environment">
<column name="id" type="UUID" defaultValueComputed="uuid_generate_v4()">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="name" type="VARCHAR(100)">
<constraints nullable="false" unique="true"/>
</column>
<column name="description" type="TEXT"/>
<column name="is_production" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="criticality_level" type="INTEGER"/>
<column name="created_at" type="TIMESTAMP" defaultValueComputed="CURRENT_TIMESTAMP">
<constraints nullable="false"/>
</column>
<column name="updated_at" type="TIMESTAMP" defaultValueComputed="CURRENT_TIMESTAMP">
<constraints nullable="false"/>
</column>
</createTable>
<!-- Create indexes -->
<createIndex tableName="environment" indexName="idx_environment_name">
<column name="name"/>
</createIndex>
<createIndex tableName="environment" indexName="idx_environment_is_production">
<column name="is_production"/>
</createIndex>
</changeSet>
</databaseChangeLog>