chore: add missing source modules to version control
Deploy to Production / deploy (push) Failing after 7s

apix-demo, apix-portal/src, apix-spider/src, apix-registry/src,
apix-common/src were never staged. Without them the CI build has no
source to compile and the Docker images cannot be produced.

Also adds docs/ (infrastructure notes) missed in prior commits.

Co-Authored-By: Mira <noreply@anthropic.com>
This commit is contained in:
Carsten Rehfeld
2026-05-14 15:49:03 +02:00
parent a9b3354bde
commit 46f32c2df2
87 changed files with 6657 additions and 34 deletions
@@ -0,0 +1,99 @@
package org.botstandards.apix.spider;
import io.quarkus.logging.Log;
import io.quarkus.scheduler.Scheduled;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* Purges expired sandboxes and all their associated data.
* Single concern: cleanup. No liveness probing, no reporting.
*
* Deletion order respects the service_versions → services FK.
* The sandbox child tables (feedback, usage_stats, agent_visits) have no
* active FK constraints after changeset 020, but are still cleaned up
* explicitly to avoid orphaned rows.
*/
@ApplicationScoped
public class SandboxCleanupJob {
@Inject
DataSource dataSource;
@Scheduled(cron = "${apix.spider.cleanup-cron:0 0 * * * ?}")
void purgeExpiredSandboxes() {
try (Connection conn = dataSource.getConnection()) {
conn.setAutoCommit(false);
try {
// DEMO tier sandboxes never expire — they are excluded from all deletes
long count = countExpired(conn);
if (count == 0) {
conn.rollback();
return;
}
// Only FREE sandboxes auto-expire; STANDARD+ are paid and stay until explicit cancellation
// service_versions references services.id — delete versions first
exec(conn,
"DELETE FROM service_versions sv " +
"USING services s, sandboxes sb " +
"WHERE sv.service_id = s.id " +
" AND s.sandbox_id = sb.id::text " +
" AND sb.expires_at < now() AND sb.tier = 'FREE'");
exec(conn,
"DELETE FROM services s " +
"USING sandboxes sb " +
"WHERE s.sandbox_id = sb.id::text " +
" AND sb.expires_at < now() AND sb.tier = 'FREE'");
exec(conn,
"DELETE FROM sandbox_feedback sf " +
"USING sandboxes sb " +
"WHERE sf.sandbox_id = sb.id::text " +
" AND sb.expires_at < now() AND sb.tier = 'FREE'");
exec(conn,
"DELETE FROM sandbox_usage_stats su " +
"USING sandboxes sb " +
"WHERE su.sandbox_id = sb.id::text " +
" AND sb.expires_at < now() AND sb.tier = 'FREE'");
exec(conn,
"DELETE FROM sandbox_agent_visits av " +
"USING sandboxes sb " +
"WHERE av.sandbox_id = sb.id::text " +
" AND sb.expires_at < now() AND sb.tier = 'FREE'");
exec(conn, "DELETE FROM sandboxes WHERE expires_at < now() AND tier = 'FREE'");
conn.commit();
Log.infof("Purged %d expired sandbox(es)", count);
} catch (SQLException e) {
conn.rollback();
Log.errorf(e, "Sandbox cleanup failed — transaction rolled back");
}
} catch (SQLException e) {
Log.errorf(e, "Sandbox cleanup failed — could not obtain connection");
}
}
private static long countExpired(Connection conn) throws SQLException {
try (var stmt = conn.prepareStatement(
"SELECT COUNT(*) FROM sandboxes WHERE expires_at < now() AND tier = 'FREE'");
var rs = stmt.executeQuery()) {
rs.next();
return rs.getLong(1);
}
}
private static void exec(Connection conn, String sql) throws SQLException {
try (var stmt = conn.prepareStatement(sql)) {
stmt.executeUpdate();
}
}
}
@@ -0,0 +1,10 @@
quarkus.http.port=8082
quarkus.smallrye-health.root-path=/q/health
quarkus.log.level=${LOG_LEVEL:INFO}
# DB — spider connects to the same database; does NOT run Liquibase (registry owns schema)
quarkus.datasource.db-kind=postgresql
quarkus.datasource.jdbc.url=${DB_URL:jdbc:postgresql://localhost:5432/apix}
quarkus.datasource.username=${DB_USER:apix}
quarkus.datasource.password=${DB_PASSWORD:apix}
quarkus.hibernate-orm.database.generation=none