Major changes

This commit is contained in:
2026-02-22 22:09:12 +00:00
parent 4b7b367730
commit 1ec9558bf7
29 changed files with 255 additions and 4 deletions

0
.gitignore vendored Normal file → Executable file
View File

0
.mvn/jvm.config Normal file → Executable file
View File

0
.mvn/maven.config Normal file → Executable file
View File

13
Dockerfile Normal file → Executable file
View File

@@ -9,13 +9,24 @@ RUN mvn -B dependency:go-offline
COPY src ./src COPY src ./src
RUN mvn -B clean package -DskipTests RUN mvn -B clean package -DskipTests
# -------- Runtime stage -------- # -------- Runtime stage --------
FROM eclipse-temurin:21-jre 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 WORKDIR /app
COPY --from=build /app/target/*.jar app.jar COPY --from=build /app/target/*.jar app.jar
EXPOSE 9010 EXPOSE 9010
ENTRYPOINT ["java", "-jar", "app.jar"] ENTRYPOINT ["java", "-jar", "app.jar"]

0
Makefile Normal file → Executable file
View File

0
README.md Normal file → Executable file
View File

0
pom.xml Normal file → Executable file
View File

0
src/main/java/org/zaine/app/Application.java Normal file → Executable file
View File

View File

View File

View File

@@ -6,7 +6,7 @@ import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
public class HealthController { public class HealthController {
@GetMapping("/health") @GetMapping("/api/health")
public String health() { public String health() {
return "OK"; return "OK";
} }

View File

View 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
View File

0
src/main/java/org/zaine/app/dto/CreateCommentDTO.java Normal file → Executable file
View File

0
src/main/java/org/zaine/app/dto/CreateNoteDTO.java Normal file → Executable file
View File

0
src/main/java/org/zaine/app/model/Comments.java Normal file → Executable file
View File

0
src/main/java/org/zaine/app/model/Competencies.java Normal file → Executable file
View File

7
src/main/java/org/zaine/app/model/Notes.java Normal file → Executable file
View File

@@ -10,6 +10,7 @@ import java.time.Instant;
import java.util.UUID; import java.util.UUID;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.annotations.CreationTimestamp;
@Entity @Entity
@Table(name = "public_notes") @Table(name = "public_notes")
@@ -28,8 +29,10 @@ public class Notes {
private String authorName; private String authorName;
@JsonProperty("created_at") @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() { public UUID getId() {

View File

View File

View File

View File

View File

0
src/main/java/org/zaine/app/service/NotesService.java Normal file → Executable file
View File

0
src/test/java/org/zaine/app/ApplicationTest.java Normal file → Executable file
View File

View File

View File

View File