feat: add production/enabled flags to environments, drop status enum

Environments now have:
- production (bool): prod vs non-prod resource allocation
- enabled (bool): disabled blocks new deployments

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-08 11:16:09 +02:00
parent d9160b7d0e
commit 2e006051bc
14 changed files with 93 additions and 46 deletions

View File

@@ -7,21 +7,12 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* Admin endpoints for environment management.
* Protected by {@code ROLE_ADMIN}.
*/
@RestController
@RequestMapping("/api/v1/admin/environments")
@Tag(name = "Environment Admin", description = "Environment management (ADMIN only)")
@@ -36,7 +27,6 @@ public class EnvironmentAdminController {
@GetMapping
@Operation(summary = "List all environments")
@ApiResponse(responseCode = "200", description = "Environment list returned")
public ResponseEntity<List<Environment>> listEnvironments() {
return ResponseEntity.ok(environmentService.listAll());
}
@@ -57,12 +47,28 @@ public class EnvironmentAdminController {
@Operation(summary = "Create a new environment")
@ApiResponse(responseCode = "201", description = "Environment created")
@ApiResponse(responseCode = "400", description = "Slug already exists")
public ResponseEntity<Environment> createEnvironment(@RequestBody CreateEnvironmentRequest request) {
public ResponseEntity<?> createEnvironment(@RequestBody CreateEnvironmentRequest request) {
try {
UUID id = environmentService.create(request.slug(), request.displayName());
UUID id = environmentService.create(request.slug(), request.displayName(), request.production());
return ResponseEntity.status(201).body(environmentService.getById(id));
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().build();
return ResponseEntity.badRequest().body(Map.of("error", e.getMessage()));
}
}
@PutMapping("/{id}")
@Operation(summary = "Update an environment")
@ApiResponse(responseCode = "200", description = "Environment updated")
@ApiResponse(responseCode = "404", description = "Environment not found")
public ResponseEntity<?> updateEnvironment(@PathVariable UUID id, @RequestBody UpdateEnvironmentRequest request) {
try {
environmentService.update(id, request.displayName(), request.production(), request.enabled());
return ResponseEntity.ok(environmentService.getById(id));
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("not found")) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.badRequest().body(Map.of("error", e.getMessage()));
}
}
@@ -71,7 +77,7 @@ public class EnvironmentAdminController {
@ApiResponse(responseCode = "204", description = "Environment deleted")
@ApiResponse(responseCode = "400", description = "Cannot delete default environment")
@ApiResponse(responseCode = "404", description = "Environment not found")
public ResponseEntity<Void> deleteEnvironment(@PathVariable UUID id) {
public ResponseEntity<?> deleteEnvironment(@PathVariable UUID id) {
try {
environmentService.delete(id);
return ResponseEntity.noContent().build();
@@ -79,9 +85,10 @@ public class EnvironmentAdminController {
if (e.getMessage().contains("not found")) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.badRequest().build();
return ResponseEntity.badRequest().body(Map.of("error", e.getMessage()));
}
}
public record CreateEnvironmentRequest(String slug, String displayName) {}
public record CreateEnvironmentRequest(String slug, String displayName, boolean production) {}
public record UpdateEnvironmentRequest(String displayName, boolean production, boolean enabled) {}
}

View File

@@ -2,7 +2,6 @@ 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;
@@ -21,14 +20,15 @@ public class PostgresEnvironmentRepository implements EnvironmentRepository {
@Override
public List<Environment> findAll() {
return jdbc.query("SELECT id, slug, display_name, status, created_at FROM environments ORDER BY created_at",
return jdbc.query(
"SELECT id, slug, display_name, production, enabled, 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 = ?",
"SELECT id, slug, display_name, production, enabled, created_at FROM environments WHERE id = ?",
(rs, rowNum) -> mapRow(rs), id);
return results.isEmpty() ? Optional.empty() : Optional.of(results.get(0));
}
@@ -36,29 +36,23 @@ public class PostgresEnvironmentRepository implements EnvironmentRepository {
@Override
public Optional<Environment> findBySlug(String slug) {
var results = jdbc.query(
"SELECT id, slug, display_name, status, created_at FROM environments WHERE slug = ?",
"SELECT id, slug, display_name, production, enabled, 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) {
public UUID create(String slug, String displayName, boolean production) {
UUID id = UUID.randomUUID();
jdbc.update("INSERT INTO environments (id, slug, display_name) VALUES (?, ?, ?)",
id, slug, displayName);
jdbc.update("INSERT INTO environments (id, slug, display_name, production) VALUES (?, ?, ?, ?)",
id, slug, displayName, production);
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);
public void update(UUID id, String displayName, boolean production, boolean enabled) {
jdbc.update("UPDATE environments SET display_name = ?, production = ?, enabled = ?, updated_at = now() WHERE id = ?",
displayName, production, enabled, id);
}
@Override
@@ -71,7 +65,8 @@ public class PostgresEnvironmentRepository implements EnvironmentRepository {
UUID.fromString(rs.getString("id")),
rs.getString("slug"),
rs.getString("display_name"),
EnvironmentStatus.valueOf(rs.getString("status")),
rs.getBoolean("production"),
rs.getBoolean("enabled"),
rs.getTimestamp("created_at").toInstant()
);
}

View File

@@ -0,0 +1,6 @@
-- V4__environment_config.sql
-- Add production flag and enabled flag to environments, drop unused status column
ALTER TABLE environments ADD COLUMN production BOOLEAN NOT NULL DEFAULT false;
ALTER TABLE environments ADD COLUMN enabled BOOLEAN NOT NULL DEFAULT true;
ALTER TABLE environments DROP COLUMN status;