From ee6b4be85346376c78c0710b899d1becb2d6ebbd Mon Sep 17 00:00:00 2001 From: Wei Zang Date: Wed, 29 Apr 2026 19:26:09 +0100 Subject: [PATCH 1/3] Bump version and rename first_record to top_record Update __version__ to 3.0.8. Modify get_prompt_table_metadata response structure: rename the returned key from "first_record" to "top_record" and enable inclusion of the "prompt" and "completion" fields (previously commented out) so the API returns full top record details. --- app/__init__.py | 2 +- app/api/prompt/prompt.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index ca4e9d7..4da493e 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,4 +1,4 @@ """Python° - FastAPI, Postgres, tsvector""" # Current Version -__version__ = "3.0.7" +__version__ = "3.0.8" diff --git a/app/api/prompt/prompt.py b/app/api/prompt/prompt.py index 251c85a..6601a6b 100644 --- a/app/api/prompt/prompt.py +++ b/app/api/prompt/prompt.py @@ -41,10 +41,10 @@ def get_prompt_table_metadata(api_key: str = Depends(get_api_key)) -> dict: return { "meta": meta, "data": { - "first_record": { + "top_record": { "id": top_row[0], - # "prompt": top_row[1], - # "completion": top_row[2], + "prompt": top_row[1], + "completion": top_row[2], "time": top_row[3].isoformat() if top_row and top_row[3] else None, "model": top_row[4], } if top_row else None, From b248f9724e65c289c546737eb6da28251f877db7 Mon Sep 17 00:00:00 2001 From: Wei Zang Date: Wed, 29 Apr 2026 19:44:30 +0100 Subject: [PATCH 2/3] Add Flickr API integration and routes Introduce a new Flickr integration under app/api/flickr: add a README, main router, and endpoints to (1) GET /flickr (return table counts and recent rows), (2) POST /flickr/createtable (create flickr_* tables), (3) POST /flickr/emptytables (delete rows), and (4) POST /flickr/sync (fetch public photos from Flickr API and insert into flickr_photos). Update .env.sample with FLICKR_USER, FLICKR_KEY, and FLICKR_SECRET variables and register flickr_router in app/api/routes.py. The sync route uses requests and dotenv, and created tables include flickr_accounts, flickr_photos, flickr_albums, and flickr_resources. --- .env.sample | 3 ++ app/api/flickr/README.md | 28 +++++++++++++ app/api/flickr/__init__.py | 14 +++++++ app/api/flickr/flickr.py | 44 +++++++++++++++++++++ app/api/flickr/sql/create_tables.py | 61 +++++++++++++++++++++++++++++ app/api/flickr/sql/empty_tables.py | 32 +++++++++++++++ app/api/flickr/sql/sync.py | 53 +++++++++++++++++++++++++ app/api/routes.py | 5 ++- 8 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 app/api/flickr/README.md create mode 100644 app/api/flickr/__init__.py create mode 100644 app/api/flickr/flickr.py create mode 100644 app/api/flickr/sql/create_tables.py create mode 100644 app/api/flickr/sql/empty_tables.py create mode 100644 app/api/flickr/sql/sync.py diff --git a/.env.sample b/.env.sample index 6b8292c..9eb3bc3 100644 --- a/.env.sample +++ b/.env.sample @@ -8,3 +8,6 @@ DB_PORT=5432 DB_NAME= DB_USER= DB_PASSWORD= +FLICKR_USER=@N00 +FLICKR_KEY= +FLICKR_SECRET= \ No newline at end of file diff --git a/app/api/flickr/README.md b/app/api/flickr/README.md new file mode 100644 index 0000000..1a24b2c --- /dev/null +++ b/app/api/flickr/README.md @@ -0,0 +1,28 @@ +# README for Flickr API integration + +This module provides API routes for accessing Flickr data, similar to the GitHub integration. It expects the following environment variables to be set in your .env file: + +- FLICKR_USER +- FLICKR_KEY +- FLICKR_SECRET + +### Route + +- **GET /flickr**: Returns counts and recent records from all Flickr tables. + +### Proposed Table Design + +1. flickr_accounts + - One row per Flickr account/user profile. + - Stores account identity fields and full raw payload. +2. flickr_photos + - One row per photo. + - Stores photo metadata plus raw JSON payload. +3. flickr_albums + - One row per album. + - Stores album metadata plus raw JSON payload. +4. flickr_resources + - Generic catch-all for any future Flickr resource type. + - Supports additional Flickr objects through jsonb payload storage. + +This structure mirrors the GitHub integration for consistency and flexibility. diff --git a/app/api/flickr/__init__.py b/app/api/flickr/__init__.py new file mode 100644 index 0000000..c5d7afe --- /dev/null +++ b/app/api/flickr/__init__.py @@ -0,0 +1,14 @@ +"""Flickr Routes""" + +from fastapi import APIRouter + +from .flickr import router as _flickr_router +from .sql.create_tables import router as _create_tables_router +from .sql.empty_tables import router as _empty_tables_router +from .sql.sync import router as _sync_router + +flickr_router = APIRouter() +flickr_router.include_router(_flickr_router) +flickr_router.include_router(_create_tables_router) +flickr_router.include_router(_empty_tables_router) +flickr_router.include_router(_sync_router) diff --git a/app/api/flickr/flickr.py b/app/api/flickr/flickr.py new file mode 100644 index 0000000..2e914a7 --- /dev/null +++ b/app/api/flickr/flickr.py @@ -0,0 +1,44 @@ +from fastapi import APIRouter, Depends +from app.utils.make_meta import make_meta +from app.utils.db import get_db_connection_direct +from app.utils.api_key_auth import get_api_key + +router = APIRouter() + +_TABLES = [ + "flickr_accounts", + "flickr_photos", + "flickr_albums", + "flickr_resources", +] + +def _fetch_table(cur, table: str) -> dict: + cur.execute(f"SELECT COUNT(*) FROM {table};") + row = cur.fetchone() + count = row[0] if row and row[0] is not None else 0 + cur.execute(f"SELECT * FROM {table} ORDER BY id DESC LIMIT 100;") + if cur.description: + columns = [desc[0] for desc in cur.description] + rows = [dict(zip(columns, r)) for r in cur.fetchall()] + else: + rows = [] + return {"count": count, "rows": rows} + + +@router.get("/flickr") +def get_flickr(api_key: str = Depends(get_api_key)) -> dict: + """GET /flickr: Return counts and records from all Flickr tables.""" + conn = None + cur = None + try: + conn = get_db_connection_direct() + cur = conn.cursor() + data = {table: _fetch_table(cur, table) for table in _TABLES} + return {"meta": make_meta("success", "Flickr data"), "data": data} + except Exception as e: + return {"meta": make_meta("error", f"DB error: {str(e)}"), "data": {}} + finally: + if cur is not None: + cur.close() + if conn is not None: + conn.close() diff --git a/app/api/flickr/sql/create_tables.py b/app/api/flickr/sql/create_tables.py new file mode 100644 index 0000000..30d6cbb --- /dev/null +++ b/app/api/flickr/sql/create_tables.py @@ -0,0 +1,61 @@ +from fastapi import APIRouter, Depends +from app.utils.db import get_db_connection_direct +from app.utils.make_meta import make_meta +from app.utils.api_key_auth import get_api_key + +router = APIRouter() + +@router.post("/flickr/createtable") +def create_flickr_tables(api_key: str = Depends(get_api_key)) -> dict: + """POST /flickr/createtable: Create Flickr tables in Postgres.""" + sql_statements = [ + # Drop tables if they exist (in reverse dependency order) + 'DROP TABLE IF EXISTS flickr_resources;', + 'DROP TABLE IF EXISTS flickr_albums;', + 'DROP TABLE IF EXISTS flickr_photos;', + 'DROP TABLE IF EXISTS flickr_accounts;', + '''CREATE TABLE IF NOT EXISTS flickr_accounts ( + id SERIAL PRIMARY KEY, + flickr_id TEXT, + username TEXT, + payload JSONB, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + );''', + '''CREATE TABLE IF NOT EXISTS flickr_photos ( + id SERIAL PRIMARY KEY, + flickr_id TEXT UNIQUE, + title TEXT, + payload JSONB, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + );''', + '''CREATE TABLE IF NOT EXISTS flickr_albums ( + id SERIAL PRIMARY KEY, + flickr_id TEXT, + title TEXT, + payload JSONB, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + );''', + '''CREATE TABLE IF NOT EXISTS flickr_resources ( + id SERIAL PRIMARY KEY, + resource_type TEXT, + flickr_id TEXT, + payload JSONB, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + );''' + ] + conn = None + cur = None + try: + conn = get_db_connection_direct() + cur = conn.cursor() + for stmt in sql_statements: + cur.execute(stmt) + conn.commit() + return {"meta": make_meta("success", "Flickr tables created"), "data": {}} + except Exception as e: + return {"meta": make_meta("error", f"DB error: {str(e)}"), "data": {}} + finally: + if cur is not None: + cur.close() + if conn is not None: + conn.close() diff --git a/app/api/flickr/sql/empty_tables.py b/app/api/flickr/sql/empty_tables.py new file mode 100644 index 0000000..48ac602 --- /dev/null +++ b/app/api/flickr/sql/empty_tables.py @@ -0,0 +1,32 @@ +from fastapi import APIRouter, Depends +from app.utils.db import get_db_connection_direct +from app.utils.make_meta import make_meta +from app.utils.api_key_auth import get_api_key + +router = APIRouter() + +@router.post("/flickr/emptytables") +def empty_flickr_tables(api_key: str = Depends(get_api_key)) -> dict: + """POST /flickr/emptytables: Delete all rows from all Flickr tables.""" + tables = [ + "flickr_accounts", + "flickr_photos", + "flickr_albums", + "flickr_resources" + ] + conn = None + cur = None + try: + conn = get_db_connection_direct() + cur = conn.cursor() + for table in tables: + cur.execute(f"DELETE FROM {table};") + conn.commit() + return {"meta": make_meta("success", "Flickr tables emptied"), "data": {}} + except Exception as e: + return {"meta": make_meta("error", f"DB error: {str(e)}"), "data": {}} + finally: + if cur is not None: + cur.close() + if conn is not None: + conn.close() diff --git a/app/api/flickr/sql/sync.py b/app/api/flickr/sql/sync.py new file mode 100644 index 0000000..597fb1c --- /dev/null +++ b/app/api/flickr/sql/sync.py @@ -0,0 +1,53 @@ +from fastapi import APIRouter, Depends +from app.utils.db import get_db_connection_direct +from app.utils.make_meta import make_meta +from app.utils.api_key_auth import get_api_key +import os +import requests +import json +from dotenv import load_dotenv + +router = APIRouter() + +@router.post("/flickr/sync") +def sync_flickr(api_key: str = Depends(get_api_key)) -> dict: + """POST /flickr/sync: Fetches data from Flickr API and stores in DB.""" + load_dotenv() + flickr_user = os.getenv("FLICKR_USER") + flickr_key = os.getenv("FLICKR_KEY") + flickr_secret = os.getenv("FLICKR_SECRET") + if not flickr_user or not flickr_key or not flickr_secret: + return {"meta": make_meta("error", "Missing Flickr API credentials"), "data": {}} + + # Example: Fetch public photos for the user + url = "https://api.flickr.com/services/rest/" + params = { + "method": "flickr.people.getPublicPhotos", + "api_key": flickr_key, + "user_id": flickr_user, + "format": "json", + "nojsoncallback": 1, + "per_page": 10 + } + try: + resp = requests.get(url, params=params) + resp.raise_for_status() + data = resp.json() + photos = data.get("photos", {}).get("photo", []) + conn = get_db_connection_direct() + cur = conn.cursor() + for photo in photos: + cur.execute( + """ + INSERT INTO flickr_photos (flickr_id, title, payload) + VALUES (%s, %s, %s) + ON CONFLICT (flickr_id) DO NOTHING; + """, + (photo.get("id"), photo.get("title"), json.dumps(photo)) + ) + conn.commit() + cur.close() + conn.close() + return {"meta": make_meta("success", f"Synced {len(photos)} photos from Flickr"), "data": {"count": len(photos)}} + except Exception as e: + return {"meta": make_meta("error", f"Sync error: {str(e)}"), "data": {}} diff --git a/app/api/routes.py b/app/api/routes.py index 02d29d3..857a3b5 100644 --- a/app/api/routes.py +++ b/app/api/routes.py @@ -14,7 +14,9 @@ from app.api.prospects.prospects import router as prospects_router from app.api.orders.orders import router as orders_router from app.api.queue import router as queue_router + from app.api.github import github_router +from app.api.flickr import flickr_router router.include_router(root_router) router.include_router(resend_router) @@ -25,4 +27,5 @@ router.include_router(prospects_router) router.include_router(orders_router) router.include_router(queue_router) -router.include_router(github_router) \ No newline at end of file +router.include_router(github_router) +router.include_router(flickr_router) \ No newline at end of file From ebd170ed4d50beab083b93b6caaeb200cf742f90 Mon Sep 17 00:00:00 2001 From: Wei Zang Date: Wed, 29 Apr 2026 19:46:22 +0100 Subject: [PATCH 3/3] Update README.md --- app/api/flickr/README.md | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/app/api/flickr/README.md b/app/api/flickr/README.md index 1a24b2c..9ecf223 100644 --- a/app/api/flickr/README.md +++ b/app/api/flickr/README.md @@ -1,28 +1,37 @@ -# README for Flickr API integration -This module provides API routes for accessing Flickr data, similar to the GitHub integration. It expects the following environment variables to be set in your .env file: +# Flickr API Integration -- FLICKR_USER -- FLICKR_KEY -- FLICKR_SECRET +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: -### Route +- `FLICKR_USER` +- `FLICKR_KEY` +- `FLICKR_SECRET` + +## Endpoints - **GET /flickr**: Returns counts and recent records from all Flickr tables. +- **POST /flickr/createtable**: Drops all Flickr tables if they exist, then recreates them with the correct schema and constraints. +- **POST /flickr/emptytables**: Deletes all rows from all Flickr tables (does not drop tables). +- **POST /flickr/sync**: Fetches public photos for the configured Flickr user and stores them in the database. -### Proposed Table Design +## Table Design -1. flickr_accounts +1. **flickr_accounts** - One row per Flickr account/user profile. - Stores account identity fields and full raw payload. -2. flickr_photos +2. **flickr_photos** - One row per photo. + - `flickr_id` is unique. - Stores photo metadata plus raw JSON payload. -3. flickr_albums +3. **flickr_albums** - One row per album. - Stores album metadata plus raw JSON payload. -4. flickr_resources +4. **flickr_resources** - Generic catch-all for any future Flickr resource type. - Supports additional Flickr objects through jsonb payload storage. -This structure mirrors the GitHub integration for consistency and flexibility. +## Notes + +- The `/flickr/createtable` endpoint will **drop all Flickr tables** before recreating them. Use with caution—this will erase all Flickr data. +- The `/flickr/sync` endpoint currently fetches public photos for the configured user and inserts them into `flickr_photos`. +- The structure and endpoints are designed for consistency and flexibility, matching the GitHub integration.