Skip to content

Commit 5a8fa4b

Browse files
committed
Add prospects seed and empty endpoints
Introduce endpoints to manage the prospects table: a new DELETE /prospects/empty to remove all records (handles missing table) and a revamped GET /prospects/seed that recreates and seeds a prospects table from embedded CSV data. Changes include column name normalization, dynamic table creation and insertion, and switching DB import path to app.utils.db. The prospects listing now exposes action links (seed & empty) using BASE_URL when the table is missing or on error. app/api/routes.py updated to register the new routers and minor renames/cleanup from previous product-focused seeding logic.
1 parent 5fc0600 commit 5a8fa4b

4 files changed

Lines changed: 88 additions & 74 deletions

File tree

app/api/prospects/empty.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from fastapi import APIRouter, status
2+
from app.utils.db import get_db_connection
3+
4+
router = APIRouter()
5+
6+
@router.delete("/prospects/empty", status_code=status.HTTP_200_OK)
7+
def empty_prospects() -> dict:
8+
"""Delete all records from the prospects table, leaving the structure intact. Handles missing table gracefully."""
9+
import psycopg2
10+
conn_gen = get_db_connection()
11+
conn = next(conn_gen)
12+
cur = conn.cursor()
13+
try:
14+
cur.execute('DELETE FROM prospects;')
15+
conn.commit()
16+
result = {"detail": "All records deleted from prospects table."}
17+
except psycopg2.errors.UndefinedTable:
18+
conn.rollback()
19+
result = {"detail": "Table 'prospects' does not exist. No records deleted."}
20+
except Exception as e:
21+
conn.rollback()
22+
result = {"detail": f"Error: {str(e)}"}
23+
finally:
24+
cur.close()
25+
conn.close()
26+
return result

app/api/prospects/prospects.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from app import __version__
2+
import os
23
from app.utils.make_meta import make_meta
34
from fastapi import APIRouter
45
from app.utils.db import get_db_connection
@@ -8,9 +9,20 @@
89
@router.get("/prospects")
910
def root() -> dict:
1011
"""Return all prospects table records"""
12+
base_url = os.getenv("BASE_URL", "http://localhost:8000")
1113
conn_gen = get_db_connection()
1214
conn = next(conn_gen)
1315
cur = conn.cursor()
16+
actions = [
17+
{
18+
"name": "Seed prospects table",
19+
"url": f"{base_url}/prospects/seed"
20+
},
21+
{
22+
"name": "Empty prospects table",
23+
"url": f"{base_url}/prospects/empty"
24+
},
25+
]
1426
try:
1527
cur.execute('SELECT * FROM prospects;')
1628
if cur.description is None:
@@ -23,11 +35,11 @@ def root() -> dict:
2335
except Exception as e:
2436
import psycopg2
2537
if isinstance(e, psycopg2.errors.UndefinedTable):
26-
meta = make_meta("error", "Table 'prospects' does not exist.")
27-
result = {"meta": meta, "data": []}
38+
meta = make_meta("error", "prospects table does not exist.")
39+
result = {"meta": meta, "data": actions}
2840
else:
2941
meta = make_meta("error", str(e))
30-
result = {"meta": meta, "data": []}
42+
result = {"meta": meta, "data": actions}
3143
finally:
3244
cur.close()
3345
conn.close()

app/api/prospects/seed.py

Lines changed: 40 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -2,95 +2,65 @@
22
import os, time
33
import csv
44
from fastapi import APIRouter, status
5-
from app.api.db import get_db_connection
5+
from app.utils.db import get_db_connection
66

77
router = APIRouter()
88

99
CSV_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), 'data/seed.csv'))
1010

11-
@router.get("/products/seed", status_code=status.HTTP_200_OK)
12-
def seed_products() -> dict:
13-
"""Delete and recreate the product table, then seed with CSV data."""
11+
def normalize_column(col):
12+
import re
13+
col = col.strip().lower().replace(' ', '_')
14+
col = re.sub(r'[^a-z0-9_]', '', col)
15+
if col and col[0].isdigit():
16+
col = '_' + col
17+
return col
18+
19+
@router.get("/prospects/seed", status_code=status.HTTP_200_OK)
20+
def seed_prospects() -> dict:
21+
"""Delete and recreate the prospects table, then seed with CSV data."""
1422
conn_gen = get_db_connection()
1523
conn = next(conn_gen)
1624
cur = conn.cursor()
17-
# Drop and recreate table with all CSV columns
18-
cur.execute('''
19-
DROP TABLE IF EXISTS product;
20-
CREATE TABLE product (
21-
id SERIAL PRIMARY KEY,
22-
Params TEXT,
23-
item INTEGER,
24-
title TEXT,
25-
UOS TEXT,
26-
Pack_Description TEXT,
27-
Hierarchy1 TEXT,
28-
Hierarchy2 TEXT,
29-
Hierarchy3 TEXT,
30-
UOP TEXT,
31-
sSell1 NUMERIC(10,2),
32-
sSell2 NUMERIC(10,2),
33-
sSell3 NUMERIC(10,2),
34-
sSell4 NUMERIC(10,2),
35-
sSell5 NUMERIC(10,2),
36-
pack1 INTEGER,
37-
pack2 INTEGER,
38-
pack3 INTEGER,
39-
pack4 INTEGER,
40-
pack5 INTEGER,
41-
EAN TEXT
42-
);
43-
''')
44-
# Read and insert CSV data
45-
with open(CSV_PATH, newline='') as csvfile:
46-
reader = csv.DictReader(csvfile)
47-
rows = [row for row in reader]
48-
for row in rows:
49-
# Map 'desc' from CSV to 'title' for DB
50-
row['title'] = row.pop('desc')
51-
cur.execute(
52-
"""
53-
INSERT INTO product (
54-
Params, item, title, UOS, Pack_Description, Hierarchy1, Hierarchy2, Hierarchy3, UOP,
55-
sSell1, sSell2, sSell3, sSell4, sSell5, pack1, pack2, pack3, pack4, pack5, EAN
56-
) VALUES (
57-
%(Params)s, %(item)s, %(title)s, %(UOS)s, %(Pack_Description)s, %(Hierarchy1)s, %(Hierarchy2)s, %(Hierarchy3)s, %(UOP)s,
58-
%(sSell1)s, %(sSell2)s, %(sSell3)s, %(sSell4)s, %(sSell5)s, %(pack1)s, %(pack2)s, %(pack3)s, %(pack4)s, %(pack5)s, %(EAN)s
59-
)
60-
""",
61-
row
62-
)
63-
# Add tsvector column for full-text search
64-
cur.execute('''
65-
ALTER TABLE product
66-
ADD COLUMN IF NOT EXISTS search_vector tsvector;
67-
''')
68-
# Populate tsvector column (example: combine title, Pack_Description, Hierarchy1-3)
69-
cur.execute('''
70-
UPDATE product SET search_vector =
71-
to_tsvector('english', coalesce(title,'') || ' ' || coalesce(Pack_Description,'') || ' ' || coalesce(Hierarchy1,'') || ' ' || coalesce(Hierarchy2,'') || ' ' || coalesce(Hierarchy3,''));
72-
''')
73-
# Create GIN index for fast search
74-
cur.execute('''
75-
CREATE INDEX IF NOT EXISTS idx_product_search_vector ON product USING GIN(search_vector);
76-
''')
25+
26+
# Provided CSV data
27+
csv_data = '''First Name,Last Name,Title,Company Name,Email,Email Status,Primary Email Source,Primary Email Verification Source,Email Confidence,Primary Email Catch-all Status,Primary Email Last Verified At,Seniority,Sub Departments,Work Direct Phone,Home Phone,Mobile Phone,Corporate Phone,Other Phone,Do Not Call,Lists,Person Linkedin Url,Country,Subsidiary of,Subsidiary of (Organization ID),Secondary Email,Secondary Email Source,Secondary Email Status,Secondary Email Verification Source,Tertiary Email,Tertiary Email Source,Tertiary Email Status,Tertiary Email Verification Source,Primary Intent Topic,Primary Intent Score,Secondary Intent Topic,Secondary Intent Score,Qualify Contact,Cleaned\nLaurence,Brophy,Finance Director,Abraham Moon & Sons,laurence.brophy@moons.co.uk,Verified,Apollo,Apollo,,,2026-03-18T22:36:33+00:00,Director,Finance,,,,'+44 194 387 3181,,FALSE,Magento 2,http://www.linkedin.com/in/laurence-brophy-248752145,United Kingdom,,,,,,,,,,,,,,,,\nJulie,Lavington,CEO and Founder,Sosandar,julie.lavington@sosandar.com,Verified,Apollo,Apollo,,,2026-03-11T10:33:18+00:00,Founder,"Executive, Founder",,,,'+44 333 305 2866,,FALSE,Magento 2,http://www.linkedin.com/in/julie-lavington-1181744,United Kingdom,,,,,,,,,,,,,,,,\nAndrew,Shand,Chief Executive Officer,Ruroc,andrew.shand@ruroc.com,Verified,Apollo,Apollo,,,2026-03-16T21:14:58+00:00,C suite,Executive,,,,,,FALSE,Magento 2,http://www.linkedin.com/in/andrew-shand-74068513,United Kingdom,,,,,,,,,,,,,,,,\nNick,Anstee,Head of Server Support / Programmer,Stock in the Channel,nick.anstee@stockinthechannel.com,Verified,Apollo,Apollo,,Not Catch-all,2026-03-24T09:59:02+00:00,Head,"Software Development, Support / Technical Services, Application Development, Servers",,,,'+44 845 225 2125,,FALSE,Magento 2,http://www.linkedin.com/in/nick-anstee-4b102277,United Kingdom,,,,,,,,,,,,,,,,'''
28+
29+
import io
30+
reader = csv.reader(io.StringIO(csv_data))
31+
columns_raw = next(reader)
32+
columns = [normalize_column(col) for col in columns_raw]
33+
34+
# Drop and recreate table
35+
cur.execute('DROP TABLE IF EXISTS prospects;')
36+
create_cols = ',\n '.join([f'{col} TEXT' for col in columns])
37+
cur.execute(f'''CREATE TABLE prospects (\n id SERIAL PRIMARY KEY,\n {create_cols}\n);''')
38+
39+
# Insert rows
40+
for row in reader:
41+
placeholders = ', '.join(['%s'] * len(columns))
42+
cur.execute(
43+
f"INSERT INTO prospects ({', '.join(columns)}) VALUES ({placeholders})",
44+
row
45+
)
46+
7747
conn.commit()
78-
cur.execute('SELECT * FROM product;')
48+
cur.execute('SELECT * FROM prospects;')
7949
if cur.description is None:
80-
products = []
50+
prospects = []
8151
else:
82-
columns = [desc[0] for desc in cur.description]
83-
products = [dict(zip(columns, row)) for row in cur.fetchall()]
52+
colnames = [desc[0] for desc in cur.description]
53+
prospects = [dict(zip(colnames, row)) for row in cur.fetchall()]
8454
cur.close()
8555
conn.close()
8656

8757
base_url = os.getenv("BASE_URL", "http://localhost:8000")
8858
epoch = int(time.time() * 1000)
8959
meta = {
9060
"severity": "success",
91-
"title": "Product table seeded",
61+
"title": "Prospects table seeded",
9262
"version": __version__,
9363
"base_url": base_url,
9464
"time": epoch,
9565
}
96-
return {"meta": meta, "data": products}
66+
return {"meta": meta, "data": prospects}

app/api/routes.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,14 @@
1212

1313
from app.api.root import router as root_router
1414
from app.api.health import router as health_router
15+
1516
from app.api.prospects.prospects import router as prospects_router
1617

18+
from app.api.prospects.seed import router as prospects_seed_router
19+
from app.api.prospects.empty import router as prospects_empty_router
20+
1721
router.include_router(root_router)
1822
router.include_router(health_router)
19-
router.include_router(prospects_router)
23+
router.include_router(prospects_router)
24+
router.include_router(prospects_seed_router)
25+
router.include_router(prospects_empty_router)

0 commit comments

Comments
 (0)