Skip to content

Commit aec9cee

Browse files
Merge pull request #90 from goldlabelapps/staging
This pull request introduces several new features and improvements to the queue API, focusing on enhanced data management and robustness.
2 parents fb14863 + 6d6d7cf commit aec9cee

7 files changed

Lines changed: 153 additions & 32 deletions

File tree

app/api/queue/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,19 @@
77
from .routes.get import router as get_router
88

99
from .routes.create import router as create_router
10+
from .routes.delete import router as delete_router
1011

1112
from .routes.import_linkedin import router as import_linkedin_router
13+
1214
from .routes.alter import router as alter_router
15+
from .routes.rename_column import router as rename_router
1316

1417
router = APIRouter()
1518
router.include_router(drop_router)
1619
router.include_router(empty_router)
1720
router.include_router(get_router)
1821
router.include_router(create_router)
22+
router.include_router(delete_router)
1923
router.include_router(import_linkedin_router)
20-
router.include_router(alter_router)
24+
router.include_router(alter_router)
25+
router.include_router(rename_router)

app/api/queue/routes/delete.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import os
2+
from fastapi import APIRouter, HTTPException
3+
from app.utils.make_meta import make_meta
4+
from app.utils.db import get_db_connection_direct
5+
6+
router = APIRouter()
7+
8+
@router.delete("/queue/delete")
9+
def delete_queue_record(id: int) -> dict:
10+
"""DELETE /queue/delete: Delete a record from the queue table by id."""
11+
try:
12+
conn = get_db_connection_direct()
13+
cursor = conn.cursor()
14+
cursor.execute("DELETE FROM queue WHERE id = %s RETURNING id;", (id,))
15+
deleted = cursor.fetchone()
16+
conn.commit()
17+
conn.close()
18+
if deleted:
19+
return {"meta": make_meta("success", f"Record with id {id} deleted.")}
20+
else:
21+
return {"meta": make_meta("error", f"No record found with id {id}.")}
22+
except Exception as e:
23+
msg = str(e)
24+
return {"meta": make_meta("error", msg)}

app/api/queue/routes/get.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,28 @@ def read_queue() -> dict:
2727
for row in cursor.fetchall()
2828
]
2929

30-
# 3. Get most recently updated record
31-
cursor.execute("SELECT * FROM queue ORDER BY updated DESC LIMIT 1;")
30+
# 3. Get a random record
31+
cursor.execute("SELECT * FROM queue ORDER BY RANDOM() LIMIT 1;")
3232
columns = [desc[0] for desc in cursor.description] if cursor.description else []
33-
row = cursor.fetchone()
34-
most_recent = dict(zip(columns, row)) if row and columns else None
33+
rows = cursor.fetchall()
34+
random_record = [dict(zip(columns, row)) for row in rows] if rows and columns else []
35+
36+
# 4. Get unique values from collection and group columns
37+
cursor.execute("SELECT DISTINCT collection FROM queue WHERE collection IS NOT NULL;")
38+
collections = [row[0] for row in cursor.fetchall()]
39+
cursor.execute('SELECT DISTINCT "group" FROM queue WHERE "group" IS NOT NULL;')
40+
groups = [row[0] for row in cursor.fetchall()]
3541

3642
conn.close()
3743

3844
return {
3945
"meta": make_meta("success", "Queue table info"),
4046
"data": {
41-
"queued": record_count,
42-
"most_recent": most_recent,
43-
# "schema": schema
47+
"in_queue": record_count,
48+
"collections": collections,
49+
"groups": groups,
50+
"example": random_record,
51+
# "queue_schema": schema
4452
}
4553
}
4654
except Exception as e:

app/api/queue/routes/import_linkedin.py

Lines changed: 58 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,38 +9,73 @@
99

1010
@router.post("/queue/import/linkedin")
1111
def import_linkedin_csv() -> dict:
12-
"""POST /queue/import/linkedin: Import data from linkedin_sample.csv into the queue table."""
13-
csv_path = os.path.join(os.path.dirname(__file__), "../csv/linkedin/linkedin_sample.csv")
12+
"""POST /queue/import/linkedin: Import data from linkedin.csv into the queue table, robust for large files."""
13+
csv_path = os.path.join(os.path.dirname(__file__), "../csv/linkedin/linkedin.csv")
1414
if not os.path.exists(csv_path):
15-
raise HTTPException(status_code=404, detail="linkedin_sample.csv not found")
15+
raise HTTPException(status_code=404, detail="linkedin.csv not found")
1616
try:
1717
conn = get_db_connection_direct()
1818
cursor = conn.cursor()
1919
with open(csv_path, newline='', encoding='utf-8') as csvfile:
20-
reader = csv.DictReader(row for row in csvfile if not row.startswith('Notes:'))
20+
# Find the header line dynamically
21+
header_line = None
22+
pre_data_lines = []
23+
while True:
24+
pos = csvfile.tell()
25+
line = csvfile.readline()
26+
if not line:
27+
break
28+
if line.strip().startswith("First Name,Last Name,URL,Email Address,Company,Position,Connected On"):
29+
header_line = line.strip()
30+
break
31+
pre_data_lines.append(line)
32+
if not header_line:
33+
raise HTTPException(status_code=400, detail="CSV header not found.")
34+
# Use DictReader with the found header
35+
fieldnames = header_line.split(",")
36+
reader = csv.DictReader(csvfile, fieldnames=fieldnames)
2137
now = int(time.time())
38+
batch = []
39+
batch_size = 500
40+
first_row = None
41+
imported_count = 0
2242
for row in reader:
23-
cursor.execute(
24-
"""
25-
INSERT INTO queue (first_name, last_name, url, email_address, company, position, connected_on, created, updated, hidden, collection)
26-
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
27-
""",
28-
[
29-
row.get('First Name'),
30-
row.get('Last Name'),
31-
row.get('URL'),
32-
row.get('Email Address'),
33-
row.get('Company'),
34-
row.get('Position'),
35-
row.get('Connected On'),
36-
now,
37-
now,
38-
False,
39-
'prospects'
40-
]
43+
# Skip any rows that are just blank or not data
44+
if not any(row.values()):
45+
continue
46+
if first_row is None:
47+
first_row = row.copy()
48+
print("DEBUG: First parsed row from CSV:", first_row)
49+
batch.append([
50+
row.get('First Name'), # first_name
51+
row.get('Last Name'), # last_name
52+
row.get('URL'), # linkedin
53+
row.get('Email Address'), # email
54+
row.get('Company'), # company
55+
row.get('Position'), # position
56+
row.get('Connected On'), # connected_on
57+
now, # created
58+
now, # updated
59+
False, # hidden
60+
'prospects', # collection
61+
'linkedin' # group
62+
])
63+
imported_count += 1
64+
if len(batch) >= batch_size:
65+
cursor.executemany(
66+
'''INSERT INTO queue (first_name, last_name, linkedin, email, company, position, connected_on, created, updated, hidden, collection, "group")
67+
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)''',
68+
batch
69+
)
70+
batch = []
71+
if batch:
72+
cursor.executemany(
73+
'''INSERT INTO queue (first_name, last_name, linkedin, email, company, position, connected_on, created, updated, hidden, collection, "group")
74+
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)''',
75+
batch
4176
)
4277
conn.commit()
4378
conn.close()
44-
return {"meta": make_meta("success", "LinkedIn CSV imported")}
79+
return {"meta": make_meta("success", f"LinkedIn CSV imported (batched): {imported_count} records imported"), "imported": imported_count}
4580
except Exception as e:
4681
raise HTTPException(status_code=500, detail=str(e))
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from fastapi import APIRouter, HTTPException, Body
2+
from app.utils.make_meta import make_meta
3+
from app.utils.db import get_db_connection_direct
4+
5+
router = APIRouter()
6+
7+
@router.post("/queue/alter/rename_column")
8+
def rename_column(
9+
old_name: str = Body(..., embed=True),
10+
new_name: str = Body(..., embed=True),
11+
column_type: str = Body(..., embed=True)
12+
) -> dict:
13+
"""POST /queue/alter/rename_column: Rename a column in the queue table."""
14+
try:
15+
conn = get_db_connection_direct()
16+
cursor = conn.cursor()
17+
sql = f'ALTER TABLE queue RENAME COLUMN "{old_name}" TO "{new_name}";'
18+
cursor.execute(sql)
19+
conn.commit()
20+
conn.close()
21+
return {"meta": make_meta("success", f"Column '{old_name}' renamed to '{new_name}'")}
22+
except Exception as e:
23+
msg = str(e)
24+
if 'does not exist' in msg:
25+
raise HTTPException(status_code=400, detail=f"Column '{old_name}' does not exist in queue table.")
26+
if 'already exists' in msg:
27+
raise HTTPException(status_code=400, detail=f"Column '{new_name}' already exists in queue table.")
28+
raise HTTPException(status_code=500, detail=msg)

app/main.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
from app import __version__
2-
from fastapi import FastAPI
2+
from fastapi import FastAPI, Request
3+
from fastapi.responses import JSONResponse
4+
from fastapi.exceptions import RequestValidationError
5+
from app.utils.make_meta import make_meta
36
from fastapi.middleware.cors import CORSMiddleware
47
from fastapi.staticfiles import StaticFiles
58
from fastapi.responses import FileResponse
@@ -26,6 +29,13 @@
2629
allow_headers=["*"]
2730
)
2831

32+
33+
# Global validation error handler for make_meta pattern
34+
@app.exception_handler(RequestValidationError)
35+
async def validation_exception_handler(request: Request, exc: RequestValidationError):
36+
msg = exc.errors()[0]['msg'] if exc.errors() else str(exc)
37+
return JSONResponse(status_code=422, content={"meta": make_meta("error", msg)})
38+
2939
app.include_router(router)
3040

3141
# Mount static directory

queue_output.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
id,first_name,last_name,url,email_address,company,position,connected_on,created,updated,hidden,collection,group
2+
22,,,,,,,,1776513092,1776513092,False,prospects,linkedin
3+
23,,,,,,,,1776513092,1776513092,False,prospects,linkedin
4+
24,,,,,,,,1776513092,1776513092,False,prospects,linkedin
5+
25,,,,,,,,1776513092,1776513092,False,prospects,linkedin
6+
26,,,,,,,,1776513092,1776513092,False,prospects,linkedin
7+
27,,,,,,,,1776513092,1776513092,False,prospects,linkedin
8+
28,,,,,,,,1776513092,1776513092,False,prospects,linkedin
9+
29,,,,,,,,1776513092,1776513092,False,prospects,linkedin
10+
30,,,,,,,,1776513092,1776513092,False,prospects,linkedin
11+
21,,,,,,,,1776513092,1776513092,False,prospects,linkedin

0 commit comments

Comments
 (0)