Skip to content

Commit fc6abf8

Browse files
Merge pull request #106 from goldlabelapps/staging
Bump version to 3.0.8 and add Flickr API integration
2 parents 557e414 + ebd170e commit fc6abf8

10 files changed

Lines changed: 252 additions & 5 deletions

File tree

.env.sample

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,6 @@ DB_PORT=5432
88
DB_NAME=
99
DB_USER=
1010
DB_PASSWORD=
11+
FLICKR_USER=@N00
12+
FLICKR_KEY=
13+
FLICKR_SECRET=

app/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
"""Python° - FastAPI, Postgres, tsvector"""
22

33
# Current Version
4-
__version__ = "3.0.7"
4+
__version__ = "3.0.8"

app/api/flickr/README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
2+
# Flickr API Integration
3+
4+
This module provides API routes for accessing and syncing Flickr data, mirroring the GitHub integration. It expects the following environment variables in your `.env` file:
5+
6+
- `FLICKR_USER`
7+
- `FLICKR_KEY`
8+
- `FLICKR_SECRET`
9+
10+
## Endpoints
11+
12+
- **GET /flickr**: Returns counts and recent records from all Flickr tables.
13+
- **POST /flickr/createtable**: Drops all Flickr tables if they exist, then recreates them with the correct schema and constraints.
14+
- **POST /flickr/emptytables**: Deletes all rows from all Flickr tables (does not drop tables).
15+
- **POST /flickr/sync**: Fetches public photos for the configured Flickr user and stores them in the database.
16+
17+
## Table Design
18+
19+
1. **flickr_accounts**
20+
- One row per Flickr account/user profile.
21+
- Stores account identity fields and full raw payload.
22+
2. **flickr_photos**
23+
- One row per photo.
24+
- `flickr_id` is unique.
25+
- Stores photo metadata plus raw JSON payload.
26+
3. **flickr_albums**
27+
- One row per album.
28+
- Stores album metadata plus raw JSON payload.
29+
4. **flickr_resources**
30+
- Generic catch-all for any future Flickr resource type.
31+
- Supports additional Flickr objects through jsonb payload storage.
32+
33+
## Notes
34+
35+
- The `/flickr/createtable` endpoint will **drop all Flickr tables** before recreating them. Use with caution—this will erase all Flickr data.
36+
- The `/flickr/sync` endpoint currently fetches public photos for the configured user and inserts them into `flickr_photos`.
37+
- The structure and endpoints are designed for consistency and flexibility, matching the GitHub integration.

app/api/flickr/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"""Flickr Routes"""
2+
3+
from fastapi import APIRouter
4+
5+
from .flickr import router as _flickr_router
6+
from .sql.create_tables import router as _create_tables_router
7+
from .sql.empty_tables import router as _empty_tables_router
8+
from .sql.sync import router as _sync_router
9+
10+
flickr_router = APIRouter()
11+
flickr_router.include_router(_flickr_router)
12+
flickr_router.include_router(_create_tables_router)
13+
flickr_router.include_router(_empty_tables_router)
14+
flickr_router.include_router(_sync_router)

app/api/flickr/flickr.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from fastapi import APIRouter, Depends
2+
from app.utils.make_meta import make_meta
3+
from app.utils.db import get_db_connection_direct
4+
from app.utils.api_key_auth import get_api_key
5+
6+
router = APIRouter()
7+
8+
_TABLES = [
9+
"flickr_accounts",
10+
"flickr_photos",
11+
"flickr_albums",
12+
"flickr_resources",
13+
]
14+
15+
def _fetch_table(cur, table: str) -> dict:
16+
cur.execute(f"SELECT COUNT(*) FROM {table};")
17+
row = cur.fetchone()
18+
count = row[0] if row and row[0] is not None else 0
19+
cur.execute(f"SELECT * FROM {table} ORDER BY id DESC LIMIT 100;")
20+
if cur.description:
21+
columns = [desc[0] for desc in cur.description]
22+
rows = [dict(zip(columns, r)) for r in cur.fetchall()]
23+
else:
24+
rows = []
25+
return {"count": count, "rows": rows}
26+
27+
28+
@router.get("/flickr")
29+
def get_flickr(api_key: str = Depends(get_api_key)) -> dict:
30+
"""GET /flickr: Return counts and records from all Flickr tables."""
31+
conn = None
32+
cur = None
33+
try:
34+
conn = get_db_connection_direct()
35+
cur = conn.cursor()
36+
data = {table: _fetch_table(cur, table) for table in _TABLES}
37+
return {"meta": make_meta("success", "Flickr data"), "data": data}
38+
except Exception as e:
39+
return {"meta": make_meta("error", f"DB error: {str(e)}"), "data": {}}
40+
finally:
41+
if cur is not None:
42+
cur.close()
43+
if conn is not None:
44+
conn.close()
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
from fastapi import APIRouter, Depends
2+
from app.utils.db import get_db_connection_direct
3+
from app.utils.make_meta import make_meta
4+
from app.utils.api_key_auth import get_api_key
5+
6+
router = APIRouter()
7+
8+
@router.post("/flickr/createtable")
9+
def create_flickr_tables(api_key: str = Depends(get_api_key)) -> dict:
10+
"""POST /flickr/createtable: Create Flickr tables in Postgres."""
11+
sql_statements = [
12+
# Drop tables if they exist (in reverse dependency order)
13+
'DROP TABLE IF EXISTS flickr_resources;',
14+
'DROP TABLE IF EXISTS flickr_albums;',
15+
'DROP TABLE IF EXISTS flickr_photos;',
16+
'DROP TABLE IF EXISTS flickr_accounts;',
17+
'''CREATE TABLE IF NOT EXISTS flickr_accounts (
18+
id SERIAL PRIMARY KEY,
19+
flickr_id TEXT,
20+
username TEXT,
21+
payload JSONB,
22+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
23+
);''',
24+
'''CREATE TABLE IF NOT EXISTS flickr_photos (
25+
id SERIAL PRIMARY KEY,
26+
flickr_id TEXT UNIQUE,
27+
title TEXT,
28+
payload JSONB,
29+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
30+
);''',
31+
'''CREATE TABLE IF NOT EXISTS flickr_albums (
32+
id SERIAL PRIMARY KEY,
33+
flickr_id TEXT,
34+
title TEXT,
35+
payload JSONB,
36+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
37+
);''',
38+
'''CREATE TABLE IF NOT EXISTS flickr_resources (
39+
id SERIAL PRIMARY KEY,
40+
resource_type TEXT,
41+
flickr_id TEXT,
42+
payload JSONB,
43+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
44+
);'''
45+
]
46+
conn = None
47+
cur = None
48+
try:
49+
conn = get_db_connection_direct()
50+
cur = conn.cursor()
51+
for stmt in sql_statements:
52+
cur.execute(stmt)
53+
conn.commit()
54+
return {"meta": make_meta("success", "Flickr tables created"), "data": {}}
55+
except Exception as e:
56+
return {"meta": make_meta("error", f"DB error: {str(e)}"), "data": {}}
57+
finally:
58+
if cur is not None:
59+
cur.close()
60+
if conn is not None:
61+
conn.close()

app/api/flickr/sql/empty_tables.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from fastapi import APIRouter, Depends
2+
from app.utils.db import get_db_connection_direct
3+
from app.utils.make_meta import make_meta
4+
from app.utils.api_key_auth import get_api_key
5+
6+
router = APIRouter()
7+
8+
@router.post("/flickr/emptytables")
9+
def empty_flickr_tables(api_key: str = Depends(get_api_key)) -> dict:
10+
"""POST /flickr/emptytables: Delete all rows from all Flickr tables."""
11+
tables = [
12+
"flickr_accounts",
13+
"flickr_photos",
14+
"flickr_albums",
15+
"flickr_resources"
16+
]
17+
conn = None
18+
cur = None
19+
try:
20+
conn = get_db_connection_direct()
21+
cur = conn.cursor()
22+
for table in tables:
23+
cur.execute(f"DELETE FROM {table};")
24+
conn.commit()
25+
return {"meta": make_meta("success", "Flickr tables emptied"), "data": {}}
26+
except Exception as e:
27+
return {"meta": make_meta("error", f"DB error: {str(e)}"), "data": {}}
28+
finally:
29+
if cur is not None:
30+
cur.close()
31+
if conn is not None:
32+
conn.close()

app/api/flickr/sql/sync.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from fastapi import APIRouter, Depends
2+
from app.utils.db import get_db_connection_direct
3+
from app.utils.make_meta import make_meta
4+
from app.utils.api_key_auth import get_api_key
5+
import os
6+
import requests
7+
import json
8+
from dotenv import load_dotenv
9+
10+
router = APIRouter()
11+
12+
@router.post("/flickr/sync")
13+
def sync_flickr(api_key: str = Depends(get_api_key)) -> dict:
14+
"""POST /flickr/sync: Fetches data from Flickr API and stores in DB."""
15+
load_dotenv()
16+
flickr_user = os.getenv("FLICKR_USER")
17+
flickr_key = os.getenv("FLICKR_KEY")
18+
flickr_secret = os.getenv("FLICKR_SECRET")
19+
if not flickr_user or not flickr_key or not flickr_secret:
20+
return {"meta": make_meta("error", "Missing Flickr API credentials"), "data": {}}
21+
22+
# Example: Fetch public photos for the user
23+
url = "https://api.flickr.com/services/rest/"
24+
params = {
25+
"method": "flickr.people.getPublicPhotos",
26+
"api_key": flickr_key,
27+
"user_id": flickr_user,
28+
"format": "json",
29+
"nojsoncallback": 1,
30+
"per_page": 10
31+
}
32+
try:
33+
resp = requests.get(url, params=params)
34+
resp.raise_for_status()
35+
data = resp.json()
36+
photos = data.get("photos", {}).get("photo", [])
37+
conn = get_db_connection_direct()
38+
cur = conn.cursor()
39+
for photo in photos:
40+
cur.execute(
41+
"""
42+
INSERT INTO flickr_photos (flickr_id, title, payload)
43+
VALUES (%s, %s, %s)
44+
ON CONFLICT (flickr_id) DO NOTHING;
45+
""",
46+
(photo.get("id"), photo.get("title"), json.dumps(photo))
47+
)
48+
conn.commit()
49+
cur.close()
50+
conn.close()
51+
return {"meta": make_meta("success", f"Synced {len(photos)} photos from Flickr"), "data": {"count": len(photos)}}
52+
except Exception as e:
53+
return {"meta": make_meta("error", f"Sync error: {str(e)}"), "data": {}}

app/api/prompt/prompt.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@ def get_prompt_table_metadata(api_key: str = Depends(get_api_key)) -> dict:
4141
return {
4242
"meta": meta,
4343
"data": {
44-
"first_record": {
44+
"top_record": {
4545
"id": top_row[0],
46-
# "prompt": top_row[1],
47-
# "completion": top_row[2],
46+
"prompt": top_row[1],
47+
"completion": top_row[2],
4848
"time": top_row[3].isoformat() if top_row and top_row[3] else None,
4949
"model": top_row[4],
5050
} if top_row else None,

app/api/routes.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
from app.api.prospects.prospects import router as prospects_router
1515
from app.api.orders.orders import router as orders_router
1616
from app.api.queue import router as queue_router
17+
1718
from app.api.github import github_router
19+
from app.api.flickr import flickr_router
1820

1921
router.include_router(root_router)
2022
router.include_router(resend_router)
@@ -25,4 +27,5 @@
2527
router.include_router(prospects_router)
2628
router.include_router(orders_router)
2729
router.include_router(queue_router)
28-
router.include_router(github_router)
30+
router.include_router(github_router)
31+
router.include_router(flickr_router)

0 commit comments

Comments
 (0)