From c196eaf5187a5a7140893cb5c748a206d6d5300f Mon Sep 17 00:00:00 2001 From: Meet-08 Date: Fri, 31 Jul 2026 14:05:28 +0530 Subject: [PATCH 1/4] feat: add clone and mock process file with needed workflow --- .../com/meet/server/ServerApplication.java | 2 + .../meet/server/common/config/AppConfig.java | 14 +- .../common/exception/CodebaseException.java | 23 +++ .../exception/GlobalExceptionHandler.java | 6 + .../feature/codebase/CodebaseController.java | 42 ++++ .../feature/codebase/CodebaseRepository.java | 10 + .../feature/codebase/CodebaseService.java | 104 ++++++++++ .../codebase/CodebaseStatusService.java | 33 ++++ .../server/feature/codebase/GitService.java | 183 +++++++++++++++++- .../codebase/dto/CodebaseImportRequest.java | 10 + .../codebase/dto/CodebaseImportResponse.java | 12 ++ .../RepositoryFileDescriptor.java | 9 + .../RepositoryFileProcessor.java | 20 ++ 13 files changed, 459 insertions(+), 9 deletions(-) create mode 100644 server/src/main/java/com/meet/server/common/exception/CodebaseException.java create mode 100644 server/src/main/java/com/meet/server/feature/codebase/CodebaseController.java create mode 100644 server/src/main/java/com/meet/server/feature/codebase/CodebaseRepository.java create mode 100644 server/src/main/java/com/meet/server/feature/codebase/CodebaseService.java create mode 100644 server/src/main/java/com/meet/server/feature/codebase/CodebaseStatusService.java create mode 100644 server/src/main/java/com/meet/server/feature/codebase/dto/CodebaseImportRequest.java create mode 100644 server/src/main/java/com/meet/server/feature/codebase/dto/CodebaseImportResponse.java create mode 100644 server/src/main/java/com/meet/server/feature/repositoryfile/RepositoryFileDescriptor.java create mode 100644 server/src/main/java/com/meet/server/feature/repositoryfile/RepositoryFileProcessor.java diff --git a/server/src/main/java/com/meet/server/ServerApplication.java b/server/src/main/java/com/meet/server/ServerApplication.java index 25361e7..b4ffb73 100644 --- a/server/src/main/java/com/meet/server/ServerApplication.java +++ b/server/src/main/java/com/meet/server/ServerApplication.java @@ -7,10 +7,12 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.EnableAsync; @SpringBootApplication @EnableJpaAuditing @EnableScheduling +@EnableAsync public class ServerApplication { private static final Logger log = LogManager.getLogger(ServerApplication.class); diff --git a/server/src/main/java/com/meet/server/common/config/AppConfig.java b/server/src/main/java/com/meet/server/common/config/AppConfig.java index fad9131..ff606e8 100644 --- a/server/src/main/java/com/meet/server/common/config/AppConfig.java +++ b/server/src/main/java/com/meet/server/common/config/AppConfig.java @@ -1,10 +1,11 @@ package com.meet.server.common.config; import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Bean; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; @Configuration public class AppConfig { @@ -26,4 +27,15 @@ public void setCookieSecure(String env) { public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(12); } + + @Bean(name = "codebaseTaskExecutor") + public ThreadPoolTaskExecutor codebaseTaskExecutor() { + var executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(2); + executor.setMaxPoolSize(4); + executor.setQueueCapacity(100); + executor.setThreadNamePrefix("codebase-"); + executor.initialize(); + return executor; + } } diff --git a/server/src/main/java/com/meet/server/common/exception/CodebaseException.java b/server/src/main/java/com/meet/server/common/exception/CodebaseException.java new file mode 100644 index 0000000..b321c79 --- /dev/null +++ b/server/src/main/java/com/meet/server/common/exception/CodebaseException.java @@ -0,0 +1,23 @@ +package com.meet.server.common.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public class CodebaseException extends RuntimeException { + + private final String errorCode; + private final HttpStatus status; + + public CodebaseException(String errorCode, String message, HttpStatus status) { + super(message); + this.errorCode = errorCode; + this.status = status; + } + + public CodebaseException(String errorCode, String message, HttpStatus status, Throwable cause) { + super(message, cause); + this.errorCode = errorCode; + this.status = status; + } +} diff --git a/server/src/main/java/com/meet/server/common/exception/GlobalExceptionHandler.java b/server/src/main/java/com/meet/server/common/exception/GlobalExceptionHandler.java index fe3bbcf..9889571 100644 --- a/server/src/main/java/com/meet/server/common/exception/GlobalExceptionHandler.java +++ b/server/src/main/java/com/meet/server/common/exception/GlobalExceptionHandler.java @@ -61,6 +61,12 @@ public ResponseEntity> handleAuthException(AuthException excep return response(exception.getStatus(), exception.getMessage()); } + @ExceptionHandler(CodebaseException.class) + public ResponseEntity> handleCodebaseException(CodebaseException exception) { + log.warn("Codebase operation failed [{}]: {}", exception.getErrorCode(), exception.getMessage()); + return response(exception.getStatus(), exception.getMessage()); + } + @ExceptionHandler(InvalidTokenException.class) public ResponseEntity> handleInvalidToken(InvalidTokenException exception) { return response(HttpStatus.UNAUTHORIZED, exception.getMessage()); diff --git a/server/src/main/java/com/meet/server/feature/codebase/CodebaseController.java b/server/src/main/java/com/meet/server/feature/codebase/CodebaseController.java new file mode 100644 index 0000000..3cde576 --- /dev/null +++ b/server/src/main/java/com/meet/server/feature/codebase/CodebaseController.java @@ -0,0 +1,42 @@ +package com.meet.server.feature.codebase; + +import com.meet.server.common.api.ApiResponse; +import com.meet.server.feature.codebase.dto.CodebaseImportRequest; +import com.meet.server.feature.codebase.dto.CodebaseImportResponse; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Optional; +import java.util.UUID; + +@RestController +@RequestMapping("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/api/codebases") +@RequiredArgsConstructor +public class CodebaseController { + + private final CodebaseService codebaseService; + + @PostMapping + public ResponseEntity> importCodebase( + Authentication authentication, + @Valid @RequestBody CodebaseImportRequest request + ) { + var response = codebaseService.startClone( + UUID.fromString(authentication.getName()), + request); + + return ResponseEntity + .status(HttpStatus.ACCEPTED) + .body(new ApiResponse<>( + true, + "Codebase import queued", + Optional.of(response))); + } +} diff --git a/server/src/main/java/com/meet/server/feature/codebase/CodebaseRepository.java b/server/src/main/java/com/meet/server/feature/codebase/CodebaseRepository.java new file mode 100644 index 0000000..5a8b470 --- /dev/null +++ b/server/src/main/java/com/meet/server/feature/codebase/CodebaseRepository.java @@ -0,0 +1,10 @@ +package com.meet.server.feature.codebase; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.UUID; + +@Repository +public interface CodebaseRepository extends JpaRepository { +} diff --git a/server/src/main/java/com/meet/server/feature/codebase/CodebaseService.java b/server/src/main/java/com/meet/server/feature/codebase/CodebaseService.java new file mode 100644 index 0000000..ab3c4b7 --- /dev/null +++ b/server/src/main/java/com/meet/server/feature/codebase/CodebaseService.java @@ -0,0 +1,104 @@ +package com.meet.server.feature.codebase; + +import com.meet.server.common.exception.CodebaseException; +import com.meet.server.feature.codebase.dto.CodebaseImportRequest; +import com.meet.server.feature.codebase.dto.CodebaseImportResponse; +import com.meet.server.feature.repositoryfile.RepositoryFileProcessor; +import com.meet.server.feature.user.UserService; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; + +@Service +public class CodebaseService { + + private final CodebaseRepository codebaseRepository; + private final UserService userService; + private final GitService gitService; + private final RepositoryFileProcessor fileProcessor; + private final CodebaseStatusService statusService; + private final Executor codebaseTaskExecutor; + + public CodebaseService( + CodebaseRepository codebaseRepository, + UserService userService, + GitService gitService, + RepositoryFileProcessor fileProcessor, + CodebaseStatusService statusService, + @Qualifier("codebaseTaskExecutor") Executor codebaseTaskExecutor + ) { + this.codebaseRepository = codebaseRepository; + this.userService = userService; + this.gitService = gitService; + this.fileProcessor = fileProcessor; + this.statusService = statusService; + this.codebaseTaskExecutor = codebaseTaskExecutor; + } + + @Transactional + public CodebaseImportResponse startClone(UUID userId, CodebaseImportRequest request) { + var codebase = codebaseRepository.save(Codebase.builder() + .user(userService.getById(userId)) + .name(request.name()) + .cloneUrl(request.cloneUrl()) + .branch(request.branch() == null || request.branch().isBlank() ? "main" : request.branch()) + .status(CodebaseStatus.QUEUED) + .build()); + + var codebaseId = codebase.getId(); + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + @Override + public void afterCommit() { + processAsync(codebaseId); + } + }); + + return new CodebaseImportResponse(codebaseId, CodebaseStatus.QUEUED, 0); + } + + public CompletableFuture processAsync(UUID codebaseId) { + return CompletableFuture.supplyAsync(() -> process(codebaseId), codebaseTaskExecutor); + } + + private CodebaseImportResponse process(UUID codebaseId) { + var codebase = codebaseRepository.findById(codebaseId) + .orElseThrow(() -> new CodebaseException( + "CODEBASE_NOT_FOUND", + "Codebase not found", + HttpStatus.NOT_FOUND)); + try { + statusService.update(codebaseId, CodebaseStatus.PROCESSING); + + Path repositoryPath = Files.createTempDirectory("codebase-" + codebaseId); + try { + gitService.cloneRepository(codebase.getCloneUrl(), codebase.getBranch(), repositoryPath); + var files = gitService.listFiles(repositoryPath); + var fileCount = fileProcessor.process(codebase, repositoryPath, files); + statusService.update(codebaseId, CodebaseStatus.INDEXED); + return new CodebaseImportResponse(codebaseId, CodebaseStatus.INDEXED, fileCount); + } finally { + gitService.deleteRepository(repositoryPath); + } + } catch (IOException exception) { + statusService.update(codebaseId, CodebaseStatus.FAILED); + throw new CodebaseException( + "CODEBASE_WORKSPACE_FAILED", + "Unable to process codebase workspace", + HttpStatus.INTERNAL_SERVER_ERROR, + exception); + } catch (RuntimeException exception) { + statusService.update(codebaseId, CodebaseStatus.FAILED); + throw exception; + } + } +} diff --git a/server/src/main/java/com/meet/server/feature/codebase/CodebaseStatusService.java b/server/src/main/java/com/meet/server/feature/codebase/CodebaseStatusService.java new file mode 100644 index 0000000..3f24f87 --- /dev/null +++ b/server/src/main/java/com/meet/server/feature/codebase/CodebaseStatusService.java @@ -0,0 +1,33 @@ +package com.meet.server.feature.codebase; + +import com.meet.server.common.exception.CodebaseException; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import java.time.Instant; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class CodebaseStatusService { + + private final CodebaseRepository codebaseRepository; + + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void update(UUID codebaseId, CodebaseStatus status) { + var codebase = codebaseRepository.findById(codebaseId) + .orElseThrow(() -> new CodebaseException( + "CODEBASE_NOT_FOUND", + "Codebase not found", + HttpStatus.NOT_FOUND)); + + codebase.setStatus(status); + if (status == CodebaseStatus.INDEXED) { + codebase.setIndexedAt(Instant.now()); + } + codebaseRepository.save(codebase); + } +} diff --git a/server/src/main/java/com/meet/server/feature/codebase/GitService.java b/server/src/main/java/com/meet/server/feature/codebase/GitService.java index ffd5739..5f5056c 100644 --- a/server/src/main/java/com/meet/server/feature/codebase/GitService.java +++ b/server/src/main/java/com/meet/server/feature/codebase/GitService.java @@ -1,29 +1,196 @@ package com.meet.server.feature.codebase; +import com.meet.server.common.exception.CodebaseException; +import com.meet.server.feature.repositoryfile.RepositoryFileDescriptor; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.ignore.IgnoreNode; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; -import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.*; @Service public class GitService { - public void clone(String url, String branch, String path) { + private static final Set IGNORED_DIRECTORIES = Set.of( + ".git", ".github", ".gitlab", ".circleci", ".idea", ".vscode", + "node_modules", "bower_components", "vendor", "target", "build", "out", + "dist", "coverage", ".gradle", ".next", ".nuxt", ".angular", "__pycache__", + ".pytest_cache", ".mypy_cache", ".tox", ".venv", "venv", "env", "bin", "obj" + ); + + private static final Set IGNORED_FILE_NAMES = Set.of( + ".gitignore", ".gitattributes", ".gitmodules", ".dockerignore", + ".editorconfig", ".env", ".env.local", ".env.development", ".env.production", + "package-lock.json", "yarn.lock", "pnpm-lock.yaml", "bun.lockb", + "composer.lock", "gemfile.lock", "poetry.lock", "pipfile.lock", "cargo.lock", + "gradle.lockfile", "go.sum" + ); + + private static final Set IGNORED_EXTENSIONS = Set.of( + "7z", "avi", "bmp", "class", "dll", "dmg", "exe", "flac", "gif", "ico", + "jar", "jpeg", "jpg", "mov", "mp3", "mp4", "otf", "pdf", "so", "svg", + "tar", "ttf", "wav", "webp", "woff", "woff2", "zip", "map", "min.js", "min.css" + ); + + public void cloneRepository(String url, String branch, Path path) { try { var git = Git.cloneRepository() .setURI(url) - .setBranch(branch) - .setDirectory(new File(path)) + .setBranch(branch == null || branch.isBlank() ? "main" : branch) + .setDirectory(path.toFile()) .call(); git.close(); } catch (GitAPIException e) { - throw new RuntimeException(e); + throw new CodebaseException( + "CODEBASE_CLONE_FAILED", + "Unable to clone repository", + HttpStatus.BAD_GATEWAY, + e); + } + } + + public List listFiles(Path repositoryPath) { + var ignoreNode = loadIgnoreNode(repositoryPath); + + try (var paths = Files.walk(repositoryPath)) { + return paths + .filter(Files::isRegularFile) + .filter(path -> shouldIndex(repositoryPath, path, ignoreNode)) + .map(path -> descriptor(repositoryPath, path)) + .toList(); + } catch (IOException e) { + throw new CodebaseException( + "CODEBASE_FILE_LIST_FAILED", + "Unable to list repository files", + HttpStatus.INTERNAL_SERVER_ERROR, + e); + } + } + + private IgnoreNode loadIgnoreNode(Path repositoryPath) { + var gitignore = repositoryPath.resolve(".gitignore"); + if (!Files.isRegularFile(gitignore)) { + return null; + } + + var ignoreNode = new IgnoreNode(); + try (InputStream input = Files.newInputStream(gitignore)) { + ignoreNode.parse(input); + return ignoreNode; + } catch (IOException e) { + throw new CodebaseException( + "CODEBASE_IGNORE_FILE_FAILED", + "Unable to read repository ignore rules", + HttpStatus.INTERNAL_SERVER_ERROR, + e); + } + } + + private boolean shouldIndex(Path repositoryPath, Path file, IgnoreNode ignoreNode) { + var relativePath = repositoryPath.relativize(file).toString().replace('\\', '/'); + var pathParts = relativePath.split("/"); + var fileName = pathParts[pathParts.length - 1].toLowerCase(Locale.ROOT); + + for (int index = 0; index < pathParts.length - 1; index++) { + if (IGNORED_DIRECTORIES.contains(pathParts[index].toLowerCase(Locale.ROOT))) { + return false; + } + } + + if (IGNORED_FILE_NAMES.contains(fileName) || isIgnoredExtension(fileName)) { + return false; + } + + return ignoreNode == null || !Boolean.TRUE.equals(ignoreNode.checkIgnored(relativePath, false)); + } + + private boolean isIgnoredExtension(String fileName) { + var dot = fileName.lastIndexOf('.'); + if (dot < 0) { + return false; + } + + var extension = fileName.substring(dot + 1); + return IGNORED_EXTENSIONS.contains(extension) + || IGNORED_EXTENSIONS.stream().anyMatch(fileName::endsWith); + } + + public void deleteRepository(Path repositoryPath) { + if (repositoryPath == null || !Files.exists(repositoryPath)) { + return; + } + + try (var paths = Files.walk(repositoryPath)) { + paths.sorted(Comparator.reverseOrder()).forEach(this::deletePath); + } catch (IOException e) { + throw new CodebaseException( + "CODEBASE_CLEANUP_FAILED", + "Unable to delete cloned repository", + HttpStatus.INTERNAL_SERVER_ERROR, + e); + } + } + + private RepositoryFileDescriptor descriptor(Path repositoryPath, Path file) { + try { + var relativePath = repositoryPath.relativize(file).toString().replace('\\', '/'); + return new RepositoryFileDescriptor( + relativePath, + languageOf(relativePath), + Files.size(file), + sha256(file)); + } catch (IOException e) { + throw new CodebaseException( + "CODEBASE_FILE_INSPECTION_FAILED", + "Unable to inspect repository file", + HttpStatus.INTERNAL_SERVER_ERROR, + e); + } + } + + private String languageOf(String path) { + var fileName = Path.of(path).getFileName().toString(); + var dot = fileName.lastIndexOf('.'); + return dot > 0 ? fileName.substring(dot + 1).toLowerCase(Locale.ROOT) : null; + } + + private String sha256(Path file) { + try { + var digest = MessageDigest.getInstance("SHA-256"); + try (var input = Files.newInputStream(file)) { + var buffer = new byte[8192]; + int read; + while ((read = input.read(buffer)) != -1) { + digest.update(buffer, 0, read); + } + } + return HexFormat.of().formatHex(digest.digest()); + } catch (IOException | NoSuchAlgorithmException e) { + throw new CodebaseException( + "CODEBASE_CHECKSUM_FAILED", + "Unable to checksum repository file", + HttpStatus.INTERNAL_SERVER_ERROR, + e); } } - public void delete(String path) { - File file = new File(path); - file.delete(); + private void deletePath(Path path) { + try { + Files.deleteIfExists(path); + } catch (IOException e) { + throw new CodebaseException( + "CODEBASE_CLEANUP_FAILED", + "Unable to delete cloned repository path", + HttpStatus.INTERNAL_SERVER_ERROR, + e); + } } } diff --git a/server/src/main/java/com/meet/server/feature/codebase/dto/CodebaseImportRequest.java b/server/src/main/java/com/meet/server/feature/codebase/dto/CodebaseImportRequest.java new file mode 100644 index 0000000..72fc05c --- /dev/null +++ b/server/src/main/java/com/meet/server/feature/codebase/dto/CodebaseImportRequest.java @@ -0,0 +1,10 @@ +package com.meet.server.feature.codebase.dto; + +import jakarta.validation.constraints.NotBlank; + +public record CodebaseImportRequest( + @NotBlank String name, + @NotBlank String cloneUrl, + String branch +) { +} diff --git a/server/src/main/java/com/meet/server/feature/codebase/dto/CodebaseImportResponse.java b/server/src/main/java/com/meet/server/feature/codebase/dto/CodebaseImportResponse.java new file mode 100644 index 0000000..9532f2d --- /dev/null +++ b/server/src/main/java/com/meet/server/feature/codebase/dto/CodebaseImportResponse.java @@ -0,0 +1,12 @@ +package com.meet.server.feature.codebase.dto; + +import com.meet.server.feature.codebase.CodebaseStatus; + +import java.util.UUID; + +public record CodebaseImportResponse( + UUID codebaseId, + CodebaseStatus status, + int fileCount +) { +} diff --git a/server/src/main/java/com/meet/server/feature/repositoryfile/RepositoryFileDescriptor.java b/server/src/main/java/com/meet/server/feature/repositoryfile/RepositoryFileDescriptor.java new file mode 100644 index 0000000..11815a8 --- /dev/null +++ b/server/src/main/java/com/meet/server/feature/repositoryfile/RepositoryFileDescriptor.java @@ -0,0 +1,9 @@ +package com.meet.server.feature.repositoryfile; + +public record RepositoryFileDescriptor( + String path, + String language, + long size, + String checksum +) { +} diff --git a/server/src/main/java/com/meet/server/feature/repositoryfile/RepositoryFileProcessor.java b/server/src/main/java/com/meet/server/feature/repositoryfile/RepositoryFileProcessor.java new file mode 100644 index 0000000..108bcb2 --- /dev/null +++ b/server/src/main/java/com/meet/server/feature/repositoryfile/RepositoryFileProcessor.java @@ -0,0 +1,20 @@ +package com.meet.server.feature.repositoryfile; + +import com.meet.server.feature.codebase.Codebase; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Service; + +import java.nio.file.Path; +import java.util.List; + +@Service +public class RepositoryFileProcessor { + + private static final Logger log = LogManager.getLogger(RepositoryFileProcessor.class); + + public int process(Codebase codebase, Path repositoryPath, List files) { + files.forEach(file -> log.info("Codebase {} file: {} ({} bytes)", codebase.getId(), file.path(), file.size())); + return files.size(); + } +} From 49d491bd88d9f8b2577af3444989045bd1ff009d Mon Sep 17 00:00:00 2001 From: Meet-08 Date: Sat, 1 Aug 2026 13:05:00 +0530 Subject: [PATCH 2/4] Add interface for parser and extractor --- server/build.gradle | 5 +- .../server/feature/codechunk/CodeChunk.java | 3 +- .../codechunk/CodeChunkRepository.java | 23 +- .../codechunk/CodeChunkRepositoryImpl.java | 256 ++++++++++++++++++ .../codechunk/SimilaritySearchResult.java | 4 + .../dto/SimilaritySearchRequest.java | 14 + .../indexing/extractor/ChunkExtractor.java | 14 + .../feature/indexing/language/Language.java | 27 ++ .../feature/indexing/parser/Parser.java | 10 + server/src/main/resources/application.yaml | 12 +- .../V4__add_code_chunk_vector_index.sql | 3 + 11 files changed, 359 insertions(+), 12 deletions(-) create mode 100644 server/src/main/java/com/meet/server/feature/codechunk/CodeChunkRepositoryImpl.java create mode 100644 server/src/main/java/com/meet/server/feature/codechunk/SimilaritySearchResult.java create mode 100644 server/src/main/java/com/meet/server/feature/codechunk/dto/SimilaritySearchRequest.java create mode 100644 server/src/main/java/com/meet/server/feature/indexing/extractor/ChunkExtractor.java create mode 100644 server/src/main/java/com/meet/server/feature/indexing/language/Language.java create mode 100644 server/src/main/java/com/meet/server/feature/indexing/parser/Parser.java create mode 100644 server/src/main/resources/db/migration/V4__add_code_chunk_vector_index.sql diff --git a/server/build.gradle b/server/build.gradle index 01a2439..44994ae 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -34,18 +34,19 @@ dependencies { implementation 'com.bucket4j:bucket4j_jdk17-redis-common:8.14.0' implementation 'com.bucket4j:bucket4j_jdk17-lettuce:8.14.0' implementation 'io.jsonwebtoken:jjwt-api:0.13.0' + implementation 'org.springframework.boot:spring-boot-starter-data-jdbc' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.13.0' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.13.0' implementation 'org.springframework.boot:spring-boot-starter-data-redis' implementation 'org.flywaydb:flyway-database-postgresql' implementation 'org.springframework.ai:spring-ai-starter-model-ollama' - implementation 'org.springframework.ai:spring-ai-starter-vector-store-pgvector' - implementation 'org.springframework.ai:spring-ai-vector-store-advisor' compileOnly 'org.projectlombok:lombok' implementation 'org.eclipse.jgit:org.eclipse.jgit:7.6.0.202603022253-r' developmentOnly 'org.springframework.boot:spring-boot-devtools' developmentOnly 'org.springframework.boot:spring-boot-docker-compose' + implementation 'com.pgvector:pgvector:0.1.6' runtimeOnly 'org.postgresql:postgresql' + runtimeOnly 'io.github.bonede:tree-sitter:0.26.3' developmentOnly 'org.springframework.ai:spring-ai-spring-boot-docker-compose' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-data-jpa-test' diff --git a/server/src/main/java/com/meet/server/feature/codechunk/CodeChunk.java b/server/src/main/java/com/meet/server/feature/codechunk/CodeChunk.java index e6209e8..8973035 100644 --- a/server/src/main/java/com/meet/server/feature/codechunk/CodeChunk.java +++ b/server/src/main/java/com/meet/server/feature/codechunk/CodeChunk.java @@ -44,7 +44,7 @@ public class CodeChunk extends BaseAuditEntity { @Column(name = "chunk_index", nullable = false) private Integer chunkIndex; - + @Column(nullable = false, columnDefinition = "TEXT") private String content; @@ -62,5 +62,4 @@ public class CodeChunk extends BaseAuditEntity { private Integer endLine; private String commitSha; - } diff --git a/server/src/main/java/com/meet/server/feature/codechunk/CodeChunkRepository.java b/server/src/main/java/com/meet/server/feature/codechunk/CodeChunkRepository.java index b426cbe..006c208 100644 --- a/server/src/main/java/com/meet/server/feature/codechunk/CodeChunkRepository.java +++ b/server/src/main/java/com/meet/server/feature/codechunk/CodeChunkRepository.java @@ -1,10 +1,25 @@ package com.meet.server.feature.codechunk; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; +import com.meet.server.feature.codechunk.dto.SimilaritySearchRequest; +import java.util.Collection; +import java.util.List; +import java.util.Optional; import java.util.UUID; -@Repository -public interface CodeChunkRepository extends JpaRepository { +public interface CodeChunkRepository { + + void saveAll(Collection chunks); + + void updateEmbedding(UUID chunkId, float[] embedding); + + void deleteByFileId(UUID fileId); + + void deleteByCodebaseId(UUID codebaseId); + + Optional findById(UUID chunkId); + + List similaritySearch(SimilaritySearchRequest request); + + long countByCodebaseId(UUID codebaseId); } diff --git a/server/src/main/java/com/meet/server/feature/codechunk/CodeChunkRepositoryImpl.java b/server/src/main/java/com/meet/server/feature/codechunk/CodeChunkRepositoryImpl.java new file mode 100644 index 0000000..4d11278 --- /dev/null +++ b/server/src/main/java/com/meet/server/feature/codechunk/CodeChunkRepositoryImpl.java @@ -0,0 +1,256 @@ +package com.meet.server.feature.codechunk; + +import com.meet.server.feature.codebase.Codebase; +import com.meet.server.feature.codechunk.dto.SimilaritySearchRequest; +import com.meet.server.feature.repositoryfile.RepositoryFile; +import com.pgvector.PGvector; +import lombok.RequiredArgsConstructor; +import org.postgresql.util.PGobject; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.simple.JdbcClient; +import org.springframework.stereotype.Repository; + +import java.sql.*; +import java.time.Instant; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Repository +@RequiredArgsConstructor +public class CodeChunkRepositoryImpl implements CodeChunkRepository { + + private static final int BATCH_SIZE = 500; + private static final String CHUNK_COLUMNS = """ + c.id, c.created_at, c.updated_at, c.file_id, c.codebase_id, + c.chunk_index, c.content, c.embedding, c.language, c.path, + c.start_line, c.end_line, c.commit_sha + """; + + private static final String INSERT_SQL = """ + INSERT INTO code_chunks ( + id, created_at, updated_at, file_id, codebase_id, chunk_index, + content, embedding, language, path, start_line, end_line, commit_sha + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ON CONFLICT (file_id, chunk_index) DO UPDATE SET + updated_at = EXCLUDED.updated_at, + content = EXCLUDED.content, + embedding = EXCLUDED.embedding, + language = EXCLUDED.language, + path = EXCLUDED.path, + start_line = EXCLUDED.start_line, + end_line = EXCLUDED.end_line, + commit_sha = EXCLUDED.commit_sha + """; + + private final JdbcClient jdbcClient; + private final JdbcTemplate jdbcTemplate; + + private static void bindChunk(PreparedStatement statement, CodeChunk chunk, Timestamp now) + throws SQLException { + UUID id = chunk.getId(); + if (id == null) { + id = UUID.randomUUID(); + chunk.setId(id); + } + + statement.setObject(1, id); + statement.setTimestamp(2, now); + statement.setTimestamp(3, now); + statement.setObject(4, requiredId(chunk.getFile(), "file")); + statement.setObject(5, requiredId(chunk.getCodebase(), "codebase")); + statement.setObject(6, chunk.getChunkIndex()); + statement.setString(7, chunk.getContent()); + setVector(statement, 8, chunk.getEmbedding()); + statement.setString(9, chunk.getLanguage()); + statement.setString(10, chunk.getPath()); + setNullableInt(statement, 11, chunk.getStartLine()); + setNullableInt(statement, 12, chunk.getEndLine()); + statement.setString(13, chunk.getCommitSha()); + } + + private static void setVector(PreparedStatement statement, int index, float[] vector) + throws SQLException { + if (vector == null) { + statement.setNull(index, Types.OTHER); + } else { + statement.setObject(index, new PGvector(vector)); + } + } + + private static void setNullableInt(PreparedStatement statement, int index, Integer value) + throws SQLException { + if (value == null) { + statement.setNull(index, Types.INTEGER); + } else { + statement.setInt(index, value); + } + } + + private static UUID requiredId(Object entity, String name) { + UUID id = entity instanceof RepositoryFile file ? file.getId() + : entity instanceof Codebase codebase ? codebase.getId() : null; + if (id == null) { + throw new IllegalArgumentException("A code chunk must have a " + name + " id"); + } + return id; + } + + private static boolean hasText(String value) { + return value != null && !value.isBlank(); + } + + private static float[] readVector(Object value) throws SQLException { + if (value == null) { + return null; + } + if (value instanceof PGvector vector) { + return vector.toArray(); + } + + String literal = value instanceof PGobject pgObject ? pgObject.getValue() : value.toString(); + return new PGvector(literal).toArray(); + } + + private static Instant readInstant(ResultSet rs, String column) throws SQLException { + Timestamp timestamp = rs.getTimestamp(column); + return timestamp == null ? null : timestamp.toInstant(); + } + + @Override + public void saveAll(Collection chunks) { + if (chunks == null || chunks.isEmpty()) { + return; + } + + Timestamp now = Timestamp.from(Instant.now()); + jdbcTemplate.batchUpdate(INSERT_SQL, chunks, BATCH_SIZE, + (statement, chunk) -> bindChunk(statement, chunk, now)); + } + + @Override + public void updateEmbedding(UUID chunkId, float[] embedding) { + jdbcClient.sql(""" + UPDATE code_chunks + SET embedding = :embedding, updated_at = :updatedAt + WHERE id = :id + """) + .param("embedding", embedding == null ? null : new PGvector(embedding)) + .param("updatedAt", Timestamp.from(Instant.now())) + .param("id", chunkId) + .update(); + } + + @Override + public void deleteByFileId(UUID fileId) { + jdbcClient.sql("DELETE FROM code_chunks WHERE file_id = :fileId") + .param("fileId", fileId) + .update(); + } + + @Override + public void deleteByCodebaseId(UUID codebaseId) { + jdbcClient.sql("DELETE FROM code_chunks WHERE codebase_id = :codebaseId") + .param("codebaseId", codebaseId) + .update(); + } + + @Override + public Optional findById(UUID chunkId) { + return jdbcClient.sql(""" + SELECT %s + FROM code_chunks c + WHERE c.id = :id + """.formatted(CHUNK_COLUMNS)) + .param("id", chunkId) + .query(this::mapChunk) + .optional(); + } + + @Override + public List similaritySearch(SimilaritySearchRequest request) { + if (request == null || request.codebaseId() == null + || request.embedding() == null || request.embedding().length == 0) { + return List.of(); + } + + StringBuilder sql = new StringBuilder(""" + SELECT ranked.* + FROM ( + SELECT %s, c.embedding <=> :embedding AS distance + FROM code_chunks c + """.formatted(CHUNK_COLUMNS)); + + if (hasText(request.branch())) { + sql.append(" JOIN codebases b ON b.id = c.codebase_id"); + } + sql.append(""" + WHERE c.codebase_id = :codebaseId + AND c.embedding IS NOT NULL + """); + if (hasText(request.language())) { + sql.append(" AND c.language = :language"); + } + if (hasText(request.branch())) { + sql.append(" AND b.branch = :branch"); + } + if (hasText(request.commitSha())) { + sql.append(" AND c.commit_sha = :commitSha"); + } + sql.append(""" + ) ranked + WHERE (:maxDistance IS NULL OR ranked.distance <= :maxDistance) + ORDER BY ranked.distance + LIMIT :topK + """); + + var statement = jdbcClient.sql(sql.toString()) + .param("codebaseId", request.codebaseId()) + .param("embedding", new PGvector(request.embedding())) + .param("maxDistance", request.maxDistance()) + .param("topK", Math.max(1, request.topK())); + if (hasText(request.language())) { + statement = statement.param("language", request.language()); + } + if (hasText(request.branch())) { + statement = statement.param("branch", request.branch()); + } + if (hasText(request.commitSha())) { + statement = statement.param("commitSha", request.commitSha()); + } + + return statement.query(this::mapSimilarityResult).list(); + } + + private SimilaritySearchResult mapSimilarityResult(ResultSet rs, int rowNum) throws SQLException { + return new SimilaritySearchResult(mapChunk(rs, rowNum), rs.getDouble("distance")); + } + + @Override + public long countByCodebaseId(UUID codebaseId) { + return jdbcClient.sql("SELECT COUNT(*) FROM code_chunks WHERE codebase_id = :codebaseId") + .param("codebaseId", codebaseId) + .query(Long.class) + .single(); + } + + private CodeChunk mapChunk(ResultSet rs, int rowNum) throws SQLException { + CodeChunk chunk = CodeChunk.builder() + .id(rs.getObject("id", UUID.class)) + .file(RepositoryFile.builder().id(rs.getObject("file_id", UUID.class)).build()) + .codebase(Codebase.builder().id(rs.getObject("codebase_id", UUID.class)).build()) + .chunkIndex((Integer) rs.getObject("chunk_index")) + .content(rs.getString("content")) + .embedding(readVector(rs.getObject("embedding"))) + .language(rs.getString("language")) + .path(rs.getString("path")) + .startLine((Integer) rs.getObject("start_line")) + .endLine((Integer) rs.getObject("end_line")) + .commitSha(rs.getString("commit_sha")) + .build(); + chunk.setCreatedAt(readInstant(rs, "created_at")); + chunk.setUpdatedAt(readInstant(rs, "updated_at")); + return chunk; + } +} diff --git a/server/src/main/java/com/meet/server/feature/codechunk/SimilaritySearchResult.java b/server/src/main/java/com/meet/server/feature/codechunk/SimilaritySearchResult.java new file mode 100644 index 0000000..6e5c1b7 --- /dev/null +++ b/server/src/main/java/com/meet/server/feature/codechunk/SimilaritySearchResult.java @@ -0,0 +1,4 @@ +package com.meet.server.feature.codechunk; + +public record SimilaritySearchResult(CodeChunk chunk, double distance) { +} diff --git a/server/src/main/java/com/meet/server/feature/codechunk/dto/SimilaritySearchRequest.java b/server/src/main/java/com/meet/server/feature/codechunk/dto/SimilaritySearchRequest.java new file mode 100644 index 0000000..ee0a449 --- /dev/null +++ b/server/src/main/java/com/meet/server/feature/codechunk/dto/SimilaritySearchRequest.java @@ -0,0 +1,14 @@ +package com.meet.server.feature.codechunk.dto; + +import java.util.UUID; + +public record SimilaritySearchRequest( + UUID codebaseId, + float[] embedding, + int topK, + Double maxDistance, + String language, + String branch, + String commitSha +) { +} diff --git a/server/src/main/java/com/meet/server/feature/indexing/extractor/ChunkExtractor.java b/server/src/main/java/com/meet/server/feature/indexing/extractor/ChunkExtractor.java new file mode 100644 index 0000000..f63f9b3 --- /dev/null +++ b/server/src/main/java/com/meet/server/feature/indexing/extractor/ChunkExtractor.java @@ -0,0 +1,14 @@ +package com.meet.server.feature.indexing.extractor; + +import com.meet.server.feature.codechunk.CodeChunk; +import com.meet.server.feature.indexing.language.Language; + +import java.util.List; + +public interface ChunkExtractor { + + boolean supports(Language language); + + List extract(ParsedFile parsed); + +} diff --git a/server/src/main/java/com/meet/server/feature/indexing/language/Language.java b/server/src/main/java/com/meet/server/feature/indexing/language/Language.java new file mode 100644 index 0000000..e256c18 --- /dev/null +++ b/server/src/main/java/com/meet/server/feature/indexing/language/Language.java @@ -0,0 +1,27 @@ +package com.meet.server.feature.indexing.language; + +public enum Language { + JAVA, + KOTLIN, + PYTHON, + JAVASCRIPT, + TYPESCRIPT, + GO, + RUST, + C, + CPP, + CSHARP, + PHP, + RUBY, + SWIFT, + + SQL, + JSON, + YAML, + XML, + MARKDOWN, + DOCKERFILE, + PROPERTIES, + + UNKNOWN +} diff --git a/server/src/main/java/com/meet/server/feature/indexing/parser/Parser.java b/server/src/main/java/com/meet/server/feature/indexing/parser/Parser.java new file mode 100644 index 0000000..8e6e993 --- /dev/null +++ b/server/src/main/java/com/meet/server/feature/indexing/parser/Parser.java @@ -0,0 +1,10 @@ +package com.meet.server.feature.indexing.parser; + +import com.meet.server.feature.repositoryfile.RepositoryFile; + +public interface Parser { + ParsedFile parse( + RepositoryFile file, + String content + ); +} diff --git a/server/src/main/resources/application.yaml b/server/src/main/resources/application.yaml index 54bdf54..52af536 100644 --- a/server/src/main/resources/application.yaml +++ b/server/src/main/resources/application.yaml @@ -1,16 +1,20 @@ spring: application: name: server + ai: + model: + embedding: ${EMBEDDING_MODEL} + chat: ${CHAT_MODEL} + datasource: + url: ${POSTGRES_URL} + password: ${POSTGRES_PASSWORD} + username: ${POSTGRES_USER} data: redis: host: ${REDIS_HOST} port: ${REDIS_PORT} jpa: generate-ddl: off - datasource: - url: ${POSTGRES_URL} - password: ${POSTGRES_PASSWORD} - username: ${POSTGRES_USER} security: oauth2: diff --git a/server/src/main/resources/db/migration/V4__add_code_chunk_vector_index.sql b/server/src/main/resources/db/migration/V4__add_code_chunk_vector_index.sql new file mode 100644 index 0000000..a39f46b --- /dev/null +++ b/server/src/main/resources/db/migration/V4__add_code_chunk_vector_index.sql @@ -0,0 +1,3 @@ +CREATE INDEX idx_code_chunks_embedding_cosine + ON code_chunks USING hnsw (embedding vector_cosine_ops) + WHERE embedding IS NOT NULL; From 0cbba12a63bef5951c63e91fc2446bbc562ca2ed Mon Sep 17 00:00:00 2001 From: Meet-08 Date: Sun, 2 Aug 2026 01:01:13 +0530 Subject: [PATCH 3/4] Add parse -> extract -> embed -> save, file processing pipeline --- server/ChunkDemo.md | 11535 ++++++++++++++++ server/build.gradle | 18 +- .../feature/codebase/CodebaseRepository.java | 10 + .../feature/codebase/CodebaseService.java | 4 +- .../server/feature/codebase/GitService.java | 28 +- .../codechunk/CodeChunkRepository.java | 2 +- .../codechunk/CodeChunkRepositoryImpl.java | 4 +- .../feature/embedding/EmbeddingService.java | 25 + .../SimilaritySearchRequest.java | 2 +- .../indexing/extractor/ChunkExtractor.java | 1 + .../indexing/extractor/CssExtractor.java | 97 + .../indexing/extractor/HtmlExtractor.java | 94 + .../indexing/extractor/JsonExtractor.java | 77 + .../indexing/extractor/MarkdownExtractor.java | 41 + .../indexing/extractor/TextExtractor.java | 44 + .../extractor/TreeSitterChunkSupport.java | 52 + .../extractor/TreeSitterExtractor.java | 175 + .../feature/indexing/language/Language.java | 147 +- .../feature/indexing/parser/JsonParser.java | 40 + .../indexing/parser/MarkdownParser.java | 19 + .../feature/indexing/parser/ParsedFile.java | 21 + .../feature/indexing/parser/Parser.java | 4 + .../feature/indexing/parser/TextParser.java | 18 + .../indexing/parser/TreeSitterParser.java | 57 + .../RepositoryFileProcessor.java | 62 +- server/src/main/resources/application.yaml | 9 +- ...reate_repository_files_and_code_chunks.sql | 42 +- .../indexing/extractor/CssExtractorTest.java | 68 + .../indexing/extractor/HtmlExtractorTest.java | 70 + .../indexing/extractor/JsonExtractorTest.java | 66 + .../extractor/TreeSitterExtractorTest.java | 116 + .../indexing/language/LanguageTest.java | 34 + 32 files changed, 12920 insertions(+), 62 deletions(-) create mode 100644 server/ChunkDemo.md create mode 100644 server/src/main/java/com/meet/server/feature/embedding/EmbeddingService.java rename server/src/main/java/com/meet/server/feature/{codechunk/dto => embedding}/SimilaritySearchRequest.java (83%) create mode 100644 server/src/main/java/com/meet/server/feature/indexing/extractor/CssExtractor.java create mode 100644 server/src/main/java/com/meet/server/feature/indexing/extractor/HtmlExtractor.java create mode 100644 server/src/main/java/com/meet/server/feature/indexing/extractor/JsonExtractor.java create mode 100644 server/src/main/java/com/meet/server/feature/indexing/extractor/MarkdownExtractor.java create mode 100644 server/src/main/java/com/meet/server/feature/indexing/extractor/TextExtractor.java create mode 100644 server/src/main/java/com/meet/server/feature/indexing/extractor/TreeSitterChunkSupport.java create mode 100644 server/src/main/java/com/meet/server/feature/indexing/extractor/TreeSitterExtractor.java create mode 100644 server/src/main/java/com/meet/server/feature/indexing/parser/JsonParser.java create mode 100644 server/src/main/java/com/meet/server/feature/indexing/parser/MarkdownParser.java create mode 100644 server/src/main/java/com/meet/server/feature/indexing/parser/ParsedFile.java create mode 100644 server/src/main/java/com/meet/server/feature/indexing/parser/TextParser.java create mode 100644 server/src/main/java/com/meet/server/feature/indexing/parser/TreeSitterParser.java create mode 100644 server/src/test/java/com/meet/server/feature/indexing/extractor/CssExtractorTest.java create mode 100644 server/src/test/java/com/meet/server/feature/indexing/extractor/HtmlExtractorTest.java create mode 100644 server/src/test/java/com/meet/server/feature/indexing/extractor/JsonExtractorTest.java create mode 100644 server/src/test/java/com/meet/server/feature/indexing/extractor/TreeSitterExtractorTest.java create mode 100644 server/src/test/java/com/meet/server/feature/indexing/language/LanguageTest.java diff --git a/server/ChunkDemo.md b/server/ChunkDemo.md new file mode 100644 index 0000000..81637df --- /dev/null +++ b/server/ChunkDemo.md @@ -0,0 +1,11535 @@ +2026-08-02T00:19:18.905+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : +Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/.cta.json chunk 0: +JSON path: $ +{"projectName":"client","mode":"file-router","typescript":true,"packageManager":"npm","includeExamples":false,"tailwind":true,"addOnOptions":{},"envVarValues":{},"git":false,"install":true,"routerOnly":false,"version":1,"framework":"react","chosenAddOns":["biome","nitro","compiler","shadcn","tanstack-query"]} 2026-08-02T00:19:18.919+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/biome.json chunk 0: +JSON path: $ +{"$schema":"https://biomejs.dev/schemas/2.2.4/schema.json","vcs":{"enabled":false,"clientKind":"git","useIgnoreFile":false},"files":{"ignoreUnknown":false,"includes":["**/src/**/*","**/.vscode/**/*","**/index.html","**/vite.config.ts","!**/src/routeTree.gen.ts","!**/src/styles.css"]},"formatter":{"enabled":true,"indentStyle":"tab"},"assist":{"actions":{"source":{"organizeImports":"off"}}},"linter":{"enabled":true,"rules":{"recommended":true}},"javascript":{"formatter":{"quoteStyle":"double"}}} +2026-08-02T00:19:18.929+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/components.json chunk 0: +JSON path: $ +{"$schema": +"https://ui.shadcn.com/schema.json","style":"new-york","rsc":false,"tsx":true,"tailwind":{"config":"","css":"src/styles.css","baseColor":"zinc","cssVariables":true,"prefix":""},"aliases":{"components":"#/components","utils":"#/lib/utils","ui":"#/components/ui","lib":"#/lib","hooks":"#/hooks"},"iconLibrary":"lucide"} +2026-08-02T00:19:18.940+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : +Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/Dockerfile chunk 0: + +# Stage 1: Build & Package + +FROM node:22-alpine AS builder + +WORKDIR /app COPY package.json package-lock.json ./ + +RUN --mount=type=cache,target=/root/.npm \ +npm ci --prefer-offline --no-audit + +ARG VITE_API_BASE_URL=http://localhost:8080/api +ENV VITE_API_BASE_URL=${VITE_API_BASE_URL} + +COPY . . + +RUN npm run build + +#Step 2: Runtime Environment FROM node:22-alpine AS runner ENV NODE_ENV=production ENV PORT=3000 ENV HOST=0.0.0.0 + +WORKDIR /app + +COPY --from=builder /app/.output ./.output + +RUN chown -R node:node /app + +USER node + +EXPOSE 3000 + +CMD ["node", ".output/server/index.mjs"] +WARNING: A restricted method in java.lang.System has been called WARNING: java.lang.System::load has been called by +org.treesitter.utils.NativeUtils in an unnamed module (file:/C: +/Users/Admin/.gradle/caches/modules-2/files-2.1/io.github.bonede/tree-sitter/0.26.3/58bc5a51a1b12ce77e6e26f6a05bf60e1d9a4a39/tree-sitter-0.26.3.jar) +WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module WARNING: Restricted +methods will be blocked in a future release unless native access is enabled + +Failed to move lib file: x86_64-windows-tree-sitter.dll551579608161209460 2026-08-02T00:19:18.988+05:30 INFO +12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase +861f1f06-e27e-42dc-972d-c716db49580a file client/index.d.ts chunk 0: +export interface ApiResponse { success: boolean message: string data: T } 2026-08-02T00:19:18.988+05:30 +INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase +861f1f06-e27e-42dc-972d-c716db49580a file client/index.d.ts chunk 1: +export interface User { id: number; email: string; name: string; roles: string[]; } 2026-08-02T00:19:18.989+05:30 INFO +12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase +861f1f06-e27e-42dc-972d-c716db49580a file client/index.d.ts chunk 2: +export interface PageResponse { content: T[]; page: number; size: number; totalElements: number; totalPages: number; +last: boolean; } 2026-08-02T00:19:18.999+05:30 INFO 12976 --- [server] [ codebase-1] +c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/package.json chunk +0: +JSON path: $ +{"name":"client","private":true,"type":"module","imports":{"#/*":"./src/*"},"scripts":{"dev":"vite dev --port 3000","build":"vite build","preview":"vite preview","test":"vitest run","format":"biome format","lint":"biome lint","check":"biome check"},"dependencies":{"@hookform/resolvers":"^5.2.2","@tailwindcss/vite":"^4.1.18","@tanstack/react-devtools":"latest","@tanstack/react-query":"latest","@tanstack/react-query-devtools":"latest","@tanstack/react-router":"latest","@tanstack/react-router-devtools":"latest","@tanstack/react-router-ssr-query":"latest","@tanstack/react-start":"latest","@tanstack/router-plugin":"^1.132.0","axios":"^1.16.1","class-variance-authority":"^0.7.1","clsx":"^2.1.1","lucide-react":"^0.577.0","nitro":"npm:nitro-nightly@latest","radix-ui":"^1.4.3","react":"^19.2.0","react-dom":"^19.2.0","react-hook-form":"^7.76.0","react-syntax-highlighter":"^16.1.1","react-toastify":"^11.1.0","tailwind-merge":"^3.0.2","tailwindcss":"^4.1.18","tw-animate-css":"^1.3.6","zod":"^4.4.3"},"devDependencies":{"@biomejs/biome":"2.4.5","@rolldown/plugin-babel":"^0.2.3","@tailwindcss/typography":"^0.5.16","@tanstack/devtools-vite":"latest","@testing-library/dom":"^10.4.1","@testing-library/react":"^16.3.0","@types/node":"^22.10.2","@types/react":"^19.2.0","@types/react-dom":"^19.2.0","@vitejs/plugin-react":"^6.0.1","babel-plugin-react-compiler":"^1.0.0","jsdom":"^28.1.0","typescript":"^6.0.2","vite":"^8.0.0","vitest":"^4.1.5"},"pnpm":{"onlyBuiltDependencies":["esbuild","lightningcss"]}} 2026-08-02T00:19:19.008+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/public/manifest.json chunk 0: +JSON path: $ +{"short_name":"TanStack App","name":"Create TanStack App +Sample","icons":[{"src":"favicon.ico","sizes":"64x64 32x32 24x24 16x16","type":"image/x-icon"},{"src":"logo192.png","type":"image/png","sizes":"192x192"},{"src":"logo512.png","type":"image/png","sizes":"512x512"}],"start_url": +".","display":"standalone","theme_color":"#000000","background_color":"#ffffff"} 2026-08-02T00:19:19.018+05:30 INFO +12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase +861f1f06-e27e-42dc-972d-c716db49580a file client/public/robots.txt chunk 0: + +# https://www.robotstxt.org/robotstxt.html + +User-agent: * +Disallow: +2026-08-02T00:19:19.027+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : +Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/README.md chunk 0: + +# Code Vault Client + +The client is a TanStack Start application built with React 19, Vite 8, TanStack Router, React Query, Tailwind CSS 4, +and Biome. It provides the web UI for authentication, dashboards, snippets, collections, and admin workflows. +2026-08-02T00:19:19.027+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : +Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/README.md chunk 1: + +## Key Directories + +```text +client/ +├── src/ +│ ├── api/ Axios client and auth refresh handling +│ ├── components/ Shared UI components +│ ├── features/ Feature modules for auth, snippets, collections, admin +│ ├── routes/ File-based TanStack Router routes +│ └── styles.css Tailwind and global styles +├── Dockerfile +├── package.json +└── README.md +``` + +2026-08-02T00:19:19.027+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : +Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/README.md chunk 2: + +## Routing + +Routes are generated from `src/routes/`. + +```text +src/routes/ +├── __root.tsx +├── index.tsx +├── _auth/ +│ ├── route.tsx +│ ├── login.tsx +│ ├── register.tsx +│ ├── forgot-password.tsx +│ └── reset-password.tsx +└── _app/ + ├── route.tsx + ├── dashboard.tsx + ├── collections/ + ├── snippets/ + └── admin/ +``` + +The `_auth` layout handles public authentication pages. The `_app` layout protects authenticated application pages, and +the admin route verifies administrative access. 2026-08-02T00:19:19.027+05:30 INFO 12976 --- [server] [ codebase-1] +c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/README.md chunk 3: + +## API Client + +API calls are centralized in [src/api/axios.ts](src/api/axios.ts). The request interceptor attaches the in-memory access +token. The response interceptor handles `401` responses by sending a refresh request, queueing concurrent requests while +refresh is in progress, and replaying them once a new access token is issued. + +For local Compose and Kubernetes deployments, the browser-facing API path is `/api`. The container also uses +`INTERNAL_API_URL=http://backend:8080/api` for server-side calls inside the Docker network. 2026-08-02T00:19:19.027+05: +30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase +861f1f06-e27e-42dc-972d-c716db49580a file client/README.md chunk 4: + +## Scripts + +| Command | Purpose | +|-------------------|--------------------------------------------------| +| `npm run dev` | Start Vite dev server on `http://localhost:3000` | +| `npm run build` | Build the production TanStack Start/Nitro app | +| `npm run preview` | Preview the production build | +| `npm run test` | Run Vitest tests | +| `npm run format` | Run Biome formatter | +| `npm run lint` | Run Biome linter | +| `npm run check` | Run Biome checks | + +2026-08-02T00:19:19.028+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : +Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/README.md chunk 5: + +## Local Development + +Start the backend dependencies and API first, then run: + +```bash +npm install +npm run dev +``` + +The app expects the backend API to be reachable through `/api` when proxied, or through the configured API base URL in +the relevant environment. 2026-08-02T00:19:19.028+05:30 INFO 12976 --- [server] [ codebase-1] +c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/README.md chunk 6: + +## Production Build + +```bash +npm run build +npm run preview +``` + +The Docker image is configured for deployment by the root Compose file and the Helm chart +in [../git-ops/charts/frontend](../git-ops/charts/frontend). 2026-08-02T00:19:19.040+05:30 INFO +12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase +861f1f06-e27e-42dc-972d-c716db49580a file client/src/api/axios.ts chunk 0: +const API_BASE_URL = import.meta.env.VITE_API_BASE_URL; 2026-08-02T00:19:19.040+05:30 INFO +12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase +861f1f06-e27e-42dc-972d-c716db49580a file client/src/api/axios.ts chunk 1: +export const api = axios.create ({ baseURL: API_BASE_URL, withCredentials: true, headers: { +"Content-Type": "application/json", }, }); 2026-08-02T00:19:19.041+05:30 INFO 12976 --- [server] [ codebase-1] +c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/api/axios.ts +chunk 2: +let isRefreshing = false; 2026-08-02T00:19:19.041+05:30 INFO 12976 --- [server] [ codebase-1] +c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/api/axios.ts +chunk 3: +let failedQueue: Array<{ resolve: (token: string) => void; reject: (err: unknown) => void; }> = []; 2026-08-02T00:19: +19.041+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase +861f1f06-e27e-42dc-972d-c716db49580a file client/src/api/axios.ts chunk 4: +function processQueue (error: unknown, token: string | null) { failedQueue.forEach ((p) => { if (error || token === +null) { p.reject (error); } else { p.resolve (token); } }); failedQueue = []; } 2026-08-02T00:19:19.041+05:30 INFO +12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase +861f1f06-e27e-42dc-972d-c716db49580a file client/src/api/axios.ts chunk 5: +api.interceptors.request.use ((config) => { const token = tokenStorage.getAccessToken (); if (token) { +config.headers.Authorization = `Bearer ${token}`; } + +return config; }); 2026-08-02T00:19:19.041+05:30 INFO 12976 --- [server] [ codebase-1] +c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/api/axios.ts +chunk 6: +api.interceptors.response.use ( +(response) => response, async (error) => { const original = error.config; + + const isServer = typeof window === "undefined"; + + if (isServer) { + return Promise.reject(error); + } + + const isAuthRoute = + original?.url?.includes("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/login") || + original?.url?.includes("/register") || + original?.url?.includes("/refresh"); + + const isUnauthorized = error.response?.status === 401; + + if (!isUnauthorized || original?._retry || isAuthRoute) { + return Promise.reject(error); + } + + if (isRefreshing) { + return new Promise((resolve, reject) => { + failedQueue.push({ + resolve, + reject, + }); + }).then((token) => { + original.headers.Authorization = `Bearer ${token}`; + return api(original); + }); + } + + original._retry = true; + isRefreshing = true; + + try { + const { data } = + await api.post>("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/auth/refresh"); + + const authData = data.data; + + tokenStorage.setAccessToken(authData.accessToken); + + processQueue(null, authData.accessToken); + + original.headers.Authorization = `Bearer ${authData.accessToken}`; + + return api(original); + } catch (err) { + processQueue(err, null); + + tokenStorage.clear(); + + if (typeof window !== "undefined") { + window.dispatchEvent(new Event("auth:expired")); + } + + return Promise.reject(err); + } finally { + isRefreshing = false; + } + +}, +); 2026-08-02T00:19:19.050+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : +Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/api/token-storage.ts chunk 0: +let accessToken: string | null = null; 2026-08-02T00:19:19.051+05:30 INFO 12976 --- [server] [ codebase-1] +c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file +client/src/api/token-storage.ts chunk 1: +export const tokenStorage = { getAccessToken: () => accessToken, + + setAccessToken: (token: string | null) => { + accessToken = token; + }, + + clear: () => { + accessToken = null; + }, + +}; 2026-08-02T00:19:19.081+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : +Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/Navbar.tsx chunk 0: +const Navbar = () => { const { queryClient } = getContext (); const { data: user } = useCurrentUser (); const +logoutMutation = useLogout (queryClient); const matchRoute = useMatchRoute (); const navigate = useNavigate (); +const [mobileMenuOpen, setMobileMenuOpen] = useState (false); const [profileMenuOpen, setProfileMenuOpen] = useState +(false); const profileCloseTimer = useRef | null>(null); + + const activeItem = navigationItems.find((item) => + matchRoute({ + to: item.to, + fuzzy: !item.exact, + }), + ); + + const handleLogout = async () => { + try { + await toast.promise(logoutMutation.mutateAsync(), { + pending: "Logging out...", + success: "Logged out successfully", + error: { + render({ data }) { + const error = data as AxiosError; + return error.response?.data?.message || "Logout failed"; + }, + }, + }); + } finally { + navigate({ to: "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/login", replace: true }); + } + }; + + const openProfileMenu = () => { + if (profileCloseTimer.current) { + clearTimeout(profileCloseTimer.current); + profileCloseTimer.current = null; + } + + setProfileMenuOpen(true); + }; + + const closeProfileMenu = () => { + if (profileCloseTimer.current) { + clearTimeout(profileCloseTimer.current); + } + + profileCloseTimer.current = setTimeout(() => { + setProfileMenuOpen(false); + profileCloseTimer.current = null; + }, 120); + }; + + return ( +
+
+
+
+ + + + + + +
+ Code Vault logo + Menu +
+ Navigation + + Open snippets or collections from the menu. + +
+ +
+ {navigationItems.map((item) => ( + +))} +
+
+
+ + + Code Vault logo + + + Code Vault + + + +
+ + + +
+ + + + + +
+
+
+ Signed in as +
+
+
+ {user?.name + ?.split(" ") + .slice(0, 2) + .map((part) => part.charAt(0)) + .join("") + .toUpperCase() || "U"} +
+
+
+ {user?.name || "Unknown user"} + +2026-08-02T00:19:19.082+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : +Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/Navbar.tsx chunk 2: +
+
+{user?.email || "No email available"} +
+
+
+
+ + + + +
+
+
+
+
+
+ ); + +}; 2026-08-02T00:19:19.082+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : +Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/Navbar.tsx chunk 3: +export default Navbar; 2026-08-02T00:19:19.096+05:30 INFO 12976 --- [server] [ codebase-1] +c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file +client/src/components/ui/alert-dialog.tsx chunk 0: +function AlertDialog ({ ...props }: React.ComponentProps) { return < +AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />; } 2026-08-02T00:19:19.096+05:30 INFO +12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase +861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/ui/alert-dialog.tsx chunk 1: +function AlertDialogTrigger ({ ...props }: React.ComponentProps) { return ( + +); } 2026-08-02T00:19:19.096+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : +Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/ui/alert-dialog.tsx chunk 2: +function AlertDialogPortal ({ ...props }: React.ComponentProps) { return ( + +); } 2026-08-02T00:19:19.096+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : +Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/ui/alert-dialog.tsx chunk 3: +function AlertDialogOverlay ({ className, ...props }: React.ComponentProps) { +return ( + +); } 2026-08-02T00:19:19.096+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : +Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/ui/alert-dialog.tsx chunk 4: +function AlertDialogContent ({ className, size = "default", ...props }: +React.ComponentProps & { size?: "default" | "sm"; }) { return ( + + + + +); } 2026-08-02T00:19:19.096+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : +Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/ui/alert-dialog.tsx chunk 5: +function AlertDialogHeader ({ className, ...props }: React.ComponentProps<"div">) { return ( +
+); +} +2026-08-02T00:19:19.097+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/ui/alert-dialog.tsx chunk 6: +function AlertDialogFooter({ +className, +...props +}: React.ComponentProps<"div">) { +return ( +
+); +} +2026-08-02T00:19:19.097+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/ui/alert-dialog.tsx chunk 7: +function AlertDialogTitle({ +className, +...props +}: React.ComponentProps) { +return ( + +); +} +2026-08-02T00:19:19.097+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/ui/alert-dialog.tsx chunk 8: +function AlertDialogDescription({ +className, +...props +}: React.ComponentProps) { +return ( + +); +} +2026-08-02T00:19:19.097+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/ui/alert-dialog.tsx chunk 9: +function AlertDialogMedia({ +className, +...props +}: React.ComponentProps<"div">) { +return ( +
+); +} +2026-08-02T00:19:19.097+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/ui/alert-dialog.tsx chunk 10: +function AlertDialogAction({ +className, +variant = "default", +size = "default", +...props +}: React.ComponentProps & +Pick, "variant" | "size">) { +return ( + +); +} +2026-08-02T00:19:19.097+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/ui/alert-dialog.tsx chunk 11: +function AlertDialogCancel({ +className, +variant = "outline", +size = "default", +...props +}: React.ComponentProps & +Pick, "variant" | "size">) { +return ( + +); +} +2026-08-02T00:19:19.097+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/ui/alert-dialog.tsx chunk 12: +export { +AlertDialog, +AlertDialogAction, +AlertDialogCancel, +AlertDialogContent, +AlertDialogDescription, +AlertDialogFooter, +AlertDialogHeader, +AlertDialogMedia, +AlertDialogOverlay, +AlertDialogPortal, +AlertDialogTitle, +AlertDialogTrigger, +}; +2026-08-02T00:19:19.108+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/ui/button.tsx chunk 0: +const buttonVariants = cva( +"inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", +{ +variants: { +variant: { +default: "bg-primary text-primary-foreground hover:bg-primary/90", +destructive: +"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40", +outline: +"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50", +secondary: +"bg-secondary text-secondary-foreground hover:bg-secondary/80", +ghost: +"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", +link: "text-primary underline-offset-4 hover:underline", +}, +size: { +default: "h-9 px-4 py-2 has-[>svg]:px-3", +xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3", +sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5", +lg: "h-10 rounded-md px-6 has-[>svg]:px-4", +icon: "size-9", +"icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3", +"icon-sm": "size-8", +"icon-lg": "size-10", +}, +}, +defaultVariants: { +variant: "default", +size: "default", +}, +}, +); +2026-08-02T00:19:19.108+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/ui/button.tsx chunk 1: +function Button({ +className, +variant = "default", +size = "default", +asChild = false, +...props +}: React.ComponentProps<"button"> & +VariantProps & { +asChild?: boolean; +}) { +const Comp = asChild ? Slot.Root : "button"; + + return ( + + ); + +} 2026-08-02T00:19:19.108+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : +Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/ui/button.tsx chunk 2: +export { Button, buttonVariants }; 2026-08-02T00:19:19.121+05:30 INFO 12976 --- [server] [ codebase-1] +c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file +client/src/components/ui/card.tsx chunk 0: +function Card ({ className, ...props }: React.ComponentProps<"div">) { return ( +
+); +} +2026-08-02T00:19:19.121+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/ui/card.tsx chunk 1: +function CardHeader({ className, ...props }: React.ComponentProps<"div">) { +return ( +
+); +} +2026-08-02T00:19:19.121+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/ui/card.tsx chunk 2: +function CardTitle({ className, ...props }: React.ComponentProps<"div">) { +return ( +
+); +} +2026-08-02T00:19:19.122+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/ui/card.tsx chunk 3: +function CardDescription({ className, ...props }: React.ComponentProps<"div">) { +return ( +
+); +} +2026-08-02T00:19:19.122+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/ui/card.tsx chunk 4: +function CardAction({ className, ...props }: React.ComponentProps<"div">) { +return ( +
+); +} +2026-08-02T00:19:19.122+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/ui/card.tsx chunk 5: +function CardContent({ className, ...props }: React.ComponentProps<"div">) { +return ( +
+); +} +2026-08-02T00:19:19.122+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/ui/card.tsx chunk 6: +function CardFooter({ className, ...props }: React.ComponentProps<"div">) { +return ( +
+); +} +2026-08-02T00:19:19.122+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/ui/card.tsx chunk 7: +export { +Card, +CardAction, +CardContent, +CardDescription, +CardFooter, +CardHeader, +CardTitle, +}; +2026-08-02T00:19:19.138+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/ui/form.tsx chunk 0: +"use client" +2026-08-02T00:19:19.139+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/ui/form.tsx chunk 1: +const Form = FormProvider +2026-08-02T00:19:19.139+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/ui/form.tsx chunk 2: +type FormFieldContextValue< +TFieldValues extends FieldValues = FieldValues, +TName extends FieldPath = FieldPath, +> = { +name: TName +} +2026-08-02T00:19:19.139+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/ui/form.tsx chunk 3: +const FormFieldContext = React.createContext( +{} as FormFieldContextValue +) +2026-08-02T00:19:19.139+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/ui/form.tsx chunk 4: +const FormField = < +TFieldValues extends FieldValues = FieldValues, +TName extends FieldPath = FieldPath, +>({ +...props +}: ControllerProps) => { +return ( + + + +) +} +2026-08-02T00:19:19.139+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/ui/form.tsx chunk 5: +const useFormField = () => { +const fieldContext = React.useContext(FormFieldContext) +const itemContext = React.useContext(FormItemContext) +const { getFieldState } = useFormContext() +const formState = useFormState({ name: fieldContext.name }) +const fieldState = getFieldState(fieldContext.name, formState) + +if (!fieldContext) { throw new Error ("useFormField should be used within ") +} + +const { id } = itemContext + +return { id, name: fieldContext.name, formItemId: `${id}-form-item`, formDescriptionId: `${id}-form-item-description`, +formMessageId: `${id}-form-item-message`, ...fieldState, } } 2026-08-02T00:19:19.139+05:30 INFO +12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase +861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/ui/form.tsx chunk 6: +type FormItemContextValue = { id: string } 2026-08-02T00:19:19.140+05:30 INFO 12976 --- [server] [ codebase-1] +c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file +client/src/components/ui/form.tsx chunk 7: +const FormItemContext = React.createContext({} as FormItemContextValue +) +2026-08-02T00:19:19.140+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : +Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/ui/form.tsx chunk 8: +function FormItem ({ className, ...props }: React.ComponentProps<"div">) { const id = React.useId () + +return ( + +
+ +) +} +2026-08-02T00:19:19.140+05:30 INFO 12976 --- [server] [ codebase-1] c.m.s.f.r.RepositoryFileProcessor : Codebase 861f1f06-e27e-42dc-972d-c716db49580a file client/src/components/ui/form.tsx chunk 9: +function FormLabel({ +className, +...props +}: React.ComponentProps) { +const { error, formItemId } = useFormField() + +return ( +