326 lines
10 KiB
Python
326 lines
10 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
from typing import Any
|
|
|
|
from .pb_client import PocketBaseError, pb
|
|
from .seed_data import (
|
|
CITIES,
|
|
CITY_DETAILS,
|
|
CONTENT_ITEMS,
|
|
DISCUSSIONS,
|
|
GIGS,
|
|
MEETUPS,
|
|
PROFILES,
|
|
)
|
|
from .enhanced_seed import ENHANCED_DISCUSSIONS, ENHANCED_GIGS, ENHANCED_MEETUPS, ENHANCED_PROFILES, ROUTES, SERVICES
|
|
|
|
|
|
def text(name: str, *, required: bool = False, max: int = 0) -> dict[str, Any]:
|
|
return {"name": name, "type": "text", "required": required, "max": max}
|
|
|
|
|
|
def number(name: str, *, required: bool = False, only_int: bool = False) -> dict[str, Any]:
|
|
return {"name": name, "type": "number", "required": required, "onlyInt": only_int}
|
|
|
|
|
|
def boolean(name: str) -> dict[str, Any]:
|
|
return {"name": name, "type": "bool"}
|
|
|
|
|
|
def json_field(name: str) -> dict[str, Any]:
|
|
return {"name": name, "type": "json"}
|
|
|
|
|
|
def date(name: str) -> dict[str, Any]:
|
|
return {"name": name, "type": "date"}
|
|
|
|
|
|
COLLECTIONS: dict[str, list[dict[str, Any]]] = {
|
|
"cities": [
|
|
text("slug", required=True, max=80),
|
|
number("idNumber", only_int=True),
|
|
text("name", required=True, max=120),
|
|
text("nameEn", max=120),
|
|
text("country", max=80),
|
|
text("countryEmoji", max=16),
|
|
number("overallScore"),
|
|
number("costPerMonth", only_int=True),
|
|
number("internetSpeed", only_int=True),
|
|
text("safety", max=40),
|
|
text("liked", max=40),
|
|
number("temperature", only_int=True),
|
|
number("humidity", only_int=True),
|
|
number("nomadsNow", only_int=True),
|
|
text("description", max=500),
|
|
text("descriptionEn", max=500),
|
|
text("gradientFrom", max=32),
|
|
text("gradientTo", max=32),
|
|
text("icon", max=16),
|
|
json_field("tags"),
|
|
text("coverImage", max=500),
|
|
number("reviewsCount", only_int=True),
|
|
number("likedCount", only_int=True),
|
|
number("dislikedCount", only_int=True),
|
|
text("qualityOfLife", max=40),
|
|
text("familyScore", max=40),
|
|
text("communityScore", max=40),
|
|
number("nomadPercent"),
|
|
text("funLabel", max=40),
|
|
number("feelsLikeTemp", only_int=True),
|
|
number("acPercent", only_int=True),
|
|
number("lat"),
|
|
number("lng"),
|
|
text("region", max=80),
|
|
],
|
|
"profiles": [
|
|
text("userId", max=80),
|
|
text("email", max=255),
|
|
text("name", max=120),
|
|
number("age", only_int=True),
|
|
text("location", max=120),
|
|
json_field("tags"),
|
|
text("bio", max=1000),
|
|
text("photo", max=500),
|
|
boolean("needsPasswordChange"),
|
|
],
|
|
"member_applications": [
|
|
text("userId", max=80),
|
|
text("email", max=255),
|
|
text("nickname", max=140),
|
|
text("gender", max=40),
|
|
text("single", max=40),
|
|
text("profession", max=140),
|
|
text("intro", max=1000),
|
|
text("wechat", max=140),
|
|
text("education", max=80),
|
|
text("graduationYear", max=20),
|
|
text("phone", max=40),
|
|
text("media", max=500),
|
|
boolean("isComplete"),
|
|
],
|
|
"memberships": [
|
|
text("userId", required=True, max=80),
|
|
text("email", max=255),
|
|
text("plan", max=40),
|
|
text("status", max=40),
|
|
date("expiresAt"),
|
|
],
|
|
"orders": [
|
|
text("orderId", required=True, max=80),
|
|
text("userId", max=120),
|
|
text("email", max=255),
|
|
number("totalFee", only_int=True),
|
|
text("type", max=60),
|
|
text("channel", max=40),
|
|
text("status", max=40),
|
|
text("returnUrl", max=500),
|
|
boolean("isNew"),
|
|
],
|
|
"media_assets": [
|
|
text("userId", max=80),
|
|
text("url", required=True, max=500),
|
|
text("filename", max=255),
|
|
text("contentType", max=120),
|
|
number("size", only_int=True),
|
|
],
|
|
"meetups": [
|
|
text("city", required=True, max=120),
|
|
text("emoji", max=16),
|
|
date("date"),
|
|
text("time", max=20),
|
|
text("venue", max=255),
|
|
text("address", max=255),
|
|
text("description", max=1000),
|
|
number("rsvpCount", only_int=True),
|
|
text("gradientFrom", max=32),
|
|
text("gradientTo", max=32),
|
|
json_field("attendees"),
|
|
boolean("isUpcoming"),
|
|
text("status", max=40),
|
|
],
|
|
"discussions": [
|
|
text("title", required=True, max=255),
|
|
text("author", max=120),
|
|
text("city", max=120),
|
|
json_field("tags"),
|
|
text("excerpt", max=1000),
|
|
number("replies", only_int=True),
|
|
number("views", only_int=True),
|
|
],
|
|
"gigs": [
|
|
text("title", required=True, max=255),
|
|
text("category", max=120),
|
|
number("budget", only_int=True),
|
|
text("location", max=120),
|
|
text("description", max=1000),
|
|
text("status", max=40),
|
|
],
|
|
"gig_applications": [
|
|
text("gigId", required=True, max=80),
|
|
text("userId", max=80),
|
|
text("applicant", max=255),
|
|
text("message", max=1000),
|
|
text("status", max=40),
|
|
],
|
|
"services": [
|
|
text("slug", required=True, max=120),
|
|
text("title", required=True, max=255),
|
|
text("titleEn", max=255),
|
|
text("icon", max=16),
|
|
text("description", max=1000),
|
|
text("descriptionEn", max=1000),
|
|
json_field("features"),
|
|
number("price", only_int=True),
|
|
text("priceLabel", max=120),
|
|
text("category", max=80),
|
|
text("status", max=40),
|
|
text("gradientFrom", max=32),
|
|
text("gradientTo", max=32),
|
|
],
|
|
"service_leads": [
|
|
text("serviceSlug", required=True, max=120),
|
|
text("userId", max=80),
|
|
text("email", max=255),
|
|
text("note", max=1000),
|
|
text("status", max=40),
|
|
],
|
|
"routes": [
|
|
text("slug", required=True, max=120),
|
|
text("title", required=True, max=255),
|
|
text("titleEn", max=255),
|
|
json_field("citySlugs"),
|
|
number("durationDays", only_int=True),
|
|
number("budget", only_int=True),
|
|
text("description", max=1000),
|
|
text("descriptionEn", max=1000),
|
|
json_field("stops"),
|
|
text("status", max=40),
|
|
],
|
|
"recommendation_logs": [
|
|
text("userId", max=80),
|
|
number("budget", only_int=True),
|
|
number("internet", only_int=True),
|
|
text("climate", max=80),
|
|
json_field("tags"),
|
|
json_field("resultSlugs"),
|
|
],
|
|
"feedback": [
|
|
text("type", max=40),
|
|
text("email", max=255),
|
|
text("title", max=255),
|
|
text("content", max=2000),
|
|
text("status", max=40),
|
|
],
|
|
"swipes": [text("userId", max=80), text("profileId", max=80), text("action", max=40)],
|
|
"content_items": [
|
|
text("slug", required=True, max=120),
|
|
text("type", required=True, max=40),
|
|
text("title", required=True, max=255),
|
|
text("titleEn", max=255),
|
|
text("subtitle", max=500),
|
|
text("subtitleEn", max=500),
|
|
text("description", max=2000),
|
|
text("descriptionEn", max=2000),
|
|
text("coverImage", max=500),
|
|
text("mediaUrl", max=500),
|
|
text("targetUrl", max=500),
|
|
text("ctaLabel", max=120),
|
|
text("ctaLabelEn", max=120),
|
|
json_field("citySlugs"),
|
|
json_field("relatedProfiles"),
|
|
json_field("chapters"),
|
|
number("durationMinutes", only_int=True),
|
|
number("sortOrder", only_int=True),
|
|
text("status", max=40),
|
|
],
|
|
"city_details": [
|
|
text("citySlug", required=True, max=120),
|
|
json_field("guide"),
|
|
json_field("pros"),
|
|
json_field("reviews"),
|
|
json_field("cost"),
|
|
json_field("people"),
|
|
json_field("chat"),
|
|
json_field("photos"),
|
|
json_field("weather"),
|
|
json_field("trends"),
|
|
json_field("demographics"),
|
|
json_field("relatedContent"),
|
|
],
|
|
}
|
|
|
|
|
|
async def collection_exists(name: str) -> bool:
|
|
try:
|
|
await pb.request("GET", f"/api/collections/{name}", admin=True)
|
|
return True
|
|
except PocketBaseError:
|
|
return False
|
|
|
|
|
|
async def create_collection(name: str, fields: list[dict[str, Any]]) -> None:
|
|
if await collection_exists(name):
|
|
print(f"collection exists: {name}")
|
|
return
|
|
payload = {
|
|
"name": name,
|
|
"type": "base",
|
|
"listRule": "",
|
|
"viewRule": "",
|
|
"createRule": "",
|
|
"updateRule": "",
|
|
"deleteRule": "",
|
|
"fields": fields,
|
|
}
|
|
await pb.request("POST", "/api/collections", admin=True, json=payload)
|
|
print(f"collection created: {name}")
|
|
|
|
|
|
async def seed_if_empty(collection: str, records: list[dict[str, Any]]) -> None:
|
|
data = await pb.list_records(collection, per_page=1)
|
|
if data.get("totalItems", 0) > 0:
|
|
print(f"seed skipped: {collection}")
|
|
return
|
|
for record in records:
|
|
payload = dict(record)
|
|
if collection == "meetups" and payload.get("date"):
|
|
payload["date"] = f"{payload['date']} 00:00:00.000Z"
|
|
payload.setdefault("status", "published")
|
|
await pb.create_record(collection, payload)
|
|
print(f"seeded {collection}: {len(records)} records")
|
|
|
|
|
|
async def upsert_seed(collection: str, key: str, records: list[dict[str, Any]]) -> None:
|
|
count = 0
|
|
for record in records:
|
|
payload = dict(record)
|
|
if collection == "meetups" and payload.get("date") and "T" not in str(payload["date"]):
|
|
payload["date"] = f"{payload['date']} 00:00:00.000Z"
|
|
payload.setdefault("status", "published")
|
|
await pb.upsert_by_field(collection, key, str(payload[key]), payload)
|
|
count += 1
|
|
print(f"upserted {collection}: {count} records")
|
|
|
|
|
|
async def main() -> None:
|
|
for name, fields in COLLECTIONS.items():
|
|
await create_collection(name, fields)
|
|
await upsert_seed("cities", "slug", CITIES)
|
|
await seed_if_empty("meetups", MEETUPS)
|
|
await seed_if_empty("discussions", DISCUSSIONS)
|
|
await seed_if_empty("gigs", GIGS)
|
|
await seed_if_empty("profiles", PROFILES)
|
|
await upsert_seed("meetups", "venue", ENHANCED_MEETUPS)
|
|
await upsert_seed("discussions", "title", ENHANCED_DISCUSSIONS)
|
|
await upsert_seed("gigs", "title", ENHANCED_GIGS)
|
|
await upsert_seed("profiles", "name", ENHANCED_PROFILES)
|
|
await upsert_seed("content_items", "slug", CONTENT_ITEMS)
|
|
await upsert_seed("city_details", "citySlug", CITY_DETAILS)
|
|
await upsert_seed("services", "slug", SERVICES)
|
|
await upsert_seed("routes", "slug", ROUTES)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|