autocomit
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user