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/__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/flickr/README.md b/app/api/flickr/README.md new file mode 100644 index 0000000..9ecf223 --- /dev/null +++ b/app/api/flickr/README.md @@ -0,0 +1,37 @@ + +# Flickr API Integration + +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: + +- `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. + +## 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. + - `flickr_id` is unique. + - 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. + +## 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. 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/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, 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