publish to github or gitlab pages

This commit is contained in:
Krrish Ghimire
2026-07-04 23:38:17 +05:45
parent 0fea26d653
commit bfc61ed660
93 changed files with 2367 additions and 193 deletions

View File

@@ -1,4 +1,4 @@
package com.indie;
package com.krrishg;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

View File

@@ -1,4 +1,4 @@
package com.indie.config;
package com.krrishg.config;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Value;

View File

@@ -1,5 +1,7 @@
package com.indie.config;
package com.krrishg.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.validation.FieldError;
@@ -12,6 +14,8 @@ import java.util.stream.Collectors;
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(IllegalArgumentException.class)
public ProblemDetail handleBadRequest(IllegalArgumentException ex) {
return ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, ex.getMessage());
@@ -27,6 +31,7 @@ public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ProblemDetail handleGeneral(Exception ex) {
log.error("Unhandled exception", ex);
return ProblemDetail.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR,
"An unexpected error occurred");
}

View File

@@ -1,4 +1,4 @@
package com.indie.config;
package com.krrishg.config;
import org.springframework.security.authentication.AbstractAuthenticationToken;

View File

@@ -1,6 +1,6 @@
package com.indie.config;
package com.krrishg.config;
import com.indie.model.User;
import com.krrishg.model.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import jakarta.annotation.PostConstruct;

View File

@@ -1,4 +1,4 @@
package com.indie.config;
package com.krrishg.config;
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket;

View File

@@ -1,7 +1,7 @@
package com.indie.config;
package com.krrishg.config;
import com.indie.service.OAuth2SuccessHandler;
import com.indie.service.OAuth2UserService;
import com.krrishg.service.OAuth2SuccessHandler;
import com.krrishg.service.OAuth2UserService;
import io.jsonwebtoken.Claims;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;

View File

@@ -0,0 +1,41 @@
package com.krrishg.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.S3Configuration;
import java.net.URI;
@Configuration
public class StorageConfig {
@Value("${indie.storage.endpoint}")
private String endpoint;
@Value("${indie.storage.region}")
private String region;
@Value("${indie.storage.access-key}")
private String accessKey;
@Value("${indie.storage.secret-key}")
private String secretKey;
@Bean
public S3Client s3Client() {
return S3Client.builder()
.endpointOverride(URI.create(endpoint))
.region(Region.of(region))
.credentialsProvider(StaticCredentialsProvider.create(
AwsBasicCredentials.create(accessKey, secretKey)))
.serviceConfiguration(S3Configuration.builder()
.pathStyleAccessEnabled(true)
.build())
.build();
}
}

View File

@@ -1,6 +1,6 @@
package com.indie.config;
package com.krrishg.config;
import com.indie.model.User;
import com.krrishg.model.User;
import io.jsonwebtoken.Claims;
public interface TokenService {

View File

@@ -1,4 +1,4 @@
package com.indie.config;
package com.krrishg.config;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

View File

@@ -1,10 +1,10 @@
package com.indie.controller;
package com.krrishg.controller;
import com.indie.dto.AuthResponse;
import com.indie.dto.LoginRequest;
import com.indie.dto.RefreshTokenRequest;
import com.indie.dto.RegisterRequest;
import com.indie.service.AuthService;
import com.krrishg.dto.AuthResponse;
import com.krrishg.dto.LoginRequest;
import com.krrishg.dto.RefreshTokenRequest;
import com.krrishg.dto.RegisterRequest;
import com.krrishg.service.AuthService;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

View File

@@ -0,0 +1,44 @@
package com.krrishg.controller;
import com.krrishg.config.UserPrincipal;
import com.krrishg.service.DeploymentService;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
import java.util.UUID;
@RestController
@RequestMapping("/api/sites/{id}")
public class DeploymentController {
private final DeploymentService deploymentService;
public DeploymentController(DeploymentService deploymentService) {
this.deploymentService = deploymentService;
}
@PostMapping("/publish")
public ResponseEntity<?> publish(
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable UUID id) {
try {
String url = deploymentService.publish(id);
return ResponseEntity.ok(Map.of(
"message", "Site published successfully",
"url", url
));
} catch (IllegalStateException e) {
return ResponseEntity.badRequest().body(Map.of("error", e.getMessage()));
}
}
@PostMapping("/unpublish")
public ResponseEntity<?> unpublish(
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable UUID id) {
deploymentService.unpublish(id);
return ResponseEntity.ok(Map.of("message", "Site unpublished successfully"));
}
}

View File

@@ -0,0 +1,62 @@
package com.krrishg.controller;
import com.krrishg.config.UserPrincipal;
import com.krrishg.dto.SiteStructure;
import com.krrishg.model.Site;
import com.krrishg.repository.SiteRepository;
import com.krrishg.service.ExportService;
import com.krrishg.service.LLMService;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.UUID;
@RestController
@RequestMapping("/api/sites/{id}")
public class ExportController {
private final ExportService exportService;
private final LLMService llmService;
private final SiteRepository siteRepository;
public ExportController(ExportService exportService, LLMService llmService,
SiteRepository siteRepository) {
this.exportService = exportService;
this.llmService = llmService;
this.siteRepository = siteRepository;
}
@GetMapping("/export")
public ResponseEntity<byte[]> exportSite(
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable UUID id) {
Site site = siteRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("Site not found"));
var versions = llmService.getVersions(id);
if (versions.isEmpty()) {
return ResponseEntity.badRequest().build();
}
SiteStructure structure = llmService.restoreVersion(versions.get(0).getId());
try {
byte[] zip = exportService.exportSite(structure);
String filename = site.getSubdomain() != null ? site.getSubdomain() : site.getName().replaceAll("\\s+", "-");
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + sanitizeFilename(filename) + "-export.zip\"")
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.contentLength(zip.length)
.body(zip);
} catch (Exception e) {
return ResponseEntity.internalServerError().build();
}
}
private String sanitizeFilename(String name) {
return name.replaceAll("[^a-zA-Z0-9_-]", "").toLowerCase();
}
}

View File

@@ -0,0 +1,102 @@
package com.krrishg.controller;
import com.krrishg.config.UserPrincipal;
import com.krrishg.model.GitRemote;
import com.krrishg.repository.GitRemoteRepository;
import com.krrishg.service.GitService;
import com.krrishg.service.KeyManager;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@RestController
@RequestMapping("/api/sites/{id}/git")
public class GitController {
private final GitService gitService;
private final GitRemoteRepository gitRemoteRepository;
private final KeyManager keyManager;
public GitController(GitService gitService,
GitRemoteRepository gitRemoteRepository,
KeyManager keyManager) {
this.gitService = gitService;
this.gitRemoteRepository = gitRemoteRepository;
this.keyManager = keyManager;
}
@Transactional
@PostMapping("/connect")
public ResponseEntity<?> connectRemote(
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable UUID id,
@RequestBody Map<String, String> request) {
String provider = request.get("provider");
String remoteUrl = request.get("remoteUrl");
String token = request.get("token");
if (provider == null || remoteUrl == null || token == null) {
return ResponseEntity.badRequest().body(Map.of("error", "provider, remoteUrl, and token are required"));
}
GitRemote.GitProvider gitProvider;
try {
gitProvider = GitRemote.GitProvider.valueOf(provider.toUpperCase());
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(Map.of("error", "Invalid provider. Must be GITHUB or GITLAB"));
}
gitRemoteRepository.findBySiteId(id).ifPresent(existing ->
gitRemoteRepository.delete(existing));
String siteId = id.toString();
gitService.initRepo(siteId);
String publicKey = keyManager.getPublicKey(siteId);
GitRemote remote = GitRemote.builder()
.siteId(id)
.provider(gitProvider)
.remoteUrl(remoteUrl)
.encryptedToken(token)
.build();
gitRemoteRepository.save(remote);
return ResponseEntity.ok(Map.of(
"message", "Remote connected successfully",
"provider", provider,
"remoteUrl", remoteUrl,
"publicKey", publicKey
));
}
@Transactional
@PostMapping("/disconnect")
public ResponseEntity<?> disconnectRemote(
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable UUID id) {
gitRemoteRepository.deleteBySiteId(id);
keyManager.deleteKeys(id.toString());
return ResponseEntity.ok(Map.of("message", "Remote disconnected"));
}
@GetMapping("/status")
public ResponseEntity<?> getStatus(
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable UUID id) {
var remote = gitRemoteRepository.findBySiteId(id);
var log = gitService.getLog(id.toString(), 10);
var response = new HashMap<String, Object>();
response.put("connected", remote.isPresent());
response.put("provider", remote.map(r -> r.getProvider().name()).orElse(null));
response.put("remoteUrl", remote.map(GitRemote::getRemoteUrl).orElse(null));
response.put("hasKeys", keyManager.hasKeyPair(id.toString()));
response.put("commits", log);
return ResponseEntity.ok(response);
}
}

View File

@@ -1,11 +1,11 @@
package com.indie.controller;
package com.krrishg.controller;
import com.indie.config.RateLimitConfig;
import com.indie.config.UserPrincipal;
import com.indie.dto.Reference;
import com.indie.dto.SiteStructure;
import com.indie.model.GenerationVersion;
import com.indie.service.LLMService;
import com.krrishg.config.RateLimitConfig;
import com.krrishg.config.UserPrincipal;
import com.krrishg.dto.Reference;
import com.krrishg.dto.SiteStructure;
import com.krrishg.model.GenerationVersion;
import com.krrishg.service.LLMService;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpStatus;

View File

@@ -0,0 +1,49 @@
package com.krrishg.controller;
import com.krrishg.config.UserPrincipal;
import com.krrishg.service.MediaService;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.Map;
import java.util.UUID;
@RestController
@RequestMapping("/api/media")
public class MediaController {
private final MediaService mediaService;
public MediaController(MediaService mediaService) {
this.mediaService = mediaService;
}
@PostMapping("/upload")
public ResponseEntity<?> upload(
@AuthenticationPrincipal UserPrincipal principal,
@RequestParam("file") MultipartFile file,
@RequestParam("siteId") UUID siteId) {
try {
MediaService.MediaUploadResult result = mediaService.upload(file, siteId);
return ResponseEntity.ok(Map.of(
"key", result.key(),
"url", result.url(),
"originalFilename", result.originalFilename(),
"contentType", result.contentType(),
"size", result.size()
));
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(Map.of("error", e.getMessage()));
}
}
@GetMapping("/{key}")
public ResponseEntity<?> getMediaUrl(
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String key) {
String url = mediaService.getPublicUrl(key);
return ResponseEntity.ok(Map.of("key", key, "url", url));
}
}

View File

@@ -1,10 +1,10 @@
package com.indie.controller;
package com.krrishg.controller;
import com.indie.config.UserPrincipal;
import com.indie.dto.SiteRequest;
import com.indie.dto.SiteResponse;
import com.indie.model.Site;
import com.indie.repository.SiteRepository;
import com.krrishg.config.UserPrincipal;
import com.krrishg.dto.SiteRequest;
import com.krrishg.dto.SiteResponse;
import com.krrishg.model.Site;
import com.krrishg.repository.SiteRepository;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

View File

@@ -1,6 +1,6 @@
package com.indie.dto;
package com.krrishg.dto;
import com.indie.model.AuthProvider;
import com.krrishg.model.AuthProvider;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;

View File

@@ -1,4 +1,4 @@
package com.indie.dto;
package com.krrishg.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;

View File

@@ -1,4 +1,4 @@
package com.indie.dto;
package com.krrishg.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;

View File

@@ -1,4 +1,4 @@
package com.indie.dto;
package com.krrishg.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;

View File

@@ -1,4 +1,4 @@
package com.indie.dto;
package com.krrishg.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;

View File

@@ -1,4 +1,4 @@
package com.indie.dto;
package com.krrishg.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;

View File

@@ -1,4 +1,4 @@
package com.indie.dto;
package com.krrishg.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;

View File

@@ -1,4 +1,4 @@
package com.indie.dto;
package com.krrishg.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;

View File

@@ -1,7 +1,7 @@
package com.indie.dto;
package com.krrishg.dto;
import com.indie.model.Site;
import com.indie.model.SiteStatus;
import com.krrishg.model.Site;
import com.krrishg.model.SiteStatus;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@@ -19,6 +19,7 @@ public class SiteResponse {
private String name;
private String description;
private String subdomain;
private String publishedUrl;
private SiteStatus status;
private LocalDateTime publishedAt;
private LocalDateTime createdAt;
@@ -31,6 +32,7 @@ public class SiteResponse {
.name(site.getName())
.description(site.getDescription())
.subdomain(site.getSubdomain())
.publishedUrl(site.getPublishedUrl())
.status(site.getStatus())
.publishedAt(site.getPublishedAt())
.createdAt(site.getCreatedAt())

View File

@@ -1,4 +1,4 @@
package com.indie.dto;
package com.krrishg.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;

View File

@@ -0,0 +1,74 @@
package com.krrishg.engine;
import org.springframework.stereotype.Component;
@Component
public class JsBundle {
public String compile() {
return """
document.addEventListener('DOMContentLoaded', function() {
// Nav toggle
var toggle = document.querySelector('.nav-toggle');
var nav = document.querySelector('nav');
if (toggle && nav) {
toggle.addEventListener('click', function() {
nav.classList.toggle('open');
});
}
// Smooth scroll
document.querySelectorAll('a[href^="#"]').forEach(function(anchor) {
anchor.addEventListener('click', function(e) {
var target = document.querySelector(this.getAttribute('href'));
if (target) {
e.preventDefault();
target.scrollIntoView({ behavior: 'smooth' });
}
});
});
// Lightbox
var lightboxOverlay = null;
document.querySelectorAll('.lightbox-trigger').forEach(function(img) {
img.addEventListener('click', function() {
if (!lightboxOverlay) {
lightboxOverlay = document.createElement('div');
lightboxOverlay.className = 'lightbox-overlay';
lightboxOverlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.9);display:flex;align-items:center;justify-content:center;z-index:9999;cursor:pointer;';
lightboxOverlay.addEventListener('click', function() {
lightboxOverlay.style.display = 'none';
});
document.body.appendChild(lightboxOverlay);
}
var fullImg = lightboxOverlay.querySelector('img');
if (!fullImg) {
fullImg = document.createElement('img');
fullImg.style.cssText = 'max-width:90%;max-height:90%;object-fit:contain;';
lightboxOverlay.appendChild(fullImg);
}
fullImg.src = this.src;
lightboxOverlay.style.display = 'flex';
});
});
// Contact form handler
document.querySelectorAll('form[data-static]').forEach(function(form) {
form.addEventListener('submit', function(e) {
e.preventDefault();
var btn = form.querySelector('.form-submit');
if (btn) {
var original = btn.textContent;
btn.textContent = 'Message sent!';
btn.disabled = true;
setTimeout(function() {
btn.textContent = original;
btn.disabled = false;
}, 3000);
}
});
});
});
""";
}
}

View File

@@ -0,0 +1,31 @@
package com.krrishg.engine;
import org.springframework.stereotype.Component;
@Component
public class StyleCompiler {
public String compile() {
return """
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: var(--font-body, 'Inter', sans-serif);
color: var(--color-text, #1a1a1a);
background: var(--color-background, #ffffff);
line-height: 1.6;
-webkit-font-smoothing: antialiased;
}
.container { max-width: var(--spacing-containerWidth, 1100px); margin: 0 auto; padding: 0 1.5rem; }
section { padding: var(--spacing-sectionPadding, 3rem) 0; }
a { color: inherit; text-decoration: none; }
img { max-width: 100%; height: auto; }
@media (max-width: 768px) {
.container { padding: 0 1rem; }
section { padding: 2rem 0; }
}
@media (max-width: 480px) {
section { padding: 1.5rem 0; }
}
""";
}
}

View File

@@ -1,4 +1,4 @@
package com.indie.model;
package com.krrishg.model;
public enum AuthProvider {
LOCAL, GOOGLE, GITHUB, OPENID

View File

@@ -1,4 +1,4 @@
package com.indie.model;
package com.krrishg.model;
import lombok.AllArgsConstructor;
import lombok.Builder;

View File

@@ -0,0 +1,63 @@
package com.krrishg.model;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.UUID;
@Entity
@Table(name = "git_remotes")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class GitRemote {
@Id
private UUID id;
@Column(nullable = false)
private UUID siteId;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private GitProvider provider;
@Column(nullable = false)
private String remoteUrl;
@Column(nullable = false)
private String encryptedToken;
@Column(nullable = false)
@Builder.Default
private String defaultBranch = "main";
@Column(nullable = false, updatable = false)
private LocalDateTime createdAt;
@Column(nullable = false)
private LocalDateTime updatedAt;
@PrePersist
void onCreate() {
if (id == null) {
id = UUID.randomUUID();
}
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}
@PreUpdate
void onUpdate() {
updatedAt = LocalDateTime.now();
}
public enum GitProvider {
GITHUB, GITLAB
}
}

View File

@@ -1,4 +1,4 @@
package com.indie.model;
package com.krrishg.model;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
@@ -32,6 +32,8 @@ public class Site {
@Column(unique = true)
private String subdomain;
private String publishedUrl;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
@Builder.Default

View File

@@ -1,4 +1,4 @@
package com.indie.model;
package com.krrishg.model;
public enum SiteStatus {
DRAFT, PUBLISHED

View File

@@ -1,4 +1,4 @@
package com.indie.model;
package com.krrishg.model;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;

View File

@@ -0,0 +1,16 @@
package com.krrishg.repository;
import com.krrishg.model.GitRemote;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
import java.util.UUID;
public interface GitRemoteRepository extends JpaRepository<GitRemote, UUID> {
Optional<GitRemote> findBySiteId(UUID siteId);
boolean existsBySiteId(UUID siteId);
void deleteBySiteId(UUID siteId);
}

View File

@@ -1,6 +1,6 @@
package com.indie.repository;
package com.krrishg.repository;
import com.indie.model.Site;
import com.krrishg.model.Site;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;

View File

@@ -1,7 +1,7 @@
package com.indie.repository;
package com.krrishg.repository;
import com.indie.model.AuthProvider;
import com.indie.model.User;
import com.krrishg.model.AuthProvider;
import com.krrishg.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;

View File

@@ -1,13 +1,13 @@
package com.indie.service;
package com.krrishg.service;
import com.indie.config.TokenService;
import com.indie.dto.AuthResponse;
import com.indie.dto.LoginRequest;
import com.indie.dto.RefreshTokenRequest;
import com.indie.dto.RegisterRequest;
import com.indie.model.AuthProvider;
import com.indie.model.User;
import com.indie.repository.UserRepository;
import com.krrishg.config.TokenService;
import com.krrishg.dto.AuthResponse;
import com.krrishg.dto.LoginRequest;
import com.krrishg.dto.RefreshTokenRequest;
import com.krrishg.dto.RegisterRequest;
import com.krrishg.model.AuthProvider;
import com.krrishg.model.User;
import com.krrishg.repository.UserRepository;
import io.jsonwebtoken.Claims;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;

View File

@@ -1,6 +1,6 @@
package com.indie.service;
package com.krrishg.service;
import com.indie.model.User;
import com.krrishg.model.User;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.core.user.OAuth2User;

View File

@@ -0,0 +1,85 @@
package com.krrishg.service;
import com.krrishg.dto.SiteStructure;
import com.krrishg.model.GitRemote;
import com.krrishg.model.Site;
import com.krrishg.model.SiteStatus;
import com.krrishg.repository.GitRemoteRepository;
import com.krrishg.repository.SiteRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.UUID;
@Service
public class DeploymentService {
private static final Logger log = LoggerFactory.getLogger(DeploymentService.class);
private final RenderService renderService;
private final GitService gitService;
private final SiteRepository siteRepository;
private final LLMService llmService;
private final GitRemoteRepository gitRemoteRepository;
public DeploymentService(RenderService renderService,
GitService gitService,
SiteRepository siteRepository,
LLMService llmService,
GitRemoteRepository gitRemoteRepository) {
this.renderService = renderService;
this.gitService = gitService;
this.siteRepository = siteRepository;
this.llmService = llmService;
this.gitRemoteRepository = gitRemoteRepository;
}
public String publish(UUID siteId) {
Site site = siteRepository.findById(siteId)
.orElseThrow(() -> new IllegalArgumentException("Site not found"));
SiteStructure structure = getLatestVersion(siteId);
if (structure == null) {
throw new IllegalStateException("No generation versions found for site " + siteId);
}
GitRemote remote = gitRemoteRepository.findBySiteId(siteId).orElse(null);
if (remote == null) {
throw new IllegalStateException("No git remote configured. Connect a GitHub or GitLab remote first.");
}
String url = gitService.deployToPages(siteId.toString(), remote, structure);
site.setStatus(SiteStatus.PUBLISHED);
site.setPublishedAt(LocalDateTime.now());
site.setPublishedUrl(url);
siteRepository.save(site);
log.info("Published site {} to {} at {}", siteId, remote.getProvider(), url);
return url;
}
public void unpublish(UUID siteId) {
Site site = siteRepository.findById(siteId)
.orElseThrow(() -> new IllegalArgumentException("Site not found"));
gitService.removePages(siteId.toString());
site.setStatus(SiteStatus.DRAFT);
site.setPublishedAt(null);
site.setPublishedUrl(null);
siteRepository.save(site);
log.info("Unpublished site {}", siteId);
}
private SiteStructure getLatestVersion(UUID siteId) {
var versions = llmService.getVersions(siteId);
if (versions.isEmpty()) {
return null;
}
return llmService.restoreVersion(versions.get(0).getId());
}
}

View File

@@ -0,0 +1,60 @@
package com.krrishg.service;
import com.krrishg.model.Site;
import com.krrishg.repository.SiteRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
public class DomainService {
private static final Logger log = LoggerFactory.getLogger(DomainService.class);
private final SiteRepository siteRepository;
private final String siteDomain;
public DomainService(SiteRepository siteRepository,
@Value("${indie.domain}") String siteDomain) {
this.siteRepository = siteRepository;
this.siteDomain = siteDomain;
}
public Optional<Site> resolveSite(String hostname) {
String subdomain = extractSubdomain(hostname);
if (subdomain != null) {
return siteRepository.findBySubdomain(subdomain);
}
return Optional.empty();
}
private String extractSubdomain(String hostname) {
if (hostname == null || hostname.isBlank()) {
return null;
}
String lower = hostname.toLowerCase();
String suffix = "." + siteDomain;
if (lower.endsWith(suffix)) {
String subdomain = lower.substring(0, lower.indexOf(suffix));
if (!subdomain.isEmpty() && !subdomain.contains(".")) {
return subdomain;
}
}
return null;
}
public boolean isSubdomainAvailable(String subdomain) {
return !siteRepository.existsBySubdomain(subdomain);
}
public String getSiteUrl(Site site) {
if (site.getSubdomain() != null) {
return "https://" + site.getSubdomain() + "." + siteDomain;
}
return null;
}
}

View File

@@ -0,0 +1,44 @@
package com.krrishg.service;
import com.krrishg.dto.SiteStructure;
import com.krrishg.service.RenderService.SiteOutput;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@Service
public class ExportService {
private static final Logger log = LoggerFactory.getLogger(ExportService.class);
private final RenderService renderService;
public ExportService(RenderService renderService) {
this.renderService = renderService;
}
public byte[] exportSite(SiteStructure structure) throws IOException {
SiteOutput output = renderService.renderSite(structure);
Map<String, byte[]> files = output.asFileMap();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (ZipOutputStream zos = new ZipOutputStream(baos)) {
for (Map.Entry<String, byte[]> entry : files.entrySet()) {
ZipEntry zipEntry = new ZipEntry(entry.getKey());
zipEntry.setTime(System.currentTimeMillis());
zos.putNextEntry(zipEntry);
zos.write(entry.getValue());
zos.closeEntry();
}
}
log.info("Exported site with {} files ({} bytes)", files.size(), baos.size());
return baos.toByteArray();
}
}

View File

@@ -0,0 +1,369 @@
package com.krrishg.service;
import com.krrishg.config.GitConfig;
import com.krrishg.dto.SiteStructure;
import com.krrishg.model.GitRemote;
import com.krrishg.repository.GitRemoteRepository;
import com.krrishg.service.RenderService.SiteOutput;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.Status;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@Service
public class GitService {
private static final Logger log = LoggerFactory.getLogger(GitService.class);
private final GitConfig gitConfig;
private final RenderService renderService;
private final GitRemoteRepository gitRemoteRepository;
private final KeyManager keyManager;
public GitService(GitConfig gitConfig,
RenderService renderService,
GitRemoteRepository gitRemoteRepository,
KeyManager keyManager) {
this.gitConfig = gitConfig;
this.renderService = renderService;
this.gitRemoteRepository = gitRemoteRepository;
this.keyManager = keyManager;
}
public void initRepo(String siteId) {
Path repoPath = gitConfig.getSiteRepoPath(siteId);
if (Files.exists(repoPath.resolve(".git"))) {
return;
}
try {
Files.createDirectories(repoPath);
Git.init().setDirectory(repoPath.toFile()).call();
log.info("Initialized git repo for site {}", siteId);
} catch (GitAPIException | IOException e) {
throw new RuntimeException("Failed to init git repo for site " + siteId, e);
}
}
public Git openRepo(String siteId) {
Path repoPath = gitConfig.getSiteRepoPath(siteId);
if (!Files.exists(repoPath.resolve(".git"))) {
initRepo(siteId);
}
try {
return Git.open(repoPath.toFile());
} catch (IOException e) {
throw new RuntimeException("Failed to open git repo for site " + siteId, e);
}
}
public void writeSiteFiles(String siteId, SiteStructure structure) {
Path repoPath = gitConfig.getSiteRepoPath(siteId);
try {
SiteOutput output = renderService.renderSite(structure);
Map<String, byte[]> files = output.asFileMap();
for (Map.Entry<String, byte[]> entry : files.entrySet()) {
Path filePath = repoPath.resolve(entry.getKey());
Files.createDirectories(filePath.getParent());
Files.write(filePath, entry.getValue());
}
} catch (IOException e) {
throw new RuntimeException("Failed to write site files for site " + siteId, e);
}
}
public RevCommit commit(String siteId, String message) {
try (Git git = openRepo(siteId)) {
git.add().addFilepattern(".").call();
Status status = git.status().call();
if (status.getUncommittedChanges().isEmpty() && status.getAdded().isEmpty()) {
log.info("No changes to commit for site {}", siteId);
return null;
}
PersonIdent author = new PersonIdent(
gitConfig.getAuthorName(), gitConfig.getAuthorEmail());
return git.commit()
.setAuthor(author)
.setCommitter(author)
.setMessage(message)
.call();
} catch (Exception e) {
throw new RuntimeException("Failed to commit for site " + siteId, e);
}
}
public void commitAndPush(String siteId, String message) {
RevCommit commit = commit(siteId, message);
if (commit == null) {
return;
}
GitRemote remote = gitRemoteRepository.findBySiteId(UUID.fromString(siteId))
.orElse(null);
if (remote == null) {
log.info("No remote configured for site {}, skipping push", siteId);
return;
}
try (Git git = openRepo(siteId)) {
git.push()
.setRemote(remote.getRemoteUrl())
.setCredentialsProvider(
new UsernamePasswordCredentialsProvider("token", remote.getEncryptedToken()))
.call();
log.info("Pushed site {} to remote {}", siteId, remote.getProvider());
} catch (Exception e) {
throw new RuntimeException("Failed to push for site " + siteId, e);
}
}
public List<CommitInfo> getLog(String siteId, int maxCount) {
try (Git git = openRepo(siteId)) {
ObjectId head = git.getRepository().resolve("HEAD");
if (head == null) {
return Collections.emptyList();
}
List<CommitInfo> commits = new ArrayList<>();
for (RevCommit rev : git.log().setMaxCount(maxCount).call()) {
commits.add(new CommitInfo(
rev.getName(),
rev.getShortMessage(),
rev.getAuthorIdent().getWhen().toInstant().toString(),
rev.getAuthorIdent().getName()
));
}
return commits;
} catch (GitAPIException | IOException e) {
throw new RuntimeException("Failed to get log for site " + siteId, e);
}
}
public void rollback(String siteId, String commitHash) {
try (Git git = openRepo(siteId)) {
ObjectId commitId = ObjectId.fromString(commitHash);
git.checkout().setName(commitHash).call();
git.branchCreate().setName("restore-" + commitHash.substring(0, 7)).call();
log.info("Rolled back site {} to commit {}", siteId, commitHash);
} catch (Exception e) {
throw new RuntimeException("Failed to rollback site " + siteId + " to " + commitHash, e);
}
}
public byte[] exportRepo(String siteId) {
Path repoPath = gitConfig.getSiteRepoPath(siteId);
if (!Files.exists(repoPath)) {
throw new IllegalArgumentException("No repo found for site " + siteId);
}
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (ZipOutputStream zos = new ZipOutputStream(baos)) {
Files.walk(repoPath)
.filter(Files::isRegularFile)
.forEach(path -> {
String entryName = repoPath.relativize(path).toString();
try {
zos.putNextEntry(new ZipEntry(entryName));
zos.write(Files.readAllBytes(path));
zos.closeEntry();
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
return baos.toByteArray();
} catch (IOException e) {
throw new RuntimeException("Failed to export repo for site " + siteId, e);
}
}
public record CommitInfo(String hash, String message, String timestamp, String author) {}
private String normalizeHttpsUrl(String url) {
if (url.startsWith("https://")) {
return url;
}
String domain = null;
if (url.contains("github.com")) domain = "github.com";
else if (url.contains("gitlab.com")) domain = "gitlab.com";
if (domain != null && url.contains(":")) {
String path = url.substring(url.lastIndexOf(":") + 1);
if (path.endsWith(".git")) path = path.substring(0, path.length() - 4);
return "https://" + domain + "/" + path + ".git";
}
return url;
}
public String deployToPages(String siteId, GitRemote remote, SiteStructure structure) {
try {
SiteOutput output = renderService.renderSite(structure);
Map<String, byte[]> files = output.asFileMap();
String httpsUrl = normalizeHttpsUrl(remote.getRemoteUrl());
String authUrl = httpsUrl.replace("https://",
"https://git:" + remote.getEncryptedToken() + "@");
boolean isGitLab = remote.getRemoteUrl().contains("gitlab.com");
String branch = isGitLab ? "gl-pages" : "gh-pages";
Path tempDir = Files.createTempDirectory("indie-pages-" + siteId);
try {
Path contentRoot = isGitLab ? tempDir.resolve("public") : tempDir;
Files.createDirectories(contentRoot);
for (Map.Entry<String, byte[]> entry : files.entrySet()) {
Path filePath = contentRoot.resolve(entry.getKey());
Files.createDirectories(filePath.getParent());
Files.write(filePath, entry.getValue());
}
if (isGitLab) {
Files.writeString(tempDir.resolve(".gitlab-ci.yml"),
"pages:\n" +
" stage: deploy\n" +
" script:\n" +
" - echo \"Deploying GitLab Pages\"\n" +
" artifacts:\n" +
" paths:\n" +
" - public\n" +
" rules:\n" +
" - if: '$CI_COMMIT_BRANCH == \"gl-pages\"'\n");
} else {
Files.writeString(tempDir.resolve(".nojekyll"), "");
}
pushOrphanBranch(tempDir, authUrl, branch);
log.info("Pushed site {} to {} branch {}", siteId, remote.getRemoteUrl(), branch);
return buildPagesUrl(remote);
} finally {
deleteDirectory(tempDir);
}
} catch (Exception e) {
throw new RuntimeException("Failed to deploy site " + siteId + " to " + remote.getProvider() + " Pages", e);
}
}
private void pushOrphanBranch(Path tempDir, String authUrl, String branch) throws Exception {
String authorConfig = "-c user.name='" + gitConfig.getAuthorName() + "' -c user.email='" + gitConfig.getAuthorEmail() + "'";
runGit(tempDir,
"git init",
"git " + authorConfig + " commit --allow-empty -m 'Initial commit'",
"git push --force " + authUrl + " HEAD:main",
"git checkout --orphan " + branch,
"git add -A",
"git " + authorConfig + " commit -m 'Deploy site'",
"git push --force " + authUrl + " HEAD:" + branch
);
}
private void runGit(Path workDir, String... commands) throws Exception {
for (String cmd : commands) {
ProcessBuilder pb = new ProcessBuilder("sh", "-c", cmd);
pb.directory(workDir.toFile());
pb.redirectErrorStream(true);
Process process = pb.start();
int exitCode = process.waitFor();
if (exitCode != 0) {
String processOutput = new String(process.getInputStream().readAllBytes());
throw new RuntimeException("git command failed (exit " + exitCode + "): " + cmd + "\n" + processOutput);
}
}
}
private String buildPagesUrl(GitRemote remote) {
String url = normalizeHttpsUrl(remote.getRemoteUrl());
String owner = "";
String repo = "";
if (url.contains("github.com")) {
String path = url.substring(url.indexOf("github.com") + "github.com".length() + 1);
if (path.endsWith(".git")) path = path.substring(0, path.length() - 4);
if (path.endsWith("/")) path = path.substring(0, path.length() - 1);
String[] parts = path.split("/", 2);
if (parts.length == 2) {
owner = parts[0];
repo = parts[1];
}
return "https://" + owner + ".github.io/" + repo + "/";
} else if (url.contains("gitlab.com")) {
String path = url.substring(url.indexOf("gitlab.com") + "gitlab.com".length() + 1);
if (path.endsWith(".git")) path = path.substring(0, path.length() - 4);
if (path.endsWith("/")) path = path.substring(0, path.length() - 1);
String[] parts = path.split("/", 2);
if (parts.length == 2) {
owner = parts[0];
repo = parts[1];
}
return "https://" + owner + ".gitlab.io/" + repo + "/";
}
return url;
}
public void removePages(String siteId) {
GitRemote remote = gitRemoteRepository.findBySiteId(UUID.fromString(siteId))
.orElse(null);
if (remote == null) {
log.info("No remote configured for site {}, skipping remove", siteId);
return;
}
try {
String httpsUrl = normalizeHttpsUrl(remote.getRemoteUrl());
String authUrl = httpsUrl.replace("https://",
"https://git:" + remote.getEncryptedToken() + "@");
boolean isGitLab = remote.getRemoteUrl().contains("gitlab.com");
String branch = isGitLab ? "gl-pages" : "gh-pages";
Path tempDir = Files.createTempDirectory("indie-pages-remove-" + siteId);
try {
String authorConfig = "-c user.name='" + gitConfig.getAuthorName() + "' -c user.email='" + gitConfig.getAuthorEmail() + "'";
runGit(tempDir,
"git init",
"git " + authorConfig + " commit --allow-empty -m 'Remove site'",
"git checkout --orphan " + branch,
"git " + authorConfig + " commit --allow-empty -m 'Remove site'",
"git push --force " + authUrl + " HEAD:" + branch
);
log.info("Removed pages for site {} from {} branch {}", siteId, remote.getProvider(), branch);
} finally {
deleteDirectory(tempDir);
}
} catch (Exception e) {
throw new RuntimeException("Failed to remove pages for site " + siteId, e);
}
}
private void deleteDirectory(Path path) throws IOException {
if (Files.exists(path)) {
try (var walk = Files.walk(path)) {
walk.sorted(java.util.Comparator.reverseOrder())
.forEach(p -> {
try {
Files.deleteIfExists(p);
} catch (IOException e) {
log.warn("Failed to delete {}", p);
}
});
}
}
}
}

View File

@@ -0,0 +1,91 @@
package com.krrishg.service;
import com.krrishg.config.GitConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermissions;
@Component
public class KeyManager {
private static final Logger log = LoggerFactory.getLogger(KeyManager.class);
private final GitConfig gitConfig;
public KeyManager(GitConfig gitConfig) {
this.gitConfig = gitConfig;
}
public Path getSshDir(String siteId) {
return gitConfig.getSiteRepoPath(siteId).resolve(".ssh");
}
public boolean hasKeyPair(String siteId) {
Path sshDir = getSshDir(siteId);
return Files.exists(sshDir.resolve("id_ed25519"));
}
public void generateKeyPair(String siteId) {
Path sshDir = getSshDir(siteId);
try {
Files.createDirectories(sshDir);
ProcessBuilder pb = new ProcessBuilder(
"ssh-keygen", "-t", "ed25519",
"-f", sshDir.resolve("id_ed25519").toString(),
"-N", "", "-q"
);
pb.redirectErrorStream(true);
Process process = pb.start();
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new IOException("ssh-keygen failed with exit code " + exitCode);
}
Files.setPosixFilePermissions(
sshDir.resolve("id_ed25519"),
PosixFilePermissions.fromString("rw-------"));
log.info("Generated SSH key pair for site {}", siteId);
} catch (IOException | InterruptedException e) {
throw new RuntimeException("Failed to generate SSH key pair for site " + siteId, e);
}
}
public String getPublicKey(String siteId) {
Path pubKeyPath = getSshDir(siteId).resolve("id_ed25519.pub");
if (!Files.exists(pubKeyPath)) {
generateKeyPair(siteId);
}
try {
return Files.readString(pubKeyPath).strip();
} catch (IOException e) {
throw new RuntimeException("Failed to read public key for site " + siteId, e);
}
}
public Path getPrivateKeyPath(String siteId) {
Path keyPath = getSshDir(siteId).resolve("id_ed25519");
if (!Files.exists(keyPath)) {
generateKeyPair(siteId);
}
return keyPath;
}
public void deleteKeys(String siteId) {
Path sshDir = getSshDir(siteId);
try {
Files.deleteIfExists(sshDir.resolve("id_ed25519"));
Files.deleteIfExists(sshDir.resolve("id_ed25519.pub"));
Files.deleteIfExists(sshDir);
} catch (IOException e) {
log.warn("Failed to delete SSH keys for site {}: {}", siteId, e.getMessage());
}
}
}

View File

@@ -1,10 +1,10 @@
package com.indie.service;
package com.krrishg.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.indie.dto.SiteStructure;
import com.krrishg.dto.SiteStructure;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

View File

@@ -1,12 +1,12 @@
package com.indie.service;
package com.krrishg.service;
import com.indie.dto.LLMOptions;
import com.indie.dto.LLMResult;
import com.indie.dto.Reference;
import com.indie.dto.SiteStructure;
import com.indie.model.GenerationVersion;
import com.indie.service.llm.LLMFactory;
import com.indie.service.llm.LLMProvider;
import com.krrishg.dto.LLMOptions;
import com.krrishg.dto.LLMResult;
import com.krrishg.dto.Reference;
import com.krrishg.dto.SiteStructure;
import com.krrishg.model.GenerationVersion;
import com.krrishg.service.llm.LLMFactory;
import com.krrishg.service.llm.LLMProvider;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -0,0 +1,88 @@
package com.krrishg.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@Service
public class MediaService {
private static final Logger log = LoggerFactory.getLogger(MediaService.class);
private static final Set<String> ALLOWED_TYPES = Set.of(
"image/jpeg", "image/png", "image/gif", "image/webp", "image/svg+xml",
"application/pdf"
);
private static final long MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB
private final S3Client s3Client;
@Value("${indie.storage.bucket}")
private String bucket;
@Value("${indie.storage.public-url-base}")
private String publicUrlBase;
public MediaService(S3Client s3Client) {
this.s3Client = s3Client;
}
public MediaUploadResult upload(MultipartFile file, UUID siteId) {
validateFile(file);
String originalFilename = file.getOriginalFilename();
String extension = "";
if (originalFilename != null && originalFilename.contains(".")) {
extension = originalFilename.substring(originalFilename.lastIndexOf("."));
}
String key = "media/" + siteId + "/" + UUID.randomUUID() + extension;
String contentType = file.getContentType();
try {
PutObjectRequest request = PutObjectRequest.builder()
.bucket(bucket)
.key(key)
.contentType(contentType)
.build();
s3Client.putObject(request, RequestBody.fromBytes(file.getBytes()));
String publicUrl = publicUrlBase + "/" + key;
log.info("Uploaded media {} ({}) for site {}", key, contentType, siteId);
return new MediaUploadResult(key, publicUrl, originalFilename, contentType, file.getSize());
} catch (IOException e) {
throw new RuntimeException("Failed to upload file", e);
}
}
public String getPublicUrl(String key) {
return publicUrlBase + "/" + key;
}
private void validateFile(MultipartFile file) {
if (file.isEmpty()) {
throw new IllegalArgumentException("File is empty");
}
if (file.getSize() > MAX_FILE_SIZE) {
throw new IllegalArgumentException("File size exceeds maximum of 10 MB");
}
String contentType = file.getContentType();
if (contentType == null || !ALLOWED_TYPES.contains(contentType)) {
throw new IllegalArgumentException("File type not allowed: " + contentType);
}
}
public record MediaUploadResult(String key, String url, String originalFilename,
String contentType, long size) {}
}

View File

@@ -1,7 +1,7 @@
package com.indie.service;
package com.krrishg.service;
import com.indie.config.JwtConfig;
import com.indie.model.User;
import com.krrishg.config.JwtConfig;
import com.krrishg.model.User;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

View File

@@ -1,8 +1,8 @@
package com.indie.service;
package com.krrishg.service;
import com.indie.model.AuthProvider;
import com.indie.model.User;
import com.indie.repository.UserRepository;
import com.krrishg.model.AuthProvider;
import com.krrishg.model.User;
import com.krrishg.repository.UserRepository;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;

View File

@@ -1,6 +1,6 @@
package com.indie.service;
package com.krrishg.service;
import com.indie.dto.Reference;
import com.krrishg.dto.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

View File

@@ -0,0 +1,347 @@
package com.krrishg.service;
import com.krrishg.dto.SiteStructure;
import com.krrishg.engine.JsBundle;
import com.krrishg.engine.StyleCompiler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class RenderService {
private static final Logger log = LoggerFactory.getLogger(RenderService.class);
private final StyleCompiler styleCompiler;
private final JsBundle jsBundle;
public RenderService(StyleCompiler styleCompiler, JsBundle jsBundle) {
this.styleCompiler = styleCompiler;
this.jsBundle = jsBundle;
}
public SiteOutput renderSite(SiteStructure structure) {
String css = styleCompiler.compile();
String js = jsBundle.compile();
Map<String, String> pages = new LinkedHashMap<>();
if (structure.getPages() != null) {
for (SiteStructure.PageStructure page : structure.getPages()) {
String html = renderPage(page, structure.getTheme());
pages.put(page.getSlug(), html);
}
}
return new SiteOutput(css, js, pages);
}
private String renderPage(SiteStructure.PageStructure page, SiteStructure.ThemeConfig theme) {
StringBuilder cssVars = new StringBuilder();
if (theme != null) {
if (theme.getColors() != null) {
for (Map.Entry<String, String> e : theme.getColors().entrySet()) {
cssVars.append(" --color-").append(e.getKey()).append(": ").append(e.getValue()).append(";\n");
}
}
if (theme.getFonts() != null) {
for (Map.Entry<String, String> e : theme.getFonts().entrySet()) {
cssVars.append(" --font-").append(e.getKey()).append(": ").append(e.getValue()).append(";\n");
}
}
if (theme.getSpacing() != null) {
for (Map.Entry<String, String> e : theme.getSpacing().entrySet()) {
cssVars.append(" --spacing-").append(e.getKey()).append(": ").append(e.getValue()).append(";\n");
}
}
}
List<Section> sections = buildSections(page.getComponents());
Set<Integer> lastIndices = sections.stream()
.map(s -> s.indices.get(s.indices.size() - 1))
.collect(Collectors.toSet());
StringBuilder sectionsHtml = new StringBuilder();
for (Section section : sections) {
String bgStyle = section.bg != null && !section.bg.isEmpty()
? " style=\"background:" + section.bg + "\"" : "";
sectionsHtml.append("<section").append(bgStyle).append(">\n <div class=\"container\">\n");
for (int idx : section.indices) {
SiteStructure.ComponentStructure comp = page.getComponents().get(idx);
sectionsHtml.append(" ").append(renderComponent(comp, idx)).append("\n");
}
sectionsHtml.append(" </div>\n</section>\n");
}
StringBuilder compStyles = new StringBuilder();
for (int i = 0; i < page.getComponents().size(); i++) {
SiteStructure.ComponentStructure c = page.getComponents().get(i);
if (c.getStyles() == null || c.getStyles().isEmpty()) continue;
List<Map.Entry<String, Object>> entries = c.getStyles().entrySet().stream()
.filter(e -> !"backgroundColor".equals(e.getKey()))
.collect(Collectors.toList());
if (lastIndices.contains(i)) {
entries = entries.stream()
.filter(e -> !"marginBottom".equals(e.getKey()))
.collect(Collectors.toList());
}
if (entries.isEmpty()) continue;
compStyles.append(".comp-").append(i).append(" {\n");
for (Map.Entry<String, Object> e : entries) {
compStyles.append(" ").append(cssProperty(e.getKey())).append(": ").append(e.getValue()).append(";\n");
}
compStyles.append("}\n");
}
String title = page.getSeoTitle() != null && !page.getSeoTitle().isEmpty()
? page.getSeoTitle() : page.getTitle();
return "<!DOCTYPE html>\n"
+ "<html lang=\"en\">\n"
+ "<head>\n"
+ " <meta charset=\"UTF-8\">\n"
+ " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n"
+ " <title>" + esc(title) + "</title>\n"
+ " <link rel=\"stylesheet\" href=\"style.css\">\n"
+ " <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Playfair+Display:wght@400;500;600;700&family=Merriweather:wght@300;400;700&family=Lato:wght@300;400;700&family=Open+Sans:wght@300;400;600;700&family=DM+Sans:wght@300;400;500;600;700&family=Plus+Jakarta+Sans:wght@300;400;500;600;700&display=swap\" rel=\"stylesheet\">\n"
+ " <style>\n"
+ " :root {\n"
+ cssVars.toString()
+ " }\n"
+ " * { margin: 0; padding: 0; box-sizing: border-box; }\n"
+ " body {\n"
+ " font-family: var(--font-body, 'Inter', sans-serif);\n"
+ " color: var(--color-text, #1a1a1a);\n"
+ " background: var(--color-background, #ffffff);\n"
+ " line-height: 1.6;\n"
+ " -webkit-font-smoothing: antialiased;\n"
+ " }\n"
+ " .container { max-width: var(--spacing-containerWidth, 1100px); margin: 0 auto; padding: 0 1.5rem; }\n"
+ " section { padding: var(--spacing-sectionPadding, 3rem) 0; }\n"
+ compStyles.toString()
+ " </style>\n"
+ "</head>\n"
+ "<body>\n"
+ sectionsHtml.toString()
+ "</body>\n"
+ "</html>";
}
private String renderComponent(SiteStructure.ComponentStructure c, int index) {
Map<String, Object> config = c.getConfig() != null ? c.getConfig() : new HashMap<>();
String type = (c.getType() != null ? c.getType() : "").toUpperCase();
String cls = "comp-" + index;
return switch (type) {
case "HEADING" -> renderHeading(config, cls);
case "TEXT" -> renderText(config, cls);
case "IMAGE" -> renderImage(config, cls);
case "BUTTON" -> renderButton(config, cls);
case "DIVIDER" -> renderDivider(config, cls);
case "GALLERY" -> renderGallery(config, cls);
case "VIDEO" -> renderVideo(config, cls);
case "CONTACT_FORM" -> renderContactForm(config, cls);
case "MAP" -> renderMap(config, cls);
case "CUSTOM_HTML" -> renderCustomHtml(config, cls);
default -> "<div class=\"" + cls + "\">[" + esc(type) + "]</div>";
};
}
private String renderHeading(Map<String, Object> config, String cls) {
String level = str(config.get("level"), "h2");
String text = str(config.get("text"), "Heading");
String tag = List.of("h1", "h2", "h3", "h4", "h5", "h6").contains(level) ? level : "h2";
return "<" + tag + " class=\"" + cls + "\">" + esc(text) + "</" + tag + ">";
}
private String renderText(Map<String, Object> config, String cls) {
String content = str(config.get("content"), "");
return "<div class=\"" + cls + "\">" + content + "</div>";
}
private String renderImage(Map<String, Object> config, String cls) {
String src = str(config.get("src"), "");
String alt = str(config.get("alt"), "");
String caption = str(config.get("caption"), "");
String linkTo = str(config.get("linkTo"), "");
String img = "<img src=\"" + esc(src) + "\" alt=\"" + esc(alt) + "\" class=\"" + cls + "\" style=\"max-width:100%;height:auto\">";
String imgHtml = linkTo.isEmpty() ? img : "<a href=\"" + esc(linkTo) + "\">" + img + "</a>";
if (!caption.isEmpty()) {
return "<figure style=\"margin:0\">" + imgHtml
+ "<figcaption style=\"text-align:center;font-size:0.875rem;color:#6b7280;margin-top:0.5rem\">"
+ esc(caption) + "</figcaption></figure>";
}
return imgHtml;
}
private String renderButton(Map<String, Object> config, String cls) {
String text = str(config.get("text"), "Button");
String link = str(config.get("link"), "#");
String variant = str(config.get("variant"), "primary");
String btnStyle = "display:inline-block;text-decoration:none;font-weight:500;font-size:1rem;padding:0.75rem 2rem;border-radius:8px;transition:all 0.2s;cursor:pointer;border:2px solid transparent";
btnStyle += switch (variant) {
case "outline" -> ";background:transparent;color:var(--color-primary,#4f46e5);border-color:var(--color-primary,#4f46e5)";
case "secondary" -> ";background:var(--color-secondary,#6366f1);color:#ffffff";
default -> ";background:var(--color-primary,#4f46e5);color:#ffffff";
};
return "<a href=\"" + esc(link) + "\" class=\"" + cls + "\" style=\"" + btnStyle + "\">" + esc(text) + "</a>";
}
private String renderDivider(Map<String, Object> config, String cls) {
String style = str(config.get("style"), "solid");
String color = str(config.get("color"), "var(--color-text, #d1d5db)");
return "<hr class=\"" + cls + "\" style=\"border:none;border-top:1px " + style + " " + color + "\">";
}
@SuppressWarnings("unchecked")
private String renderGallery(Map<String, Object> config, String cls) {
List<String> images = (List<String>) config.getOrDefault("images", List.of());
int columns = Math.min(Math.max(toInt(config.get("columns"), 3), 1), 6);
if (images.isEmpty()) {
return "<div class=\"" + cls + "\">Gallery (no images)</div>";
}
StringBuilder sb = new StringBuilder();
sb.append("<div class=\"").append(cls).append("\" style=\"display:grid;grid-template-columns:repeat(")
.append(columns).append(",1fr);gap:1rem\">\n");
for (String src : images) {
sb.append(" <img src=\"").append(esc(src))
.append("\" style=\"width:100%;height:250px;object-fit:cover;border-radius:8px\">\n");
}
sb.append(" </div>");
return sb.toString();
}
private String renderVideo(Map<String, Object> config, String cls) {
String src = str(config.get("src"), "");
boolean controls = config.get("controls") != Boolean.FALSE;
boolean autoplay = config.get("autoplay") == Boolean.TRUE;
StringBuilder sb = new StringBuilder();
sb.append("<video src=\"").append(esc(src)).append("\" class=\"").append(cls)
.append("\"");
if (controls) sb.append(" controls");
if (autoplay) sb.append(" autoplay");
sb.append(" muted style=\"max-width:100%\"></video>");
return sb.toString();
}
@SuppressWarnings("unchecked")
private String renderContactForm(Map<String, Object> config, String cls) {
List<String> fields = (List<String>) config.getOrDefault("fields", List.of("name", "email", "message"));
String submitText = str(config.get("submitText"), "Send");
StringBuilder sb = new StringBuilder();
sb.append("<form class=\"").append(cls).append("\">\n");
for (String field : fields) {
sb.append(" <div style=\"margin-bottom:1rem\">\n");
sb.append(" <label style=\"display:block;font-size:0.875rem;font-weight:600;margin-bottom:0.375rem;color:var(--color-text,#374151)\">")
.append(esc(capitalize(field))).append("</label>\n");
if ("message".equals(field)) {
sb.append(" <textarea rows=\"4\" style=\"width:100%;padding:0.75rem;border:1px solid #d1d5db;border-radius:8px;font-family:inherit;font-size:0.95rem;transition:border-color 0.2s;outline:none\" onfocus=\"this.style.borderColor='var(--color-primary,#4f46e5)'\" onblur=\"this.style.borderColor='#d1d5db'\"></textarea>\n");
} else {
String inputType = "email".equals(field) ? "email" : "text";
sb.append(" <input type=\"").append(inputType)
.append("\" style=\"width:100%;padding:0.75rem;border:1px solid #d1d5db;border-radius:8px;font-family:inherit;font-size:0.95rem;transition:border-color 0.2s;outline:none\" onfocus=\"this.style.borderColor='var(--color-primary,#4f46e5)'\" onblur=\"this.style.borderColor='#d1d5db'\">\n");
}
sb.append(" </div>\n");
}
sb.append(" <button type=\"submit\" style=\"padding:0.75rem 2rem;background:var(--color-primary,#4f46e5);color:#fff;border:none;border-radius:8px;cursor:pointer;font-size:1rem;font-weight:500;transition:opacity 0.2s\" onmouseover=\"this.style.opacity='0.9'\" onmouseout=\"this.style.opacity='1'\">")
.append(esc(submitText)).append("</button>\n");
sb.append(" </form>");
return sb.toString();
}
private String renderMap(Map<String, Object> config, String cls) {
String address = str(config.get("address"), "New York, NY");
return "<div class=\"" + cls + "\" style=\"display:flex;align-items:center;justify-content:center;background:#f3f4f6\">\n"
+ " <span style=\"color:#9ca3af\">Map: " + esc(address) + "</span>\n"
+ " </div>";
}
private String renderCustomHtml(Map<String, Object> config, String cls) {
String html = str(config.get("html"), "");
return "<div class=\"" + cls + "\">" + html + "</div>";
}
private List<Section> buildSections(List<SiteStructure.ComponentStructure> components) {
List<Section> sections = new ArrayList<>();
if (components == null) return sections;
for (int i = 0; i < components.size(); i++) {
SiteStructure.ComponentStructure c = components.get(i);
String bg = "";
if (c.getStyles() != null && c.getStyles().get("backgroundColor") != null) {
bg = String.valueOf(c.getStyles().get("backgroundColor"));
}
if ((bg != null && !bg.isEmpty()) || i == 0) {
sections.add(new Section(bg, new ArrayList<>()));
}
sections.get(sections.size() - 1).indices.add(i);
}
return sections;
}
private String cssProperty(String key) {
return key.replaceAll("([a-z])([A-Z])", "$1-$2").toLowerCase();
}
private String esc(String s) {
if (s == null) return "";
return s.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("\"", "&quot;")
.replace("'", "&#39;");
}
private String capitalize(String s) {
if (s == null || s.isEmpty()) return s;
return Character.toUpperCase(s.charAt(0)) + s.substring(1);
}
private String str(Object value, String defaultValue) {
return value != null ? String.valueOf(value) : defaultValue;
}
private int toInt(Object value, int defaultValue) {
if (value instanceof Number n) return n.intValue();
if (value instanceof String s) {
try { return Integer.parseInt(s); } catch (NumberFormatException e) { return defaultValue; }
}
return defaultValue;
}
private record Section(String bg, List<Integer> indices) {}
public record SiteOutput(String css, String js, Map<String, String> pages) {
public Map<String, byte[]> asFileMap() {
Map<String, byte[]> files = new LinkedHashMap<>();
files.put("style.css", css.getBytes());
files.put("script.js", js.getBytes());
for (Map.Entry<String, String> entry : pages.entrySet()) {
String slug = entry.getKey();
String html = entry.getValue();
if (slug == null || slug.isEmpty() || "home".equals(slug)) {
files.put("index.html", html.getBytes());
} else {
files.put(slug + "/index.html", html.getBytes());
}
}
files.put("404.html", generate404().getBytes());
return files;
}
private String generate404() {
return "<!DOCTYPE html>\n"
+ "<html lang=\"en\">\n"
+ "<head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">"
+ "<title>404 - Page Not Found</title>"
+ "<link rel=\"stylesheet\" href=\"style.css\"></head>\n"
+ "<body style=\"display:flex;align-items:center;justify-content:center;min-height:100vh;text-align:center;padding:2rem;\">\n"
+ "<div><h1>404</h1><p>The page you're looking for doesn't exist.</p>"
+ "<a href=\"index.html\" style=\"display:inline-block;text-decoration:none;font-weight:500;font-size:1rem;padding:0.75rem 2rem;border-radius:8px;background:var(--color-primary,#4f46e5);color:#ffffff;margin-top:1rem\">Go Home</a></div>\n"
+ "</body></html>";
}
}
}

View File

@@ -1,8 +1,8 @@
package com.indie.service.llm;
package com.krrishg.service.llm;
import com.indie.dto.LLMResult;
import com.indie.dto.LLMOptions;
import com.indie.dto.Reference;
import com.krrishg.dto.LLMResult;
import com.krrishg.dto.LLMOptions;
import com.krrishg.dto.Reference;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.*;

View File

@@ -1,4 +1,4 @@
package com.indie.service.llm;
package com.krrishg.service.llm;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;

View File

@@ -1,8 +1,8 @@
package com.indie.service.llm;
package com.krrishg.service.llm;
import com.indie.dto.LLMResult;
import com.indie.dto.LLMOptions;
import com.indie.dto.Reference;
import com.krrishg.dto.LLMResult;
import com.krrishg.dto.LLMOptions;
import com.krrishg.dto.Reference;
import java.util.List;

View File

@@ -1,8 +1,8 @@
package com.indie.service.llm;
package com.krrishg.service.llm;
import com.indie.dto.LLMResult;
import com.indie.dto.LLMOptions;
import com.indie.dto.Reference;
import com.krrishg.dto.LLMResult;
import com.krrishg.dto.LLMOptions;
import com.krrishg.dto.Reference;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.*;

View File

@@ -43,9 +43,18 @@ indie:
repos-path: ${INDIE_GIT_REPOS_PATH:/home/krrish/git/sites/}
author-name: ${INDIE_GIT_AUTHOR_NAME:Indie Platform}
author-email: ${INDIE_GIT_AUTHOR_EMAIL:git@indie.local}
domain: ${INDIE_SITE_DOMAIN:indie.com}
cors:
allowed-origins: ${INDIE_CORS_ORIGINS:http://localhost:4200,http://localhost:3000}
storage:
endpoint: ${INDIE_STORAGE_ENDPOINT:http://localhost:9000}
region: ${INDIE_STORAGE_REGION:auto}
access-key: ${INDIE_STORAGE_ACCESS_KEY:indie}
secret-key: ${INDIE_STORAGE_SECRET_KEY:indie_dev}
bucket: ${INDIE_STORAGE_BUCKET:indie-sites}
public-url-base: ${INDIE_STORAGE_PUBLIC_URL:http://localhost:9000/indie-sites}
llm:
active-provider: ${INDIE_LLM_ACTIVE_PROVIDER:gemini}
fallback-provider: ${INDIE_LLM_FALLBACK_PROVIDER:openai}

View File

@@ -1,4 +1,4 @@
package com.indie.config;
package com.krrishg.config;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;

View File

@@ -1,4 +1,4 @@
package com.indie.config;
package com.krrishg.config;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

View File

@@ -1,4 +1,4 @@
package com.indie.config;
package com.krrishg.config;
import org.junit.jupiter.api.Test;

View File

@@ -1,14 +1,14 @@
package com.indie.controller;
package com.krrishg.controller;
import com.indie.config.GlobalExceptionHandler;
import com.indie.dto.AuthResponse;
import com.indie.dto.AuthResponse.UserInfo;
import com.indie.dto.LoginRequest;
import com.indie.dto.RefreshTokenRequest;
import com.indie.dto.RegisterRequest;
import com.indie.model.AuthProvider;
import com.indie.service.AuthService;
import com.indie.support.TestSecurityConfig;
import com.krrishg.config.GlobalExceptionHandler;
import com.krrishg.dto.AuthResponse;
import com.krrishg.dto.AuthResponse.UserInfo;
import com.krrishg.dto.LoginRequest;
import com.krrishg.dto.RefreshTokenRequest;
import com.krrishg.dto.RegisterRequest;
import com.krrishg.model.AuthProvider;
import com.krrishg.service.AuthService;
import com.krrishg.support.TestSecurityConfig;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

View File

@@ -1,14 +1,14 @@
package com.indie.controller;
package com.krrishg.controller;
import com.indie.config.GlobalExceptionHandler;
import com.indie.config.JwtAuthenticationToken;
import com.indie.config.RateLimitConfig;
import com.indie.config.UserPrincipal;
import com.indie.dto.Reference;
import com.indie.dto.SiteStructure;
import com.indie.model.GenerationVersion;
import com.indie.service.LLMService;
import com.indie.support.TestSecurityConfig;
import com.krrishg.config.GlobalExceptionHandler;
import com.krrishg.config.JwtAuthenticationToken;
import com.krrishg.config.RateLimitConfig;
import com.krrishg.config.UserPrincipal;
import com.krrishg.dto.Reference;
import com.krrishg.dto.SiteStructure;
import com.krrishg.model.GenerationVersion;
import com.krrishg.service.LLMService;
import com.krrishg.support.TestSecurityConfig;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

View File

@@ -1,13 +1,13 @@
package com.indie.controller;
package com.krrishg.controller;
import com.indie.config.GlobalExceptionHandler;
import com.indie.config.JwtAuthenticationToken;
import com.indie.config.UserPrincipal;
import com.indie.dto.SiteRequest;
import com.indie.model.Site;
import com.indie.model.SiteStatus;
import com.indie.repository.SiteRepository;
import com.indie.support.TestSecurityConfig;
import com.krrishg.config.GlobalExceptionHandler;
import com.krrishg.config.JwtAuthenticationToken;
import com.krrishg.config.UserPrincipal;
import com.krrishg.dto.SiteRequest;
import com.krrishg.model.Site;
import com.krrishg.model.SiteStatus;
import com.krrishg.repository.SiteRepository;
import com.krrishg.support.TestSecurityConfig;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

View File

@@ -1,7 +1,7 @@
package com.indie.dto;
package com.krrishg.dto;
import com.indie.model.Site;
import com.indie.model.SiteStatus;
import com.krrishg.model.Site;
import com.krrishg.model.SiteStatus;
import org.junit.jupiter.api.Test;
import java.time.LocalDateTime;

View File

@@ -1,4 +1,4 @@
package com.indie.model;
package com.krrishg.model;
import org.junit.jupiter.api.Test;

View File

@@ -1,4 +1,4 @@
package com.indie.model;
package com.krrishg.model;
import org.junit.jupiter.api.Test;

View File

@@ -1,7 +1,7 @@
package com.indie.repository;
package com.krrishg.repository;
import com.indie.model.Site;
import com.indie.model.SiteStatus;
import com.krrishg.model.Site;
import com.krrishg.model.SiteStatus;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;

View File

@@ -1,7 +1,7 @@
package com.indie.repository;
package com.krrishg.repository;
import com.indie.model.AuthProvider;
import com.indie.model.User;
import com.krrishg.model.AuthProvider;
import com.krrishg.model.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;

View File

@@ -1,14 +1,14 @@
package com.indie.service;
package com.krrishg.service;
import com.indie.dto.AuthResponse;
import com.indie.dto.LoginRequest;
import com.indie.dto.RefreshTokenRequest;
import com.indie.dto.RegisterRequest;
import com.indie.model.AuthProvider;
import com.indie.model.User;
import com.indie.support.FakeJwtConfig;
import com.indie.support.FakeUserRepository;
import com.indie.support.StubPasswordEncoder;
import com.krrishg.dto.AuthResponse;
import com.krrishg.dto.LoginRequest;
import com.krrishg.dto.RefreshTokenRequest;
import com.krrishg.dto.RegisterRequest;
import com.krrishg.model.AuthProvider;
import com.krrishg.model.User;
import com.krrishg.support.FakeJwtConfig;
import com.krrishg.support.FakeUserRepository;
import com.krrishg.support.StubPasswordEncoder;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

View File

@@ -1,6 +1,6 @@
package com.indie.service;
package com.krrishg.service;
import com.indie.dto.Reference;
import com.krrishg.dto.Reference;
import java.util.List;

View File

@@ -1,7 +1,7 @@
package com.indie.service;
package com.krrishg.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.indie.dto.SiteStructure;
import com.krrishg.dto.SiteStructure;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;

View File

@@ -1,10 +1,10 @@
package com.indie.service;
package com.krrishg.service;
import com.indie.dto.Reference;
import com.indie.dto.SiteStructure;
import com.indie.model.GenerationVersion;
import com.indie.service.llm.LLMFactory;
import com.indie.support.FakeLLMProvider;
import com.krrishg.dto.Reference;
import com.krrishg.dto.SiteStructure;
import com.krrishg.model.GenerationVersion;
import com.krrishg.service.llm.LLMFactory;
import com.krrishg.support.FakeLLMProvider;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -69,7 +69,8 @@ class LLMServiceTest {
when(resourceLoader.getResource(anyString())).thenReturn(mockResource);
llmService = new LLMService(llmFactory, responseParser, referenceService,
mongoTemplate, resourceLoader, "classpath:prompts/site-generator.txt");
mongoTemplate, resourceLoader, "classpath:prompts/site-generator.txt",
"classpath:prompts/site-refiner.txt");
}
@Test

View File

@@ -1,9 +1,9 @@
package com.indie.service;
package com.krrishg.service;
import com.indie.model.AuthProvider;
import com.indie.model.User;
import com.indie.support.FakeUserRepository;
import com.indie.support.TestOAuth2User;
import com.krrishg.model.AuthProvider;
import com.krrishg.model.User;
import com.krrishg.support.FakeUserRepository;
import com.krrishg.support.TestOAuth2User;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

View File

@@ -1,6 +1,6 @@
package com.indie.service;
package com.krrishg.service;
import com.indie.dto.Reference;
import com.krrishg.dto.Reference;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

View File

@@ -1,6 +1,6 @@
package com.indie.service.llm;
package com.krrishg.service.llm;
import com.indie.support.FakeLLMProvider;
import com.krrishg.support.FakeLLMProvider;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

View File

@@ -1,7 +1,7 @@
package com.indie.support;
package com.krrishg.support;
import com.indie.config.TokenService;
import com.indie.model.User;
import com.krrishg.config.TokenService;
import com.krrishg.model.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;

View File

@@ -1,9 +1,9 @@
package com.indie.support;
package com.krrishg.support;
import com.indie.dto.LLMResult;
import com.indie.dto.LLMOptions;
import com.indie.dto.Reference;
import com.indie.service.llm.LLMProvider;
import com.krrishg.dto.LLMResult;
import com.krrishg.dto.LLMOptions;
import com.krrishg.dto.Reference;
import com.krrishg.service.llm.LLMProvider;
import java.util.List;

View File

@@ -1,7 +1,7 @@
package com.indie.support;
package com.krrishg.support;
import com.indie.model.Site;
import com.indie.repository.SiteRepository;
import com.krrishg.model.Site;
import com.krrishg.repository.SiteRepository;
import java.util.Comparator;
import java.util.List;

View File

@@ -1,8 +1,8 @@
package com.indie.support;
package com.krrishg.support;
import com.indie.model.AuthProvider;
import com.indie.model.User;
import com.indie.repository.UserRepository;
import com.krrishg.model.AuthProvider;
import com.krrishg.model.User;
import com.krrishg.repository.UserRepository;
import java.util.Optional;
import java.util.UUID;

View File

@@ -1,4 +1,4 @@
package com.indie.support;
package com.krrishg.support;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;

View File

@@ -1,4 +1,4 @@
package com.indie.support;
package com.krrishg.support;
import org.springframework.security.crypto.password.PasswordEncoder;

View File

@@ -1,4 +1,4 @@
package com.indie.support;
package com.krrishg.support;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

View File

@@ -1,4 +1,4 @@
package com.indie.support;
package com.krrishg.support;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;