Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 41 additions & 23 deletions app/api/queue/routes/next.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def get_next_queue(
cursor = conn.cursor()

# Build query with optional filters
query = "SELECT * FROM queue"
base_query = "SELECT * FROM queue"
filters = []
params = []
if collection:
Expand All @@ -27,34 +27,52 @@ def get_next_queue(
if group:
filters.append('"group" = %s')
params.append(group)
if filters:
query += " WHERE " + " AND ".join(filters)
query += " ORDER BY updated DESC LIMIT 1;"
where_clause = (" WHERE " + " AND ".join(filters)) if filters else ""

# 1. Get the next record
query = base_query + where_clause + " ORDER BY updated DESC LIMIT 1;"
cursor.execute(query, params)
row = cursor.fetchone()
columns = [desc[0] for desc in cursor.description] if cursor.description else []
record = dict(zip(columns, row)) if row and columns else None

# 2. Get count of records matching filters
count_query = "SELECT COUNT(*) FROM queue" + where_clause + ";"
cursor.execute(count_query, params)
filtered_row = cursor.fetchone()
filtered_count = filtered_row[0] if filtered_row else 0

# 3. Get total count
cursor.execute("SELECT COUNT(*) FROM queue;")
total_row = cursor.fetchone()
total_count = total_row[0] if total_row else 0

# 4. Get table schema
cursor.execute("SELECT column_name, data_type, is_nullable FROM information_schema.columns WHERE table_name = 'queue';")
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The schema lookup uses information_schema.columns filtered only by table_name = 'queue'. In Postgres this can return columns from multiple schemas if another queue table exists on the DB (and it also makes results depend on search_path). Consider filtering by table_schema (e.g., 'public') and ordering by ordinal_position to make the returned schema deterministic.

Suggested change
cursor.execute("SELECT column_name, data_type, is_nullable FROM information_schema.columns WHERE table_name = 'queue';")
cursor.execute(
"SELECT column_name, data_type, is_nullable "
"FROM information_schema.columns "
"WHERE table_schema = 'public' AND table_name = 'queue' "
"ORDER BY ordinal_position;"
)

Copilot uses AI. Check for mistakes.
schema = [
{"name": row[0], "type": row[1], "nullable": row[2]} for row in cursor.fetchall()
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is_nullable from information_schema.columns is returned as 'YES'/'NO', but the response field is named nullable, which reads like a boolean. Consider either converting this to a boolean (True/False) or renaming the field to something like is_nullable to match the underlying value and avoid surprising API consumers.

Suggested change
{"name": row[0], "type": row[1], "nullable": row[2]} for row in cursor.fetchall()
{"name": row[0], "type": row[1], "nullable": row[2] == "YES"} for row in cursor.fetchall()

Copilot uses AI. Check for mistakes.
]
Comment on lines +39 to +54
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This endpoint now runs 4 separate DB queries on every request (next record, filtered count, total count, schema). The two COUNT(*) queries can be combined into a single query (e.g., using subqueries or FILTER), and the schema query is a good candidate for caching since table schemas rarely change at runtime. Without this, /queue/next could become noticeably slower on large tables/high traffic.

Copilot uses AI. Check for mistakes.

conn.close()

if row and columns:
record = dict(zip(columns, row))
# Build a dynamic title with filters
filters = []
if collection:
filters.append(f"collection='{collection}'")
if group:
filters.append(f"group='{group}'")
filter_str = f" (filtered by {', '.join(filters)})" if filters else ""
title = f"Next queue record found{filter_str}"
return {
"meta": make_meta("success", title),
"data": record
}
else:
return {
"meta": make_meta("info", "No queue record to show"),
"data": None,
"message": "Nothing to show for the given filters."
# Build a dynamic title with filters
filter_labels = []
if collection:
filter_labels.append(f"collection='{collection}'")
if group:
filter_labels.append(f"group='{group}'")
filter_str = f" (filtered by {', '.join(filter_labels)})" if filter_labels else ""
title = f"Next queue record found{filter_str}" if record else "No queue record to show"

return {
"meta": make_meta("success" if record else "info", title),
"data": {
"record": record,
"filtered_count": filtered_count,
"total_count": total_count,
"schema": schema,
"message": None if record else "Nothing to show for the given filters."
Comment on lines +67 to +74
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/queue/next appears to be unauthenticated, and the response now includes full table schema details. If this API is exposed beyond trusted/internal users, this increases information disclosure risk. Consider protecting this route (e.g., API key dependency like other DB/LLM routes) or gating/removing schema from the default response.

Copilot uses AI. Check for mistakes.
}
Comment on lines +67 to 75
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are existing FastAPI endpoint tests for /queue (tests/test_queue.py), but there’s no coverage for /queue/next. Since this change alters the response contract (nested data.record plus filtered_count, total_count, and schema), please add tests that validate the new shape for both the “record found” and “no record” cases (including filter parameters).

Copilot uses AI. Check for mistakes.
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
265 changes: 0 additions & 265 deletions app/static/python-logo.svg

This file was deleted.

Loading