publish to github or gitlab pages
This commit is contained in:
@@ -4,7 +4,7 @@ plugins {
|
||||
id 'io.spring.dependency-management' version '1.1.4'
|
||||
}
|
||||
|
||||
group = 'com.indie'
|
||||
group = 'com.krrishg'
|
||||
version = '0.1.0'
|
||||
|
||||
java {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.indie;
|
||||
package com.krrishg;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.indie.config;
|
||||
package com.krrishg.config;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@@ -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");
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.indie.config;
|
||||
package com.krrishg.config;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
|
||||
@@ -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;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.indie.config;
|
||||
package com.krrishg.config;
|
||||
|
||||
import io.github.bucket4j.Bandwidth;
|
||||
import io.github.bucket4j.Bucket;
|
||||
@@ -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;
|
||||
41
backend/src/main/java/com/krrishg/config/StorageConfig.java
Normal file
41
backend/src/main/java/com/krrishg/config/StorageConfig.java
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
102
backend/src/main/java/com/krrishg/controller/GitController.java
Normal file
102
backend/src/main/java/com/krrishg/controller/GitController.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.indie.dto;
|
||||
package com.krrishg.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.indie.dto;
|
||||
package com.krrishg.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.indie.dto;
|
||||
package com.krrishg.dto;
|
||||
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.indie.dto;
|
||||
package com.krrishg.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.indie.dto;
|
||||
package com.krrishg.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.indie.dto;
|
||||
package com.krrishg.dto;
|
||||
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.indie.dto;
|
||||
package com.krrishg.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
@@ -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())
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.indie.dto;
|
||||
package com.krrishg.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
74
backend/src/main/java/com/krrishg/engine/JsBundle.java
Normal file
74
backend/src/main/java/com/krrishg/engine/JsBundle.java
Normal 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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
""";
|
||||
}
|
||||
}
|
||||
31
backend/src/main/java/com/krrishg/engine/StyleCompiler.java
Normal file
31
backend/src/main/java/com/krrishg/engine/StyleCompiler.java
Normal 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; }
|
||||
}
|
||||
""";
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.indie.model;
|
||||
package com.krrishg.model;
|
||||
|
||||
public enum AuthProvider {
|
||||
LOCAL, GOOGLE, GITHUB, OPENID
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.indie.model;
|
||||
package com.krrishg.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
63
backend/src/main/java/com/krrishg/model/GitRemote.java
Normal file
63
backend/src/main/java/com/krrishg/model/GitRemote.java
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.indie.model;
|
||||
package com.krrishg.model;
|
||||
|
||||
public enum SiteStatus {
|
||||
DRAFT, PUBLISHED
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.indie.model;
|
||||
package com.krrishg.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
60
backend/src/main/java/com/krrishg/service/DomainService.java
Normal file
60
backend/src/main/java/com/krrishg/service/DomainService.java
Normal 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;
|
||||
}
|
||||
}
|
||||
44
backend/src/main/java/com/krrishg/service/ExportService.java
Normal file
44
backend/src/main/java/com/krrishg/service/ExportService.java
Normal 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();
|
||||
}
|
||||
}
|
||||
369
backend/src/main/java/com/krrishg/service/GitService.java
Normal file
369
backend/src/main/java/com/krrishg/service/GitService.java
Normal 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
91
backend/src/main/java/com/krrishg/service/KeyManager.java
Normal file
91
backend/src/main/java/com/krrishg/service/KeyManager.java
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
88
backend/src/main/java/com/krrishg/service/MediaService.java
Normal file
88
backend/src/main/java/com/krrishg/service/MediaService.java
Normal 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) {}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
347
backend/src/main/java/com/krrishg/service/RenderService.java
Normal file
347
backend/src/main/java/com/krrishg/service/RenderService.java
Normal 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("&", "&")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace("\"", """)
|
||||
.replace("'", "'");
|
||||
}
|
||||
|
||||
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>";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.*;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.indie.service.llm;
|
||||
package com.krrishg.service.llm;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.slf4j.Logger;
|
||||
@@ -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;
|
||||
|
||||
@@ -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.*;
|
||||
@@ -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}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.indie.config;
|
||||
package com.krrishg.config;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.http.HttpStatus;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.indie.config;
|
||||
package com.krrishg.config;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.indie.config;
|
||||
package com.krrishg.config;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.indie.model;
|
||||
package com.krrishg.model;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.indie.model;
|
||||
package com.krrishg.model;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.indie.support;
|
||||
package com.krrishg.support;
|
||||
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.Page;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.indie.support;
|
||||
package com.krrishg.support;
|
||||
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user