Major changes
This commit is contained in:
0
.gitignore
vendored
Normal file → Executable file
0
.gitignore
vendored
Normal file → Executable file
0
.mvn/jvm.config
Normal file → Executable file
0
.mvn/jvm.config
Normal file → Executable file
0
.mvn/maven.config
Normal file → Executable file
0
.mvn/maven.config
Normal file → Executable file
13
Dockerfile
Normal file → Executable file
13
Dockerfile
Normal file → Executable file
@@ -9,13 +9,24 @@ RUN mvn -B dependency:go-offline
|
||||
COPY src ./src
|
||||
RUN mvn -B clean package -DskipTests
|
||||
|
||||
|
||||
# -------- Runtime stage --------
|
||||
FROM eclipse-temurin:21-jre
|
||||
|
||||
# Install build dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
make \
|
||||
emacs-nox \
|
||||
python3 \
|
||||
python3-venv \
|
||||
python3-pip \
|
||||
git \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=build /app/target/*.jar app.jar
|
||||
|
||||
EXPOSE 9010
|
||||
|
||||
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||
0
src/main/java/org/zaine/app/Application.java
Normal file → Executable file
0
src/main/java/org/zaine/app/Application.java
Normal file → Executable file
0
src/main/java/org/zaine/app/controller/CommentsController.java
Normal file → Executable file
0
src/main/java/org/zaine/app/controller/CommentsController.java
Normal file → Executable file
0
src/main/java/org/zaine/app/controller/CompetenciesController.java
Normal file → Executable file
0
src/main/java/org/zaine/app/controller/CompetenciesController.java
Normal file → Executable file
2
src/main/java/org/zaine/app/controller/HealthController.java
Normal file → Executable file
2
src/main/java/org/zaine/app/controller/HealthController.java
Normal file → Executable file
@@ -6,7 +6,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
@RestController
|
||||
public class HealthController {
|
||||
|
||||
@GetMapping("/health")
|
||||
@GetMapping("/api/health")
|
||||
public String health() {
|
||||
return "OK";
|
||||
}
|
||||
|
||||
0
src/main/java/org/zaine/app/controller/NotesController.java
Normal file → Executable file
0
src/main/java/org/zaine/app/controller/NotesController.java
Normal file → Executable file
237
src/main/java/org/zaine/app/controller/zone/BuildController.java
Normal file
237
src/main/java/org/zaine/app/controller/zone/BuildController.java
Normal file
@@ -0,0 +1,237 @@
|
||||
package org.zaine.app.controller.zone;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
|
||||
public class BuildController {
|
||||
|
||||
// Separate state for each build
|
||||
private final AtomicBoolean webBuildRunning = new AtomicBoolean(false);
|
||||
private final AtomicBoolean roamBuildRunning = new AtomicBoolean(false);
|
||||
|
||||
private volatile Instant webLastRun;
|
||||
private volatile Integer webLastExitCode;
|
||||
|
||||
private volatile Instant roamLastRun;
|
||||
private volatile Integer roamLastExitCode;
|
||||
|
||||
private static final Logger log =
|
||||
LoggerFactory.getLogger(BuildController.class);
|
||||
|
||||
@Value("${zone.build.dir}")
|
||||
private String webBuildDirectory;
|
||||
|
||||
@Value("${zone.build.log}")
|
||||
private String webBuildLogFile;
|
||||
|
||||
@Value("${orgroam.build.dir}")
|
||||
private String roamBuildDirectory;
|
||||
|
||||
@Value("${orgroam.build.log}")
|
||||
private String roamBuildLogFile;
|
||||
|
||||
/* =========================================================
|
||||
WEB BUILD
|
||||
========================================================= */
|
||||
|
||||
@PostMapping("/build-web")
|
||||
public ResponseEntity<String> triggerWebBuild() {
|
||||
return startBuild(
|
||||
webBuildDirectory,
|
||||
webBuildLogFile,
|
||||
webBuildRunning,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
@GetMapping("/build-web/status")
|
||||
public ResponseEntity<BuildStatus> getWebBuildStatus() {
|
||||
return ResponseEntity.ok(
|
||||
new BuildStatus(
|
||||
webBuildRunning.get(),
|
||||
webLastRun,
|
||||
webLastExitCode
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@GetMapping("/build-web/logs")
|
||||
public SseEmitter streamWebLogs() {
|
||||
return streamLogs(webBuildLogFile);
|
||||
}
|
||||
|
||||
/* =========================================================
|
||||
ORG ROAM BUILD
|
||||
========================================================= */
|
||||
|
||||
@PostMapping("/build-roam")
|
||||
public ResponseEntity<String> triggerRoamBuild() {
|
||||
return startBuild(
|
||||
roamBuildDirectory,
|
||||
roamBuildLogFile,
|
||||
roamBuildRunning,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
@GetMapping("/build-roam/status")
|
||||
public ResponseEntity<BuildStatus> getRoamBuildStatus() {
|
||||
return ResponseEntity.ok(
|
||||
new BuildStatus(
|
||||
roamBuildRunning.get(),
|
||||
roamLastRun,
|
||||
roamLastExitCode
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@GetMapping("/build-roam/logs")
|
||||
public SseEmitter streamRoamLogs() {
|
||||
return streamLogs(roamBuildLogFile);
|
||||
}
|
||||
|
||||
/* =========================================================
|
||||
CORE BUILD LOGIC
|
||||
========================================================= */
|
||||
|
||||
private ResponseEntity<String> startBuild(
|
||||
String buildDir,
|
||||
String logPath,
|
||||
AtomicBoolean runningFlag,
|
||||
boolean isWeb
|
||||
) {
|
||||
|
||||
if (!runningFlag.compareAndSet(false, true)) {
|
||||
return ResponseEntity.status(HttpStatus.CONFLICT)
|
||||
.body("Build already running");
|
||||
}
|
||||
|
||||
try {
|
||||
File workingDir = Path.of(buildDir).toFile();
|
||||
File logFile = new File(logPath);
|
||||
|
||||
if (logFile.getParentFile() != null) {
|
||||
logFile.getParentFile().mkdirs();
|
||||
}
|
||||
|
||||
if (logFile.exists()) {
|
||||
logFile.delete();
|
||||
}
|
||||
|
||||
logFile.createNewFile();
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder("make");
|
||||
pb.directory(workingDir);
|
||||
pb.redirectErrorStream(true);
|
||||
pb.redirectOutput(ProcessBuilder.Redirect.appendTo(logFile));
|
||||
|
||||
log.info("Starting build in {}", workingDir.getAbsolutePath());
|
||||
|
||||
Process process = pb.start();
|
||||
|
||||
if (isWeb) {
|
||||
webLastRun = Instant.now();
|
||||
} else {
|
||||
roamLastRun = Instant.now();
|
||||
}
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
int exit = process.waitFor();
|
||||
|
||||
if (isWeb) {
|
||||
webLastExitCode = exit;
|
||||
} else {
|
||||
roamLastExitCode = exit;
|
||||
}
|
||||
|
||||
log.info("Build finished with exit code {}", exit);
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.error("Build thread interrupted", e);
|
||||
} finally {
|
||||
runningFlag.set(false);
|
||||
}
|
||||
}).start();
|
||||
|
||||
return ResponseEntity.ok("Build started");
|
||||
|
||||
} catch (IOException e) {
|
||||
runningFlag.set(false);
|
||||
log.error("Build failed to start", e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body("Failed to start build: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/* =========================================================
|
||||
LOG STREAMING (SSE)
|
||||
========================================================= */
|
||||
|
||||
private SseEmitter streamLogs(String logFilePath) {
|
||||
|
||||
SseEmitter emitter = new SseEmitter(0L);
|
||||
|
||||
new Thread(() -> {
|
||||
try (RandomAccessFile file = new RandomAccessFile(logFilePath, "r")) {
|
||||
|
||||
long pointer = 0;
|
||||
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
|
||||
long length = file.length();
|
||||
|
||||
if (length > pointer) {
|
||||
file.seek(pointer);
|
||||
|
||||
String line;
|
||||
while ((line = file.readLine()) != null) {
|
||||
emitter.send(SseEmitter.event().data(line));
|
||||
}
|
||||
|
||||
pointer = file.getFilePointer();
|
||||
}
|
||||
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.info("Log stream closed");
|
||||
emitter.complete();
|
||||
}
|
||||
}).start();
|
||||
|
||||
return emitter;
|
||||
}
|
||||
|
||||
/* =========================================================
|
||||
DTO
|
||||
========================================================= */
|
||||
|
||||
static class BuildStatus {
|
||||
public boolean running;
|
||||
public Instant lastRun;
|
||||
public Integer lastExitCode;
|
||||
|
||||
public BuildStatus(boolean running, Instant lastRun, Integer lastExitCode) {
|
||||
this.running = running;
|
||||
this.lastRun = lastRun;
|
||||
this.lastExitCode = lastExitCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
0
src/main/java/org/zaine/app/dto/CompetenciesDTO.java
Normal file → Executable file
0
src/main/java/org/zaine/app/dto/CompetenciesDTO.java
Normal file → Executable file
0
src/main/java/org/zaine/app/dto/CreateCommentDTO.java
Normal file → Executable file
0
src/main/java/org/zaine/app/dto/CreateCommentDTO.java
Normal file → Executable file
0
src/main/java/org/zaine/app/dto/CreateNoteDTO.java
Normal file → Executable file
0
src/main/java/org/zaine/app/dto/CreateNoteDTO.java
Normal file → Executable file
0
src/main/java/org/zaine/app/model/Comments.java
Normal file → Executable file
0
src/main/java/org/zaine/app/model/Comments.java
Normal file → Executable file
0
src/main/java/org/zaine/app/model/Competencies.java
Normal file → Executable file
0
src/main/java/org/zaine/app/model/Competencies.java
Normal file → Executable file
7
src/main/java/org/zaine/app/model/Notes.java
Normal file → Executable file
7
src/main/java/org/zaine/app/model/Notes.java
Normal file → Executable file
@@ -10,6 +10,7 @@ import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.hibernate.annotations.CreationTimestamp;
|
||||
|
||||
@Entity
|
||||
@Table(name = "public_notes")
|
||||
@@ -28,8 +29,10 @@ public class Notes {
|
||||
private String authorName;
|
||||
|
||||
@JsonProperty("created_at")
|
||||
@Column(name="created_at")
|
||||
private Instant createdAt;
|
||||
|
||||
@CreationTimestamp
|
||||
@Column(name = "created_at", nullable = false, updatable = false)
|
||||
private Instant createdAt;
|
||||
|
||||
|
||||
public UUID getId() {
|
||||
|
||||
0
src/main/java/org/zaine/app/repositories/CommentsRepository.java
Normal file → Executable file
0
src/main/java/org/zaine/app/repositories/CommentsRepository.java
Normal file → Executable file
0
src/main/java/org/zaine/app/repositories/CompetenciesRepository.java
Normal file → Executable file
0
src/main/java/org/zaine/app/repositories/CompetenciesRepository.java
Normal file → Executable file
0
src/main/java/org/zaine/app/repositories/NotesRepository.java
Normal file → Executable file
0
src/main/java/org/zaine/app/repositories/NotesRepository.java
Normal file → Executable file
0
src/main/java/org/zaine/app/service/CommentsService.java
Normal file → Executable file
0
src/main/java/org/zaine/app/service/CommentsService.java
Normal file → Executable file
0
src/main/java/org/zaine/app/service/CompetenciesService.java
Normal file → Executable file
0
src/main/java/org/zaine/app/service/CompetenciesService.java
Normal file → Executable file
0
src/main/java/org/zaine/app/service/NotesService.java
Normal file → Executable file
0
src/main/java/org/zaine/app/service/NotesService.java
Normal file → Executable file
0
src/test/java/org/zaine/app/ApplicationTest.java
Normal file → Executable file
0
src/test/java/org/zaine/app/ApplicationTest.java
Normal file → Executable file
0
src/test/java/org/zaine/app/service/CommentsServiceTest.java
Normal file → Executable file
0
src/test/java/org/zaine/app/service/CommentsServiceTest.java
Normal file → Executable file
0
src/test/java/org/zaine/app/service/CompetenciesServiceTest.java
Normal file → Executable file
0
src/test/java/org/zaine/app/service/CompetenciesServiceTest.java
Normal file → Executable file
0
src/test/java/org/zaine/app/service/NotesServiceTest.java
Normal file → Executable file
0
src/test/java/org/zaine/app/service/NotesServiceTest.java
Normal file → Executable file
Reference in New Issue
Block a user