fix timeout, cors and token issues

This commit is contained in:
Krrish Ghimire
2026-07-10 11:39:19 +05:45
parent 3e77b54d71
commit 24ca1521b4
8 changed files with 55 additions and 13 deletions

View File

@@ -5,7 +5,6 @@ import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
@@ -13,7 +12,6 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
@@ -33,9 +31,6 @@ public class SecurityConfig {
private final JwtConfig jwtConfig; private final JwtConfig jwtConfig;
@Value("${indie.cors.allowed-origins:http://localhost:4200}")
private String[] allowedOrigins;
public SecurityConfig(JwtConfig jwtConfig) { public SecurityConfig(JwtConfig jwtConfig) {
this.jwtConfig = jwtConfig; this.jwtConfig = jwtConfig;
} }
@@ -72,7 +67,7 @@ public class SecurityConfig {
@Bean @Bean
public CorsConfigurationSource corsConfigurationSource() { public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration(); CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of(allowedOrigins)); config.setAllowedOriginPatterns(List.of("*"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")); config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
config.setAllowedHeaders(List.of("*")); config.setAllowedHeaders(List.of("*"));
config.setAllowCredentials(true); config.setAllowCredentials(true);

View File

@@ -72,7 +72,7 @@ public class LLMController {
SiteStructure structure = llmService.generateSite( SiteStructure structure = llmService.generateSite(
principal.id(), siteId, prompt, references, provider, apiKey, baseUrl, model); principal.id(), siteId, prompt, references, provider, apiKey, baseUrl, model);
return ResponseEntity.ok(structure); return ResponseEntity.ok(structure);
} catch (IllegalStateException e) { } catch (IllegalStateException | IllegalArgumentException e) {
return ResponseEntity.badRequest().body(Map.of("error", e.getMessage())); return ResponseEntity.badRequest().body(Map.of("error", e.getMessage()));
} }
} }
@@ -130,7 +130,7 @@ public class LLMController {
SiteStructure structure = llmService.refineSite(principal.id(), siteId, SiteStructure structure = llmService.refineSite(principal.id(), siteId,
currentStructure, prompt, references, provider, apiKey, baseUrl, model); currentStructure, prompt, references, provider, apiKey, baseUrl, model);
return ResponseEntity.ok(structure); return ResponseEntity.ok(structure);
} catch (IllegalStateException e) { } catch (IllegalStateException | IllegalArgumentException e) {
return ResponseEntity.badRequest().body(Map.of("error", e.getMessage())); return ResponseEntity.badRequest().body(Map.of("error", e.getMessage()));
} }
} }

View File

@@ -13,7 +13,7 @@ public class LLMOptions {
private double temperature = 0.9; private double temperature = 0.9;
@Builder.Default @Builder.Default
private int maxTokens = 4096; private int maxTokens = 16384;
@Builder.Default @Builder.Default
private int timeoutSeconds = 60; private int timeoutSeconds = 60;

View File

@@ -6,6 +6,7 @@ import com.krrishg.dto.LLMResult;
import com.krrishg.dto.Reference; import com.krrishg.dto.Reference;
import com.krrishg.dto.SiteStructure; import com.krrishg.dto.SiteStructure;
import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.ResourceAccessException;
import com.krrishg.model.GenerationVersion; import com.krrishg.model.GenerationVersion;
import com.krrishg.model.User; import com.krrishg.model.User;
import com.krrishg.repository.UserRepository; import com.krrishg.repository.UserRepository;
@@ -204,6 +205,9 @@ public class LLMService {
LLMProvider p = providerFactory.createProvider(provider, apiKey, baseUrl, model); LLMProvider p = providerFactory.createProvider(provider, apiKey, baseUrl, model);
try { try {
return p.generate(systemPrompt, userPrompt, references, options); return p.generate(systemPrompt, userPrompt, references, options);
} catch (ResourceAccessException e) {
throw new IllegalStateException(
provider + " timed out. Please try again or use a simpler prompt.");
} catch (HttpClientErrorException e) { } catch (HttpClientErrorException e) {
String detail = e.getResponseBodyAsString(); String detail = e.getResponseBodyAsString();
String msg = switch (e.getStatusCode().value()) { String msg = switch (e.getStatusCode().value()) {

View File

@@ -14,14 +14,17 @@ public class ProviderFactory {
private final RestTemplateBuilder restTemplateBuilder; private final RestTemplateBuilder restTemplateBuilder;
private final Map<String, ProviderConfig> providerConfigs; private final Map<String, ProviderConfig> providerConfigs;
private final Duration readTimeout;
public ProviderFactory( public ProviderFactory(
@Value("${indie.llm.providers.gemini.model:gemini-3.1-flash-lite}") String geminiModel, @Value("${indie.llm.providers.gemini.model:gemini-3.1-flash-lite}") String geminiModel,
@Value("${indie.llm.providers.gemini.url:https://generativelanguage.googleapis.com/v1beta/models}") String geminiUrl, @Value("${indie.llm.providers.gemini.url:https://generativelanguage.googleapis.com/v1beta/models}") String geminiUrl,
@Value("${indie.llm.providers.openai.model:gpt-4o-mini}") String openaiModel, @Value("${indie.llm.providers.openai.model:gpt-4o-mini}") String openaiModel,
@Value("${indie.llm.providers.openai.url:https://api.openai.com/v1/chat/completions}") String openaiUrl, @Value("${indie.llm.providers.openai.url:https://api.openai.com/v1/chat/completions}") String openaiUrl,
@Value("${indie.llm.read-timeout:180s}") Duration readTimeout,
RestTemplateBuilder restTemplateBuilder) { RestTemplateBuilder restTemplateBuilder) {
this.restTemplateBuilder = restTemplateBuilder; this.restTemplateBuilder = restTemplateBuilder;
this.readTimeout = readTimeout;
this.providerConfigs = Map.of( this.providerConfigs = Map.of(
"gemini", new ProviderConfig(geminiModel, geminiUrl), "gemini", new ProviderConfig(geminiModel, geminiUrl),
"openai", new ProviderConfig(openaiModel, openaiUrl) "openai", new ProviderConfig(openaiModel, openaiUrl)
@@ -52,7 +55,7 @@ public class ProviderFactory {
} }
RestTemplate rt = restTemplateBuilder RestTemplate rt = restTemplateBuilder
.setConnectTimeout(Duration.ofSeconds(30)) .setConnectTimeout(Duration.ofSeconds(30))
.setReadTimeout(Duration.ofSeconds(60)) .setReadTimeout(readTimeout)
.build(); .build();
return switch (name) { return switch (name) {
case "gemini" -> new GeminiProvider(apiKey, resolvedModel, url, rt); case "gemini" -> new GeminiProvider(apiKey, resolvedModel, url, rt);

View File

@@ -55,9 +55,6 @@ indie:
repos-path: ${INDIE_GIT_REPOS_PATH:/home/krrish/git/sites/} repos-path: ${INDIE_GIT_REPOS_PATH:/home/krrish/git/sites/}
author-name: ${INDIE_GIT_AUTHOR_NAME:Indie Platform} author-name: ${INDIE_GIT_AUTHOR_NAME:Indie Platform}
author-email: ${INDIE_GIT_AUTHOR_EMAIL:git@indie.local} author-email: ${INDIE_GIT_AUTHOR_EMAIL:git@indie.local}
cors:
allowed-origins: ${INDIE_CORS_ORIGINS:http://localhost:4200,http://localhost:3000}
storage: storage:
endpoint: ${INDIE_STORAGE_ENDPOINT:http://localhost:9000} endpoint: ${INDIE_STORAGE_ENDPOINT:http://localhost:9000}
region: ${INDIE_STORAGE_REGION:auto} region: ${INDIE_STORAGE_REGION:auto}
@@ -74,6 +71,7 @@ indie:
verification-token-expiration-minutes: ${INDIE_VERIFICATION_TOKEN_EXPIRATION:1440} verification-token-expiration-minutes: ${INDIE_VERIFICATION_TOKEN_EXPIRATION:1440}
llm: llm:
read-timeout: ${INDIE_LLM_READ_TIMEOUT:180s}
rate-limit: rate-limit:
max-requests-per-hour: ${INDIE_LLM_RATE_LIMIT:10} max-requests-per-hour: ${INDIE_LLM_RATE_LIMIT:10}
providers: providers:

40
docker-compose.v3.yml Normal file
View File

@@ -0,0 +1,40 @@
version: '3'
services:
postgres:
image: postgres:16-alpine
network_mode: host
environment:
POSTGRES_DB: indie
POSTGRES_USER: indie
POSTGRES_PASSWORD: indie_dev
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U indie"]
interval: 5s
timeout: 5s
retries: 5
mongodb:
image: mongo:7
network_mode: host
environment:
MONGO_INITDB_DATABASE: indie
volumes:
- mongodata:/data/db
minio:
image: minio/minio:latest
network_mode: host
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: indieadmin
MINIO_ROOT_PASSWORD: indiesecret
volumes:
- miniodata:/data
volumes:
pgdata:
mongodata:
miniodata:

View File

@@ -10,5 +10,7 @@ server {
location /api/ { location /api/ {
proxy_pass http://backend:8080; proxy_pass http://backend:8080;
proxy_read_timeout 180s;
proxy_connect_timeout 30s;
} }
} }