diff --git a/cameleer3-server-core/src/main/java/com/cameleer3/server/core/runtime/AppService.java b/cameleer3-server-core/src/main/java/com/cameleer3/server/core/runtime/AppService.java new file mode 100644 index 00000000..da474c0b --- /dev/null +++ b/cameleer3-server-core/src/main/java/com/cameleer3/server/core/runtime/AppService.java @@ -0,0 +1,79 @@ +package com.cameleer3.server.core.runtime; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.util.HexFormat; +import java.util.List; +import java.util.UUID; + +public class AppService { + private static final Logger log = LoggerFactory.getLogger(AppService.class); + + private final AppRepository appRepo; + private final AppVersionRepository versionRepo; + private final String jarStoragePath; + + public AppService(AppRepository appRepo, AppVersionRepository versionRepo, String jarStoragePath) { + this.appRepo = appRepo; + this.versionRepo = versionRepo; + this.jarStoragePath = jarStoragePath; + } + + public List listByEnvironment(UUID environmentId) { return appRepo.findByEnvironmentId(environmentId); } + public App getById(UUID id) { return appRepo.findById(id).orElseThrow(() -> new IllegalArgumentException("App not found: " + id)); } + public List listVersions(UUID appId) { return versionRepo.findByAppId(appId); } + + public UUID createApp(UUID environmentId, String slug, String displayName) { + if (appRepo.findByEnvironmentIdAndSlug(environmentId, slug).isPresent()) { + throw new IllegalArgumentException("App with slug '" + slug + "' already exists in this environment"); + } + return appRepo.create(environmentId, slug, displayName); + } + + public AppVersion uploadJar(UUID appId, String filename, InputStream jarData, long size) throws IOException { + getById(appId); // verify app exists + int nextVersion = versionRepo.findMaxVersion(appId) + 1; + + // Store JAR: {jarStoragePath}/{appId}/v{version}/app.jar + Path versionDir = Path.of(jarStoragePath, appId.toString(), "v" + nextVersion); + Files.createDirectories(versionDir); + Path jarFile = versionDir.resolve("app.jar"); + + MessageDigest digest; + try { digest = MessageDigest.getInstance("SHA-256"); } + catch (Exception e) { throw new RuntimeException(e); } + + try (InputStream in = jarData) { + byte[] buffer = new byte[8192]; + int bytesRead; + try (var out = Files.newOutputStream(jarFile)) { + while ((bytesRead = in.read(buffer)) != -1) { + out.write(buffer, 0, bytesRead); + digest.update(buffer, 0, bytesRead); + } + } + } + + String checksum = HexFormat.of().formatHex(digest.digest()); + UUID versionId = versionRepo.create(appId, nextVersion, jarFile.toString(), checksum, filename, size); + + log.info("Uploaded JAR for app {}: version={}, size={}, sha256={}", appId, nextVersion, size, checksum); + return versionRepo.findById(versionId).orElseThrow(); + } + + public String resolveJarPath(UUID appVersionId) { + AppVersion version = versionRepo.findById(appVersionId) + .orElseThrow(() -> new IllegalArgumentException("AppVersion not found: " + appVersionId)); + return version.jarPath(); + } + + public void deleteApp(UUID id) { + appRepo.delete(id); + } +} diff --git a/cameleer3-server-core/src/main/java/com/cameleer3/server/core/runtime/DeploymentService.java b/cameleer3-server-core/src/main/java/com/cameleer3/server/core/runtime/DeploymentService.java new file mode 100644 index 00000000..be313b1e --- /dev/null +++ b/cameleer3-server-core/src/main/java/com/cameleer3/server/core/runtime/DeploymentService.java @@ -0,0 +1,53 @@ +package com.cameleer3.server.core.runtime; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.UUID; + +public class DeploymentService { + private static final Logger log = LoggerFactory.getLogger(DeploymentService.class); + + private final DeploymentRepository deployRepo; + private final AppService appService; + private final EnvironmentService envService; + + public DeploymentService(DeploymentRepository deployRepo, AppService appService, EnvironmentService envService) { + this.deployRepo = deployRepo; + this.appService = appService; + this.envService = envService; + } + + public List listByApp(UUID appId) { return deployRepo.findByAppId(appId); } + public Deployment getById(UUID id) { return deployRepo.findById(id).orElseThrow(() -> new IllegalArgumentException("Deployment not found: " + id)); } + + /** Create a deployment record. Actual container start is handled by DeploymentExecutor (async). */ + public Deployment createDeployment(UUID appId, UUID appVersionId, UUID environmentId) { + App app = appService.getById(appId); + Environment env = envService.getById(environmentId); + String containerName = env.slug() + "-" + app.slug(); + + UUID deploymentId = deployRepo.create(appId, appVersionId, environmentId, containerName); + return deployRepo.findById(deploymentId).orElseThrow(); + } + + /** Promote: deploy the same app version to a different environment. */ + public Deployment promote(UUID appId, UUID appVersionId, UUID targetEnvironmentId) { + return createDeployment(appId, appVersionId, targetEnvironmentId); + } + + public void markRunning(UUID deploymentId, String containerId) { + deployRepo.updateStatus(deploymentId, DeploymentStatus.RUNNING, containerId, null); + deployRepo.markDeployed(deploymentId); + } + + public void markFailed(UUID deploymentId, String errorMessage) { + deployRepo.updateStatus(deploymentId, DeploymentStatus.FAILED, null, errorMessage); + } + + public void markStopped(UUID deploymentId) { + deployRepo.updateStatus(deploymentId, DeploymentStatus.STOPPED, null, null); + deployRepo.markStopped(deploymentId); + } +} diff --git a/cameleer3-server-core/src/main/java/com/cameleer3/server/core/runtime/EnvironmentService.java b/cameleer3-server-core/src/main/java/com/cameleer3/server/core/runtime/EnvironmentService.java new file mode 100644 index 00000000..b5683286 --- /dev/null +++ b/cameleer3-server-core/src/main/java/com/cameleer3/server/core/runtime/EnvironmentService.java @@ -0,0 +1,37 @@ +package com.cameleer3.server.core.runtime; + +import java.util.List; +import java.util.UUID; + +public class EnvironmentService { + private final EnvironmentRepository repo; + + public EnvironmentService(EnvironmentRepository repo) { + this.repo = repo; + } + + public List listAll() { return repo.findAll(); } + + public Environment getById(UUID id) { + return repo.findById(id).orElseThrow(() -> new IllegalArgumentException("Environment not found: " + id)); + } + + public Environment getBySlug(String slug) { + return repo.findBySlug(slug).orElseThrow(() -> new IllegalArgumentException("Environment not found: " + slug)); + } + + public UUID create(String slug, String displayName) { + if (repo.findBySlug(slug).isPresent()) { + throw new IllegalArgumentException("Environment with slug '" + slug + "' already exists"); + } + return repo.create(slug, displayName); + } + + public void delete(UUID id) { + Environment env = getById(id); + if ("default".equals(env.slug())) { + throw new IllegalArgumentException("Cannot delete the default environment"); + } + repo.delete(id); + } +}