feat: implement PostgreSQL repositories for runtime management
- PostgresEnvironmentRepository, PostgresAppRepository - PostgresAppVersionRepository, PostgresDeploymentRepository - RuntimeBeanConfig wiring repositories, services, and async executor Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
package com.cameleer3.server.app.config;
|
||||
|
||||
import com.cameleer3.server.app.storage.PostgresAppRepository;
|
||||
import com.cameleer3.server.app.storage.PostgresAppVersionRepository;
|
||||
import com.cameleer3.server.app.storage.PostgresDeploymentRepository;
|
||||
import com.cameleer3.server.app.storage.PostgresEnvironmentRepository;
|
||||
import com.cameleer3.server.core.runtime.AppRepository;
|
||||
import com.cameleer3.server.core.runtime.AppService;
|
||||
import com.cameleer3.server.core.runtime.AppVersionRepository;
|
||||
import com.cameleer3.server.core.runtime.DeploymentRepository;
|
||||
import com.cameleer3.server.core.runtime.DeploymentService;
|
||||
import com.cameleer3.server.core.runtime.EnvironmentRepository;
|
||||
import com.cameleer3.server.core.runtime.EnvironmentService;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* Creates runtime management beans: repositories, services, and async executor.
|
||||
* <p>
|
||||
* Follows the established pattern: core module plain class, app module bean config.
|
||||
*/
|
||||
@Configuration
|
||||
public class RuntimeBeanConfig {
|
||||
|
||||
@Bean
|
||||
public EnvironmentRepository environmentRepository(JdbcTemplate jdbc) {
|
||||
return new PostgresEnvironmentRepository(jdbc);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AppRepository appRepository(JdbcTemplate jdbc) {
|
||||
return new PostgresAppRepository(jdbc);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AppVersionRepository appVersionRepository(JdbcTemplate jdbc) {
|
||||
return new PostgresAppVersionRepository(jdbc);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DeploymentRepository deploymentRepository(JdbcTemplate jdbc) {
|
||||
return new PostgresDeploymentRepository(jdbc);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public EnvironmentService environmentService(EnvironmentRepository repo) {
|
||||
return new EnvironmentService(repo);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AppService appService(AppRepository appRepo, AppVersionRepository versionRepo,
|
||||
@Value("${cameleer.runtime.jar-storage-path:/data/jars}") String jarStoragePath) {
|
||||
return new AppService(appRepo, versionRepo, jarStoragePath);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DeploymentService deploymentService(DeploymentRepository deployRepo, AppService appService, EnvironmentService envService) {
|
||||
return new DeploymentService(deployRepo, appService, envService);
|
||||
}
|
||||
|
||||
@Bean(name = "deploymentExecutor")
|
||||
public Executor deploymentTaskExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(4);
|
||||
executor.setMaxPoolSize(4);
|
||||
executor.setQueueCapacity(25);
|
||||
executor.setThreadNamePrefix("deploy-");
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.cameleer3.server.app.storage;
|
||||
|
||||
import com.cameleer3.server.core.runtime.App;
|
||||
import com.cameleer3.server.core.runtime.AppRepository;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public class PostgresAppRepository implements AppRepository {
|
||||
|
||||
private final JdbcTemplate jdbc;
|
||||
|
||||
public PostgresAppRepository(JdbcTemplate jdbc) {
|
||||
this.jdbc = jdbc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<App> findByEnvironmentId(UUID environmentId) {
|
||||
return jdbc.query(
|
||||
"SELECT id, environment_id, slug, display_name, created_at FROM apps WHERE environment_id = ? ORDER BY created_at",
|
||||
(rs, rowNum) -> mapRow(rs), environmentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<App> findById(UUID id) {
|
||||
var results = jdbc.query(
|
||||
"SELECT id, environment_id, slug, display_name, created_at FROM apps WHERE id = ?",
|
||||
(rs, rowNum) -> mapRow(rs), id);
|
||||
return results.isEmpty() ? Optional.empty() : Optional.of(results.get(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<App> findByEnvironmentIdAndSlug(UUID environmentId, String slug) {
|
||||
var results = jdbc.query(
|
||||
"SELECT id, environment_id, slug, display_name, created_at FROM apps WHERE environment_id = ? AND slug = ?",
|
||||
(rs, rowNum) -> mapRow(rs), environmentId, slug);
|
||||
return results.isEmpty() ? Optional.empty() : Optional.of(results.get(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID create(UUID environmentId, String slug, String displayName) {
|
||||
UUID id = UUID.randomUUID();
|
||||
jdbc.update("INSERT INTO apps (id, environment_id, slug, display_name) VALUES (?, ?, ?, ?)",
|
||||
id, environmentId, slug, displayName);
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(UUID id) {
|
||||
jdbc.update("DELETE FROM apps WHERE id = ?", id);
|
||||
}
|
||||
|
||||
private App mapRow(ResultSet rs) throws SQLException {
|
||||
return new App(
|
||||
UUID.fromString(rs.getString("id")),
|
||||
UUID.fromString(rs.getString("environment_id")),
|
||||
rs.getString("slug"),
|
||||
rs.getString("display_name"),
|
||||
rs.getTimestamp("created_at").toInstant()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.cameleer3.server.app.storage;
|
||||
|
||||
import com.cameleer3.server.core.runtime.AppVersion;
|
||||
import com.cameleer3.server.core.runtime.AppVersionRepository;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public class PostgresAppVersionRepository implements AppVersionRepository {
|
||||
|
||||
private final JdbcTemplate jdbc;
|
||||
|
||||
public PostgresAppVersionRepository(JdbcTemplate jdbc) {
|
||||
this.jdbc = jdbc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AppVersion> findByAppId(UUID appId) {
|
||||
return jdbc.query(
|
||||
"SELECT id, app_id, version, jar_path, jar_checksum, jar_filename, jar_size_bytes, uploaded_at FROM app_versions WHERE app_id = ? ORDER BY version DESC",
|
||||
(rs, rowNum) -> mapRow(rs), appId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<AppVersion> findById(UUID id) {
|
||||
var results = jdbc.query(
|
||||
"SELECT id, app_id, version, jar_path, jar_checksum, jar_filename, jar_size_bytes, uploaded_at FROM app_versions WHERE id = ?",
|
||||
(rs, rowNum) -> mapRow(rs), id);
|
||||
return results.isEmpty() ? Optional.empty() : Optional.of(results.get(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int findMaxVersion(UUID appId) {
|
||||
Integer max = jdbc.queryForObject(
|
||||
"SELECT COALESCE(MAX(version), 0) FROM app_versions WHERE app_id = ?",
|
||||
Integer.class, appId);
|
||||
return max != null ? max : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID create(UUID appId, int version, String jarPath, String jarChecksum, String jarFilename, Long jarSizeBytes) {
|
||||
UUID id = UUID.randomUUID();
|
||||
jdbc.update("INSERT INTO app_versions (id, app_id, version, jar_path, jar_checksum, jar_filename, jar_size_bytes) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
id, appId, version, jarPath, jarChecksum, jarFilename, jarSizeBytes);
|
||||
return id;
|
||||
}
|
||||
|
||||
private AppVersion mapRow(ResultSet rs) throws SQLException {
|
||||
Long sizeBytes = rs.getLong("jar_size_bytes");
|
||||
if (rs.wasNull()) sizeBytes = null;
|
||||
return new AppVersion(
|
||||
UUID.fromString(rs.getString("id")),
|
||||
UUID.fromString(rs.getString("app_id")),
|
||||
rs.getInt("version"),
|
||||
rs.getString("jar_path"),
|
||||
rs.getString("jar_checksum"),
|
||||
rs.getString("jar_filename"),
|
||||
sizeBytes,
|
||||
rs.getTimestamp("uploaded_at").toInstant()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.cameleer3.server.app.storage;
|
||||
|
||||
import com.cameleer3.server.core.runtime.Deployment;
|
||||
import com.cameleer3.server.core.runtime.DeploymentRepository;
|
||||
import com.cameleer3.server.core.runtime.DeploymentStatus;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public class PostgresDeploymentRepository implements DeploymentRepository {
|
||||
|
||||
private final JdbcTemplate jdbc;
|
||||
|
||||
public PostgresDeploymentRepository(JdbcTemplate jdbc) {
|
||||
this.jdbc = jdbc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Deployment> findByAppId(UUID appId) {
|
||||
return jdbc.query(
|
||||
"SELECT id, app_id, app_version_id, environment_id, status, container_id, container_name, error_message, deployed_at, stopped_at, created_at FROM deployments WHERE app_id = ? ORDER BY created_at DESC",
|
||||
(rs, rowNum) -> mapRow(rs), appId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Deployment> findByEnvironmentId(UUID environmentId) {
|
||||
return jdbc.query(
|
||||
"SELECT id, app_id, app_version_id, environment_id, status, container_id, container_name, error_message, deployed_at, stopped_at, created_at FROM deployments WHERE environment_id = ? ORDER BY created_at DESC",
|
||||
(rs, rowNum) -> mapRow(rs), environmentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Deployment> findById(UUID id) {
|
||||
var results = jdbc.query(
|
||||
"SELECT id, app_id, app_version_id, environment_id, status, container_id, container_name, error_message, deployed_at, stopped_at, created_at FROM deployments WHERE id = ?",
|
||||
(rs, rowNum) -> mapRow(rs), id);
|
||||
return results.isEmpty() ? Optional.empty() : Optional.of(results.get(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Deployment> findActiveByAppIdAndEnvironmentId(UUID appId, UUID environmentId) {
|
||||
var results = jdbc.query(
|
||||
"SELECT id, app_id, app_version_id, environment_id, status, container_id, container_name, error_message, deployed_at, stopped_at, created_at FROM deployments WHERE app_id = ? AND environment_id = ? AND status IN ('STARTING', 'RUNNING') ORDER BY created_at DESC LIMIT 1",
|
||||
(rs, rowNum) -> mapRow(rs), appId, environmentId);
|
||||
return results.isEmpty() ? Optional.empty() : Optional.of(results.get(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID create(UUID appId, UUID appVersionId, UUID environmentId, String containerName) {
|
||||
UUID id = UUID.randomUUID();
|
||||
jdbc.update("INSERT INTO deployments (id, app_id, app_version_id, environment_id, container_name) VALUES (?, ?, ?, ?, ?)",
|
||||
id, appId, appVersionId, environmentId, containerName);
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateStatus(UUID id, DeploymentStatus status, String containerId, String errorMessage) {
|
||||
jdbc.update("UPDATE deployments SET status = ?, container_id = ?, error_message = ? WHERE id = ?",
|
||||
status.name(), containerId, errorMessage, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markDeployed(UUID id) {
|
||||
jdbc.update("UPDATE deployments SET deployed_at = now() WHERE id = ?", id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markStopped(UUID id) {
|
||||
jdbc.update("UPDATE deployments SET stopped_at = now() WHERE id = ?", id);
|
||||
}
|
||||
|
||||
private Deployment mapRow(ResultSet rs) throws SQLException {
|
||||
Timestamp deployedAt = rs.getTimestamp("deployed_at");
|
||||
Timestamp stoppedAt = rs.getTimestamp("stopped_at");
|
||||
return new Deployment(
|
||||
UUID.fromString(rs.getString("id")),
|
||||
UUID.fromString(rs.getString("app_id")),
|
||||
UUID.fromString(rs.getString("app_version_id")),
|
||||
UUID.fromString(rs.getString("environment_id")),
|
||||
DeploymentStatus.valueOf(rs.getString("status")),
|
||||
rs.getString("container_id"),
|
||||
rs.getString("container_name"),
|
||||
rs.getString("error_message"),
|
||||
deployedAt != null ? deployedAt.toInstant() : null,
|
||||
stoppedAt != null ? stoppedAt.toInstant() : null,
|
||||
rs.getTimestamp("created_at").toInstant()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.cameleer3.server.app.storage;
|
||||
|
||||
import com.cameleer3.server.core.runtime.Environment;
|
||||
import com.cameleer3.server.core.runtime.EnvironmentRepository;
|
||||
import com.cameleer3.server.core.runtime.EnvironmentStatus;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public class PostgresEnvironmentRepository implements EnvironmentRepository {
|
||||
|
||||
private final JdbcTemplate jdbc;
|
||||
|
||||
public PostgresEnvironmentRepository(JdbcTemplate jdbc) {
|
||||
this.jdbc = jdbc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Environment> findAll() {
|
||||
return jdbc.query("SELECT id, slug, display_name, status, created_at FROM environments ORDER BY created_at",
|
||||
(rs, rowNum) -> mapRow(rs));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Environment> findById(UUID id) {
|
||||
var results = jdbc.query(
|
||||
"SELECT id, slug, display_name, status, created_at FROM environments WHERE id = ?",
|
||||
(rs, rowNum) -> mapRow(rs), id);
|
||||
return results.isEmpty() ? Optional.empty() : Optional.of(results.get(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Environment> findBySlug(String slug) {
|
||||
var results = jdbc.query(
|
||||
"SELECT id, slug, display_name, status, created_at FROM environments WHERE slug = ?",
|
||||
(rs, rowNum) -> mapRow(rs), slug);
|
||||
return results.isEmpty() ? Optional.empty() : Optional.of(results.get(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID create(String slug, String displayName) {
|
||||
UUID id = UUID.randomUUID();
|
||||
jdbc.update("INSERT INTO environments (id, slug, display_name) VALUES (?, ?, ?)",
|
||||
id, slug, displayName);
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName(UUID id, String displayName) {
|
||||
jdbc.update("UPDATE environments SET display_name = ?, updated_at = now() WHERE id = ?",
|
||||
displayName, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateStatus(UUID id, EnvironmentStatus status) {
|
||||
jdbc.update("UPDATE environments SET status = ?, updated_at = now() WHERE id = ?",
|
||||
status.name(), id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(UUID id) {
|
||||
jdbc.update("DELETE FROM environments WHERE id = ?", id);
|
||||
}
|
||||
|
||||
private Environment mapRow(ResultSet rs) throws SQLException {
|
||||
return new Environment(
|
||||
UUID.fromString(rs.getString("id")),
|
||||
rs.getString("slug"),
|
||||
rs.getString("display_name"),
|
||||
EnvironmentStatus.valueOf(rs.getString("status")),
|
||||
rs.getTimestamp("created_at").toInstant()
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user