"""Seed reference data: the four plans, and an optional admin account. Usage: python seed.py # seed plans only python seed.py --admin EMAIL PASS # also create/upgrade an admin user """ import sys from app import create_app from app.extensions import db from app.models.plan import Plan from app.models.user import User from app.models.category import Category from app.models.geo import ZipGeo from app.models.enums import Role PLANS = [ { "slug": "free", "name": "Free", "price_monthly_cents": 0, "sort_order": 0, "config": { "active_listings": 3, "listing_life_days": 14, "images_per_listing": 3, "featured_per_month": 0, "auto_bump": None, "analytics": None, "storefront": False, "verified_badge": False, "ad_free": False, "scheduled_posting": False, "bulk_csv": False, "priority_support": False, }, }, { "slug": "basic", "name": "Basic", "price_monthly_cents": 999, "sort_order": 1, "config": { "active_listings": 15, "listing_life_days": 30, "images_per_listing": 8, "featured_per_month": 1, "auto_bump": None, "analytics": "basic", "storefront": False, "verified_badge": False, "ad_free": True, "scheduled_posting": False, "bulk_csv": False, "priority_support": False, }, }, { "slug": "pro", "name": "Pro", "price_monthly_cents": 2499, "sort_order": 2, "config": { "active_listings": 50, "listing_life_days": 60, "images_per_listing": 15, "featured_per_month": 5, "auto_bump": "weekly", "analytics": "full", "storefront": True, "verified_badge": True, "ad_free": True, "scheduled_posting": True, "bulk_csv": False, "priority_support": False, }, }, { "slug": "business", "name": "Business", "price_monthly_cents": 5999, "sort_order": 3, "config": { "active_listings": None, "listing_life_days": 90, "images_per_listing": 25, "featured_per_month": 20, "auto_bump": "daily", "analytics": "full_export", "storefront": True, "verified_badge": True, "ad_free": True, "scheduled_posting": True, "bulk_csv": True, "priority_support": True, }, }, ] def seed_plans(): for p in PLANS: existing = Plan.query.filter_by(slug=p["slug"]).first() if existing: existing.name = p["name"] existing.price_monthly_cents = p["price_monthly_cents"] existing.config = p["config"] existing.sort_order = p["sort_order"] else: db.session.add(Plan(**p)) db.session.commit() print(f"Seeded {len(PLANS)} plans.") CATEGORIES = [ {"slug": "for-sale", "name": "For Sale", "icon": "tag", "sort_order": 0, "field_schema": {"fields": [ {"name": "condition", "label": "Condition", "type": "select", "required": True, "options": ["new", "like_new", "good", "fair"], "hot": True}, {"name": "brand", "label": "Brand", "type": "text", "required": False, "max": 80}, ]}, "children": ["Furniture", "Electronics", "Cars & Trucks", "Appliances"]}, {"slug": "wanted", "name": "Wanted", "icon": "search", "sort_order": 1, "field_schema": {"fields": [ {"name": "budget", "label": "Budget (USD)", "type": "number", "required": False, "min": 0}, ]}, "children": ["Items Wanted", "Housing Wanted"]}, {"slug": "jobs", "name": "Jobs", "icon": "briefcase", "sort_order": 2, "field_schema": {"fields": [ {"name": "job_type", "label": "Job type", "type": "select", "required": True, "options": ["full_time", "part_time", "contract", "temp"], "hot": True}, {"name": "salary_min", "label": "Salary min (USD/yr)", "type": "number", "required": False, "min": 0, "hot": True}, {"name": "salary_max", "label": "Salary max (USD/yr)", "type": "number", "required": False, "min": 0, "hot": True}, {"name": "remote", "label": "Remote", "type": "bool", "required": False}, ]}, "children": ["Restaurant", "Salon & Spa", "Construction", "Office"]}, {"slug": "services", "name": "Services", "icon": "tools", "sort_order": 3, "field_schema": {"fields": [ {"name": "rate", "label": "Rate (USD/hr)", "type": "number", "required": False, "min": 0}, ]}, "children": ["Home Services", "Beauty", "Tutoring", "Auto Repair"]}, {"slug": "supplies", "name": "Supplies", "icon": "box", "sort_order": 4, "field_schema": {"fields": [ {"name": "min_order", "label": "Min order qty", "type": "number", "required": False, "min": 1}, {"name": "condition", "label": "Condition", "type": "select", "required": False, "options": ["new", "used"], "hot": True}, ]}, "children": ["Wholesale", "Restaurant Supply", "Salon Supply"]}, {"slug": "community", "name": "Community", "icon": "users", "sort_order": 5, "field_schema": {"fields": [ {"name": "event_date", "label": "Event date", "type": "text", "required": False, "max": 40}, ]}, "children": ["Events", "Announcements", "Free Stuff", "Classes"]}, ] # Sample ZIPs for Vietnamese/Hispanic hubs (replace with full SimpleMaps/Census dataset). ZIP_SAMPLE = [ ("92683", "Westminster", "CA", 33.7513, -117.9939, "Orange County"), ("92840", "Garden Grove", "CA", 33.7886, -117.9390, "Orange County"), ("95111", "San Jose", "CA", 37.2956, -121.8200, "San Jose"), ("95122", "San Jose", "CA", 37.3300, -121.8350, "San Jose"), ("77036", "Houston", "TX", 29.7045, -95.5380, "Houston"), ("77072", "Houston", "TX", 29.7060, -95.5930, "Houston"), ("78228", "San Antonio", "TX", 29.4630, -98.5790, "San Antonio"), ("33125", "Miami", "FL", 25.7840, -80.2370, "Miami"), ("90250", "Hawthorne", "CA", 33.9130, -118.3490, "Los Angeles"), ("22041", "Falls Church", "VA", 38.8480, -77.1370, "DC Metro"), ] def seed_categories(): for c in CATEGORIES: parent = Category.query.filter_by(slug=c["slug"]).first() if parent is None: parent = Category(slug=c["slug"], name=c["name"], icon=c.get("icon"), sort_order=c["sort_order"], field_schema=c["field_schema"]) db.session.add(parent) db.session.flush() else: parent.field_schema = c["field_schema"] parent.sort_order = c["sort_order"] for i, child_name in enumerate(c.get("children", [])): cslug = f"{c['slug']}-{child_name.lower().replace(' & ', '-').replace(' ', '-')}" if Category.query.filter_by(slug=cslug).first() is None: db.session.add(Category(slug=cslug, name=child_name, parent_id=parent.id, sort_order=i, field_schema=c["field_schema"])) db.session.commit() print(f"Seeded {len(CATEGORIES)} top categories + subcategories.") def seed_zip_sample(): for z, city, st, lat, lng, metro in ZIP_SAMPLE: if ZipGeo.query.get(z) is None: db.session.add(ZipGeo(zip=z, city=city, state=st, lat=lat, lng=lng, metro=metro)) db.session.commit() print(f"Seeded {len(ZIP_SAMPLE)} sample ZIP rows.") def make_admin(email, password): user = User.query.filter_by(email=email.lower()).first() if user is None: user = User(email=email.lower(), display_name="Admin", role=Role.admin, email_verified=True) user.set_password(password) db.session.add(user) else: user.role = Role.admin user.email_verified = True user.set_password(password) db.session.commit() print(f"Admin ready: {email}") if __name__ == "__main__": app = create_app() with app.app_context(): seed_plans() seed_categories() seed_zip_sample() if "--admin" in sys.argv: i = sys.argv.index("--admin") make_admin(sys.argv[i + 1], sys.argv[i + 2])