diff --git a/backend/app/scheduler.py b/backend/app/scheduler.py index 893f4e2..c6d7783 100644 --- a/backend/app/scheduler.py +++ b/backend/app/scheduler.py @@ -11,6 +11,7 @@ from app.config import get_settings from app.database import AsyncSessionLocal from app.models.domain import Domain, DomainCheck from app.models.user import User +from app.models.subscription import Subscription, SubscriptionTier, TIER_CONFIG from app.services.domain_checker import domain_checker from app.services.email_service import email_service from app.services.price_tracker import price_tracker @@ -44,14 +45,38 @@ async def scrape_tld_prices(): logger.exception(f"TLD price scrape failed: {e}") -async def check_all_domains(): - """Check availability of all monitored domains.""" - logger.info("Starting daily domain check...") +async def check_domains_by_frequency(frequency: str): + """Check availability of domains based on their subscription frequency. + + Args: + frequency: One of 'daily', 'hourly', 'realtime' (10-min) + """ + logger.info(f"Starting {frequency} domain check...") start_time = datetime.utcnow() async with AsyncSessionLocal() as db: - # Get all domains - result = await db.execute(select(Domain)) + # Get users with matching check frequency + tiers_for_frequency = [] + for tier, config in TIER_CONFIG.items(): + if config['check_frequency'] == frequency: + tiers_for_frequency.append(tier) + # Realtime includes hourly and daily too (more frequent = superset) + elif frequency == 'realtime': + tiers_for_frequency.append(tier) + elif frequency == 'hourly' and config['check_frequency'] in ['hourly', 'realtime']: + tiers_for_frequency.append(tier) + + # Get domains from users with matching subscription tier + from sqlalchemy.orm import joinedload + result = await db.execute( + select(Domain) + .join(User, Domain.user_id == User.id) + .outerjoin(Subscription, Subscription.user_id == User.id) + .where( + (Subscription.tier.in_(tiers_for_frequency)) | + (Subscription.id.is_(None) & (frequency == 'daily')) # Scout users (no subscription) = daily + ) + ) domains = result.scalars().all() logger.info(f"Checking {len(domains)} domains...") @@ -112,14 +137,47 @@ async def check_all_domains(): await send_domain_availability_alerts(db, newly_available) +async def check_all_domains(): + """Legacy function - checks all domains (daily).""" + await check_domains_by_frequency('daily') + + +async def check_hourly_domains(): + """Check domains for Trader users (hourly).""" + await check_domains_by_frequency('hourly') + + +async def check_realtime_domains(): + """Check domains for Tycoon users (every 10 minutes).""" + await check_domains_by_frequency('realtime') + + def setup_scheduler(): """Configure and start the scheduler.""" - # Daily domain check at configured hour + # Daily domain check for Scout users at configured hour scheduler.add_job( check_all_domains, CronTrigger(hour=settings.check_hour, minute=settings.check_minute), id="daily_domain_check", - name="Daily Domain Availability Check", + name="Daily Domain Check (Scout)", + replace_existing=True, + ) + + # Hourly domain check for Trader users + scheduler.add_job( + check_hourly_domains, + CronTrigger(minute=0), # Every hour at :00 + id="hourly_domain_check", + name="Hourly Domain Check (Trader)", + replace_existing=True, + ) + + # 10-minute domain check for Tycoon users + scheduler.add_job( + check_realtime_domains, + CronTrigger(minute='*/10'), # Every 10 minutes + id="realtime_domain_check", + name="10-Minute Domain Check (Tycoon)", replace_existing=True, ) @@ -152,7 +210,9 @@ def setup_scheduler(): logger.info( f"Scheduler configured:" - f"\n - Domain check at {settings.check_hour:02d}:{settings.check_minute:02d}" + f"\n - Scout domain check at {settings.check_hour:02d}:{settings.check_minute:02d} (daily)" + f"\n - Trader domain check every hour at :00" + f"\n - Tycoon domain check every 10 minutes" f"\n - TLD price scrape at 03:00 UTC" f"\n - Price change alerts at 04:00 UTC" f"\n - Auction scrape every hour at :30" diff --git a/frontend/src/app/pricing/page.tsx b/frontend/src/app/pricing/page.tsx index d910e46..1223cc3 100644 --- a/frontend/src/app/pricing/page.tsx +++ b/frontend/src/app/pricing/page.tsx @@ -231,10 +231,7 @@ export default function PricingPage() { {tier.features.map((feature) => (
  • - + {feature.text}
  • diff --git a/frontend/src/app/settings/page.tsx b/frontend/src/app/settings/page.tsx index f0f35bb..8f4309b 100644 --- a/frontend/src/app/settings/page.tsx +++ b/frontend/src/app/settings/page.tsx @@ -8,7 +8,6 @@ import { useStore } from '@/lib/store' import { api, PriceAlert } from '@/lib/api' import { User, - Mail, Bell, CreditCard, Shield, @@ -20,8 +19,8 @@ import { ExternalLink, Crown, Zap, - Settings, Key, + TrendingUp, } from 'lucide-react' import Link from 'next/link' import clsx from 'clsx' @@ -466,98 +465,214 @@ export default function SettingsPage() { {/* Billing Tab */} {activeTab === 'billing' && ( -
    -

    Subscription & Billing

    - -
    -
    -
    -

    {tierName} Plan

    -

    - {subscription?.check_frequency || 'Daily'} checks · {subscription?.domain_limit || 5} domains -

    -
    - - {isProOrHigher ? 'Active' : 'Free'} - -
    +
    + {/* Current Plan */} +
    +

    Your Current Plan

    - {isProOrHigher ? ( - - ) : ( - - - Upgrade Plan - - )} -
    +
    +
    +
    + {tierName === 'Tycoon' ? ( + + ) : tierName === 'Trader' ? ( + + ) : ( + + )} +
    +

    {tierName}

    +

    + {tierName === 'Scout' ? 'Free forever' : tierName === 'Trader' ? '$19/month' : '$49/month'} +

    +
    +
    + + {isProOrHigher ? 'Active' : 'Free'} + +
    + + {/* Plan Stats */} +
    +
    +

    {subscription?.domain_limit || 5}

    +

    Domains

    +
    +
    +

    + {subscription?.check_frequency === 'realtime' ? '10m' : + subscription?.check_frequency === 'hourly' ? '1h' : '24h'} +

    +

    Check Interval

    +
    +
    +

    + {subscription?.portfolio_limit === -1 ? '∞' : subscription?.portfolio_limit || 0} +

    +

    Portfolio

    +
    +
    + + {isProOrHigher ? ( + + ) : ( + + + Upgrade Plan + + )} +
    -
    -

    Plan Features

    -
      - {subscription?.features && Object.entries(subscription.features) - .filter(([key]) => !['sms_alerts', 'api_access', 'webhooks', 'bulk_tools', 'seo_metrics'].includes(key)) - .map(([key, value]) => { - const featureNames: Record = { - email_alerts: 'Email Alerts', - priority_alerts: 'Priority Alerts', - full_whois: 'Full WHOIS Data', - expiration_tracking: 'Expiry Tracking', - domain_valuation: 'Domain Valuation', - market_insights: 'Market Insights', - } - return ( -
    • - {value ? ( - - ) : ( - - )} - - {featureNames[key] || key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())} - -
    • - ) - })} - {/* Show additional plan info */} + {/* Plan Features */} +

      Your Plan Includes

      +
        +
      • + + {subscription?.domain_limit || 5} Watchlist Domains +
      • - {subscription?.domain_limit} Watchlist Domains + {subscription?.check_frequency === 'realtime' ? '10-minute' : + subscription?.check_frequency === 'hourly' ? 'Hourly' : 'Daily'} Scans
      • +
      • + + Email Alerts +
      • +
      • + + TLD Price Data +
      • + {subscription?.features?.domain_valuation && ( +
      • + + Domain Valuation +
      • + )} {(subscription?.portfolio_limit ?? 0) !== 0 && (
      • - {subscription?.portfolio_limit === -1 ? 'Unlimited' : subscription?.portfolio_limit} Portfolio Domains + {subscription?.portfolio_limit === -1 ? 'Unlimited' : subscription?.portfolio_limit} Portfolio
      • )} + {subscription?.features?.expiration_tracking && ( +
      • + + Expiry Tracking +
      • + )} {(subscription?.history_days ?? 0) !== 0 && (
      • - {subscription?.history_days === -1 ? 'Full' : `${subscription?.history_days}-day`} Price History + {subscription?.history_days === -1 ? 'Full' : `${subscription?.history_days}-day`} History
      • )}
    + + {/* Compare All Plans */} +
    +

    Compare All Plans

    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FeatureScoutTraderTycoon
    PriceFree$19/mo$49/mo
    Watchlist Domains550500
    Scan FrequencyDailyHourly10 min
    Portfolio25Unlimited
    Domain Valuation
    Price History90 daysUnlimited
    Expiry Tracking
    +
    + + {!isProOrHigher && ( +
    + + + Upgrade Now + +
    + )} +
    )}