chore: initial commit — UdyamLinc backend, phases A-E
Spring Boot 4 / Java 17 / Postgres. Masters and serving (A), Agmarknet and climate ingestion (B), feature store with 14 derivations (C), SpEL rule engine and risk scoring (D). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
package com.lincfusion.udyamlinc.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
/**
|
||||
* The HTTP client used for external feeds, and the scheduler that drives them.
|
||||
*
|
||||
* <p>Short, explicit timeouts are the point: a government endpoint that hangs
|
||||
* must fail the nightly job quickly, not hold a thread until someone notices.
|
||||
* Nothing in the request path uses this client — see ARCHITECTURE §2.1.
|
||||
*/
|
||||
@Configuration
|
||||
@EnableScheduling
|
||||
public class ExternalFeedConfig {
|
||||
|
||||
@Value("${udyam.ingestion.http.connect-timeout-ms:10000}")
|
||||
private int connectTimeoutMs;
|
||||
|
||||
@Value("${udyam.ingestion.http.read-timeout-ms:30000}")
|
||||
private int readTimeoutMs;
|
||||
|
||||
@Bean("externalFeedClient")
|
||||
public RestClient externalFeedClient(RestClient.Builder builder) {
|
||||
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
|
||||
factory.setConnectTimeout(connectTimeoutMs);
|
||||
factory.setReadTimeout(readTimeoutMs);
|
||||
return builder.requestFactory(factory).build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.lincfusion.udyamlinc.config;
|
||||
|
||||
import com.lincfusion.udyamlinc.security.JwtAuthFilter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
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.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;
|
||||
import java.util.List;
|
||||
|
||||
/** Stateless JWT security. /auth/** is open; everything else needs a token. */
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@RequiredArgsConstructor
|
||||
public class SecurityConfig {
|
||||
|
||||
private final JwtAuthFilter jwtAuthFilter;
|
||||
|
||||
@Value("${udyam.cors.allowed-origins}")
|
||||
private String allowedOrigins;
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
||||
.csrf(csrf -> csrf.disable())
|
||||
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers("/auth/**", "/health").permitAll()
|
||||
// The officer console and the enterprise app are separate surfaces.
|
||||
.requestMatchers("/officer/**", "/forecast/**").hasAuthority("SCOPE_OFFICER")
|
||||
.requestMatchers("/app/**").hasAuthority("SCOPE_ENTERPRISE")
|
||||
// Any officer may read the configuration; only a supervisor may change it.
|
||||
.requestMatchers(HttpMethod.GET, "/admin/**").hasAuthority("SCOPE_OFFICER")
|
||||
.requestMatchers("/admin/**").hasAuthority("ROLE_SUPERVISOR")
|
||||
.anyRequest().authenticated())
|
||||
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CorsConfigurationSource corsConfigurationSource() {
|
||||
CorsConfiguration cfg = new CorsConfiguration();
|
||||
cfg.setAllowedOrigins(Arrays.asList(allowedOrigins.split(",")));
|
||||
cfg.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
|
||||
cfg.setAllowedHeaders(List.of("*"));
|
||||
cfg.setAllowCredentials(true);
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", cfg);
|
||||
return source;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user