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
|
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
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
|
@RestController
|
||||||
public class HealthController {
|
public class HealthController {
|
||||||
|
|
||||||
@GetMapping("/health")
|
@GetMapping("/api/health")
|
||||||
public String health() {
|
public String health() {
|
||||||
return "OK";
|
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 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() {
|
||||||
|
|||||||
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