fix: Build args format for version info + schema-tolerant routes

CI/CD:
- Fix build_args format in woodpecker CI (comma-separated, not YAML list)
- This fixes "unknown" SHA/version showing on remote deployments

Backend schema-tolerant fixes (graceful fallbacks when tables missing):
- users.ts: Check which columns exist before querying
- worker-registry.ts: Return empty result if table doesn't exist
- task-service.ts: Add tableExists() helper, handle missing tables/views
- proxies.ts: Return totalProxies in test-all response

Frontend fixes:
- Proxies: Use total from response for accurate progress display
- SEO PagesTab: Dim Generate button when no AI provider active

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Kelly
2025-12-10 09:53:21 -07:00
parent 9647f94f89
commit 249d3c1b7f
9 changed files with 161 additions and 42 deletions

View File

@@ -10,6 +10,17 @@
import { pool } from '../db/pool';
// Helper to check if a table exists
async function tableExists(tableName: string): Promise<boolean> {
const result = await pool.query(`
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_name = $1
) as exists
`, [tableName]);
return result.rows[0].exists;
}
export type TaskRole =
| 'store_discovery'
| 'entry_point_discovery'
@@ -270,6 +281,11 @@ class TaskService {
* List tasks with filters
*/
async listTasks(filter: TaskFilter = {}): Promise<WorkerTask[]> {
// Return empty list if table doesn't exist
if (!await tableExists('worker_tasks')) {
return [];
}
const conditions: string[] = [];
const params: (string | number | string[])[] = [];
let paramIndex = 1;
@@ -323,21 +339,41 @@ class TaskService {
* Get capacity metrics for all roles
*/
async getCapacityMetrics(): Promise<CapacityMetrics[]> {
const result = await pool.query(
`SELECT * FROM v_worker_capacity`
);
return result.rows as CapacityMetrics[];
// Return empty metrics if worker_tasks table doesn't exist
if (!await tableExists('worker_tasks')) {
return [];
}
try {
const result = await pool.query(
`SELECT * FROM v_worker_capacity`
);
return result.rows as CapacityMetrics[];
} catch {
// View may not exist
return [];
}
}
/**
* Get capacity metrics for a specific role
*/
async getRoleCapacity(role: TaskRole): Promise<CapacityMetrics | null> {
const result = await pool.query(
`SELECT * FROM v_worker_capacity WHERE role = $1`,
[role]
);
return (result.rows[0] as CapacityMetrics) || null;
// Return null if worker_tasks table doesn't exist
if (!await tableExists('worker_tasks')) {
return null;
}
try {
const result = await pool.query(
`SELECT * FROM v_worker_capacity WHERE role = $1`,
[role]
);
return (result.rows[0] as CapacityMetrics) || null;
} catch {
// View may not exist
return null;
}
}
/**
@@ -463,12 +499,6 @@ class TaskService {
* Get task counts by status for dashboard
*/
async getTaskCounts(): Promise<Record<TaskStatus, number>> {
const result = await pool.query(
`SELECT status, COUNT(*) as count
FROM worker_tasks
GROUP BY status`
);
const counts: Record<TaskStatus, number> = {
pending: 0,
claimed: 0,
@@ -478,6 +508,17 @@ class TaskService {
stale: 0,
};
// Return empty counts if table doesn't exist
if (!await tableExists('worker_tasks')) {
return counts;
}
const result = await pool.query(
`SELECT status, COUNT(*) as count
FROM worker_tasks
GROUP BY status`
);
for (const row of result.rows) {
const typedRow = row as { status: TaskStatus; count: string };
counts[typedRow.status] = parseInt(typedRow.count, 10);