Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ DB_PORT=5432
DB_NAME=
DB_USER=
DB_PASSWORD=
FLICKR_USER=@N00
FLICKR_KEY=
FLICKR_SECRET=
2 changes: 1 addition & 1 deletion app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Python° - FastAPI, Postgres, tsvector"""

# Current Version
__version__ = "3.0.7"
__version__ = "3.0.8"
37 changes: 37 additions & 0 deletions app/api/flickr/README.md
Original file line number Diff line number Diff line change
@@ -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.
14 changes: 14 additions & 0 deletions app/api/flickr/__init__.py
Original file line number Diff line number Diff line change
@@ -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)
44 changes: 44 additions & 0 deletions app/api/flickr/flickr.py
Original file line number Diff line number Diff line change
@@ -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()
61 changes: 61 additions & 0 deletions app/api/flickr/sql/create_tables.py
Original file line number Diff line number Diff line change
@@ -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()
32 changes: 32 additions & 0 deletions app/api/flickr/sql/empty_tables.py
Original file line number Diff line number Diff line change
@@ -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()
53 changes: 53 additions & 0 deletions app/api/flickr/sql/sync.py
Original file line number Diff line number Diff line change
@@ -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": {}}
6 changes: 3 additions & 3 deletions app/api/prompt/prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
5 changes: 4 additions & 1 deletion app/api/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -25,4 +27,5 @@
router.include_router(prospects_router)
router.include_router(orders_router)
router.include_router(queue_router)
router.include_router(github_router)
router.include_router(github_router)
router.include_router(flickr_router)
Loading