nomadweb/backend/app/services/pocketbase.py
2026-08-28 19:49:10 -05:00

184 lines
6.0 KiB
Python

import httpx
from typing import Any
from app.config import settings
from app.data import mock_data
class PocketBaseService:
def __init__(self):
self.base_url = settings.pocketbase_url.rstrip("/")
self._token: str | None = None
self._available: bool | None = None
async def is_available(self) -> bool:
if self._available is not None:
return self._available
try:
async with httpx.AsyncClient(timeout=1.0) as client:
r = await client.get(f"{self.base_url}/api/health")
self._available = r.status_code == 200
except Exception:
self._available = False
return self._available
async def _auth(self, client: httpx.AsyncClient) -> bool:
if self._token:
return True
try:
r = await client.post(
f"{self.base_url}/api/admins/auth-with-password",
json={
"identity": settings.pocketbase_admin_email,
"password": settings.pocketbase_admin_password,
},
)
if r.status_code == 200:
self._token = r.json().get("token")
return True
except Exception:
pass
return False
async def list_records(self, collection: str, sort: str = "") -> list[dict[str, Any]]:
if not await self.is_available():
return []
params: dict[str, str] = {"perPage": "200"}
if sort:
params["sort"] = sort
try:
async with httpx.AsyncClient(timeout=10.0) as client:
headers = {}
if await self._auth(client):
headers["Authorization"] = self._token or ""
r = await client.get(
f"{self.base_url}/api/collections/{collection}/records",
params=params,
headers=headers,
)
if r.status_code == 200:
items = r.json().get("items", [])
return items if items else []
except Exception:
pass
return []
async def create_record(self, collection: str, data: dict[str, Any]) -> dict[str, Any] | None:
if not await self.is_available():
return None
try:
async with httpx.AsyncClient(timeout=10.0) as client:
headers = {}
if await self._auth(client):
headers["Authorization"] = self._token or ""
r = await client.post(
f"{self.base_url}/api/collections/{collection}/records",
json=data,
headers=headers,
)
if r.status_code in (200, 201):
return r.json()
except Exception:
pass
return None
async def get_destinations(
self, region: str | None = None, search: str | None = None, sort: str = "rating"
) -> list[dict]:
records = await self.list_records("destinations", sort=f"-{sort}" if sort != "cost" else "cost")
if not records:
records = mock_data.DESTINATIONS
else:
records = [self._map_destination(r) for r in records]
if region and region != "all":
records = [d for d in records if d.get("region") == region]
if search:
q = search.lower()
records = [
d for d in records
if q in d.get("name", "").lower() or q in d.get("country", "").lower()
]
return self._sort_destinations(records, sort)
async def get_destination(self, slug: str) -> dict | None:
records = await self.get_destinations()
return next((d for d in records if d.get("slug") == slug), None)
async def get_visas(self) -> list[dict]:
records = await self.list_records("visas", sort="difficulty")
return records if records else mock_data.VISAS
async def get_faqs(self) -> list[dict]:
records = await self.list_records("faqs", sort="order")
return records if records else mock_data.FAQS
async def get_testimonials(self) -> list[dict]:
records = await self.list_records("testimonials")
return records if records else mock_data.TESTIMONIALS
async def get_tools(self) -> list[dict]:
records = await self.list_records("tools")
return records if records else mock_data.TOOLS
async def get_blog_posts(self) -> list[dict]:
records = await self.list_records("blog_posts", sort="-published_at")
return records if records else mock_data.BLOG_POSTS
async def get_blog_post(self, slug: str) -> dict | None:
posts = await self.get_blog_posts()
post = next((p for p in posts if p.get("slug") == slug), None)
if not post:
return None
content = post.get("content") or mock_data.BLOG_CONTENT.get(slug, "")
return {**post, "content": content}
async def subscribe(self, email: str) -> tuple[bool, str]:
existing = await self.list_records("subscriptions")
if any(s.get("email") == email for s in existing):
return True, "你已经订阅过了,欢迎回来!"
result = await self.create_record("subscriptions", {"email": email})
if result:
return True, "订阅成功!欢迎加入 NomadFlow 社区 🎉"
# fallback: always succeed in demo mode
return True, "订阅成功!欢迎加入 NomadFlow 社区 🎉"
def _map_destination(self, r: dict) -> dict:
return {
"id": r.get("id", ""),
"slug": r.get("slug", ""),
"name": r.get("name", ""),
"country": r.get("country", ""),
"emoji": r.get("emoji", ""),
"tag": r.get("tag", ""),
"description": r.get("description", ""),
"region": r.get("region", ""),
"cost": r.get("cost", 0),
"speed": r.get("speed", 0),
"temperature": r.get("temperature", 0),
"rating": r.get("rating", 0),
"hue": r.get("hue", 170),
"nomads_count": r.get("nomads_count", ""),
"highlights": r.get("highlights", []),
"map_x": r.get("map_x", 0),
"map_y": r.get("map_y", 0),
}
def _sort_destinations(self, records: list[dict], sort: str) -> list[dict]:
if sort == "cost-asc":
return sorted(records, key=lambda x: x.get("cost", 0))
if sort == "cost-desc":
return sorted(records, key=lambda x: x.get("cost", 0), reverse=True)
if sort == "speed":
return sorted(records, key=lambda x: x.get("speed", 0), reverse=True)
return sorted(records, key=lambda x: x.get("rating", 0), reverse=True)
pb = PocketBaseService()