Skip to content

YeboCars Services

Complete documentation of every service in the YeboCars API.

Service Overview

ServicePurposeKey Methods
CarServiceCar CRUD operationscreate, getCars, getById, update, delete
AIServiceAI search and recommendationssearchNaturalLanguage, getRecommendations
VINServiceVIN decodinglookupVIN, decodeModelYear
BillingServiceStripe subscriptionscreateCheckout, handleWebhook
DealerServiceDealer managementcreate, approve, getApplications
CountryServiceMulti-country supportgetCurrency, formatPrice
MessageServiceUser messagingsend, getConversations
NotificationServicePush notificationscreate, markRead

CarService

Core service for car listing management.

Key Methods

typescript
class CarService {
  // Create a new car listing
  static async createCar(
    carData: ICarInput, 
    sellerId: string, 
    sellerType: SellerType
  ): Promise<ICarResponse> {
    // 1. Validate seller (dealer must be approved, user must be verified)
    // 2. Determine country from seller's profile
    // 3. Create car record
    // 4. Return formatted response with currency
  }
  
  // Get cars with filters and pagination
  static async getCars(query: ICarQuery): Promise<{
    cars: ICarResponse[];
    pagination: IPagination;
  }> {
    const where = { status: 'ACTIVE' };
    
    // Apply filters
    if (query.make) where.make = { contains: query.make, mode: 'insensitive' };
    if (query.model) where.model = { contains: query.model, mode: 'insensitive' };
    if (query.minPrice) where.price = { gte: query.minPrice };
    if (query.maxPrice) where.price = { ...where.price, lte: query.maxPrice };
    if (query.fuelType) where.fuelType = query.fuelType;
    if (query.transmission) where.transmission = query.transmission;
    if (query.bodyType) where.bodyType = query.bodyType;
    if (query.countryId) where.countryId = query.countryId;
    
    const [cars, total] = await Promise.all([
      prisma.car.findMany({ where, skip, take: limit }),
      prisma.car.count({ where })
    ]);
    
    return { cars: formattedCars, pagination };
  }
  
  // Get single car by ID
  static async getCarById(carId: string): Promise<ICarResponse> {
    const car = await prisma.car.findUnique({ where: { id: carId } });
    const sellerData = await this.getSellerData(car.sellerId, car.sellerType);
    return this.formatCarResponse(car, sellerData, car.sellerType);
  }
  
  // Update car listing
  static async updateCar(
    carId: string, 
    carData: Partial<ICarInput>, 
    userId: string, 
    userRole: string
  ): Promise<ICarResponse> {
    // Verify ownership or admin role
    // Update car record
  }
  
  // Delete car listing
  static async deleteCar(carId: string, userId: string, userRole: string): Promise<void> {
    // Verify ownership or admin role
    // Delete car record
  }
  
  // Get available makes
  static async getMakes(): Promise<string[]> {
    const cars = await prisma.car.findMany({
      select: { make: true },
      distinct: ['make'],
      orderBy: { make: 'asc' }
    });
    return cars.map(c => c.make);
  }
  
  // Get models for a make
  static async getModelsByMake(make: string): Promise<string[]> {
    const cars = await prisma.car.findMany({
      where: { make: { equals: make, mode: 'insensitive' } },
      select: { model: true },
      distinct: ['model']
    });
    return cars.map(c => c.model);
  }
  
  // Get available body types
  static async getBodyTypes(): Promise<string[]> {
    return ['Sedan', 'SUV', 'Hatchback', 'Coupe', 'Convertible', 
            'Wagon', 'Truck', 'Van', 'Minivan', 'Crossover'];
  }
  
  // Get common features
  static async getFeatures(): Promise<string[]> {
    return ['Air Conditioning', 'Bluetooth', 'Backup Camera', 'Sunroof',
            'GPS Navigation', 'Heated Seats', 'Leather Seats', 'Keyless Entry',
            'Remote Start', 'Parking Sensors', 'Cruise Control', 'Power Windows'];
  }
}

Car Status Flow

PENDING ──► ACTIVE ──► SOLD


          INACTIVE


          REJECTED

Response Formatting

typescript
private static async formatCarResponse(car: any, seller: any, sellerType: SellerType): Promise<ICarResponse> {
  let currency, formattedPrice;
  
  if (car.countryId) {
    currency = await CountryService.getCountryCurrency(car.countryId);
    formattedPrice = await CountryService.formatPriceForCountry(car.price, car.countryId);
  }
  
  return {
    _id: car.id,
    id: car.id,
    make: car.make,
    model: car.model,
    year: car.year,
    price: car.price,
    mileage: car.mileage,
    fuelType: car.fuelType,
    transmission: car.transmission,
    bodyType: car.bodyType,
    color: car.color,
    exteriorColor: car.exteriorColor,
    interiorColor: car.interiorColor,
    description: car.description,
    images: car.images,
    videos: car.videos,
    features: car.features,
    sellerId: car.sellerId,
    sellerType: car.sellerType,
    sellerName: seller?.name,
    sellerPhone: seller?.phone,
    condition: car.condition,
    status: car.status,
    currency,
    formattedPrice,
    countryId: car.countryId,
    createdAt: car.createdAt,
    updatedAt: car.updatedAt
  };
}

AIService

AI-powered search and car recommendations.

typescript
class AIService {
  // Search cars using natural language
  static async searchNaturalLanguage(query: string): Promise<{
    models: any[];
    availableCars: ICarMatch[];
    searchIntent: any;
  }> {
    // 1. Parse query for structured filters
    const searchIntent = this.parseNaturalLanguageQuery(query);
    
    // 2. Build structured filters
    const structuredFilters = this.buildStructuredFilters(searchIntent);
    
    // 3. Search for matching cars
    const carFilter = { status: 'active' };
    
    if (searchIntent.make) carFilter.make = searchIntent.make;
    if (searchIntent.priceRange) {
      carFilter.price = { 
        gte: searchIntent.priceRange.min, 
        lte: searchIntent.priceRange.max 
      };
    }
    if (searchIntent.fuelType) carFilter.fuelType = searchIntent.fuelType;
    if (searchIntent.bodyType) carFilter.bodyType = searchIntent.bodyType;
    
    const availableCars = await Car.findMany({ where: carFilter }).limit(20);
    
    // 4. Calculate match scores
    const carMatches = availableCars.map(car => ({
      car: this.formatCarForMatch(car),
      matchScore: this.calculateMatchScore(car, searchIntent),
      reasons: this.generateMatchReasons(car, searchIntent),
      lifestyleMatch: this.generateLifestyleMatch(car, query)
    }));
    
    return {
      models: [],
      availableCars: carMatches,
      searchIntent: {
        query,
        extractedCriteria: searchIntent,
        confidence: this.calculateSearchConfidence(searchIntent)
      }
    };
  }
  
  // Parse natural language into structured query
  private static parseNaturalLanguageQuery(query: string): any {
    const terms = {};
    const lowerQuery = query.toLowerCase();
    
    // Extract make
    const makes = ['toyota', 'honda', 'bmw', 'mercedes', 'audi', 'volkswagen', 
                   'ford', 'nissan', 'mazda', 'subaru', 'lexus', 'tesla'];
    makes.forEach(make => {
      if (lowerQuery.includes(make)) {
        terms.make = make.charAt(0).toUpperCase() + make.slice(1);
      }
    });
    
    // Extract body type
    if (lowerQuery.includes('suv') || lowerQuery.includes('crossover')) terms.bodyType = 'suv';
    if (lowerQuery.includes('sedan')) terms.bodyType = 'sedan';
    if (lowerQuery.includes('truck') || lowerQuery.includes('pickup')) terms.bodyType = 'truck';
    
    // Extract fuel type
    if (lowerQuery.includes('electric') || lowerQuery.includes('ev')) terms.fuelType = 'Electric';
    if (lowerQuery.includes('hybrid')) terms.fuelType = 'Hybrid';
    if (lowerQuery.includes('diesel')) terms.fuelType = 'Diesel';
    
    // Extract price range
    const priceMatch = lowerQuery.match(/under\s*\$?([\d,]+)/i);
    if (priceMatch) {
      terms.priceRange = { min: 0, max: parseInt(priceMatch[1].replace(/,/g, '')) };
    }
    
    // Extract market segment
    if (lowerQuery.includes('cheap') || lowerQuery.includes('budget')) terms.marketSegment = 'economy';
    if (lowerQuery.includes('luxury') || lowerQuery.includes('premium')) terms.marketSegment = 'luxury';
    
    // Extract usage patterns
    if (lowerQuery.includes('family')) terms.targetDemographic = 'families';
    if (lowerQuery.includes('commut')) terms.targetDemographic = 'commuters';
    
    return terms;
  }
}

Lifestyle Recommendations

typescript
// Get recommendations based on lifestyle profile
static async getRecommendations(profile: ILifestyleProfile): Promise<ICarMatch[]> {
  const filter = { status: 'active' };
  
  // Budget matching
  if (profile.budget) {
    filter.price = { gte: profile.budget.min, lte: profile.budget.max };
  }
  
  // Fuel preference
  if (profile.fuelPreference) {
    filter.fuelType = profile.fuelPreference;
  }
  
  // Family size → body type
  if (profile.familySize > 4) {
    filter.bodyType = { in: ['SUV', 'MPV', 'Van'] };
  } else if (profile.familySize <= 2) {
    filter.bodyType = { in: ['Sedan', 'Hatchback', 'Coupe'] };
  }
  
  const cars = await Car.findMany({ where: filter }).limit(10);
  
  return cars.map(car => ({
    car: this.formatCarForMatch(car),
    matchScore: this.calculateLifestyleMatch(car, profile),
    reasons: this.generateLifestyleReasons(car, profile),
    lifestyleMatch: this.generateLifestyleDescription(car, profile)
  }));
}

// Get pricing insights
static async getPricingInsights(carId: string): Promise<IPricingInsights> {
  const car = await Car.findUnique({ where: { id: carId } });
  
  // Find comparable cars
  const comparables = await Car.find({
    make: car.make,
    model: car.model,
    year: { gte: car.year - 2, lte: car.year + 2 },
    status: 'active'
  }).limit(5);
  
  const averagePrice = comparables.reduce((sum, c) => sum + c.price, 0) / comparables.length;
  
  let marketPosition, suggestion;
  if (car.price > averagePrice * 1.1) {
    marketPosition = 'Above Market';
    suggestion = 'Consider reducing price';
  } else if (car.price < averagePrice * 0.9) {
    marketPosition = 'Below Market';
    suggestion = 'Price is competitive';
  } else {
    marketPosition = 'At Market';
    suggestion = 'Price is well-positioned';
  }
  
  return { marketPosition, suggestion, comparables };
}

VINService

Vehicle Identification Number decoding.

typescript
class VINService {
  static async lookupVIN(vin: string): Promise<IVINResponse> {
    // Validate VIN format
    if (!this.isValidVIN(vin)) {
      return { success: false, error: 'Invalid VIN format' };
    }
    
    // Decode VIN
    const vinData = this.decodeVIN(vin);
    
    return {
      success: true,
      data: vinData,
      source: 'VIN Service'
    };
  }
  
  private static isValidVIN(vin: string): boolean {
    // VIN must be 17 characters
    if (vin.length !== 17) return false;
    
    // VINs don't contain I, O, or Q
    if (/[IOQ]/.test(vin.toUpperCase())) return false;
    
    // Must be alphanumeric
    if (!/^[A-HJ-NPR-Z0-9]{17}$/i.test(vin)) return false;
    
    return true;
  }
  
  private static decodeModelYear(yearCode: string): number {
    const yearMap = {
      'A': 2010, 'B': 2011, 'C': 2012, 'D': 2013, 'E': 2014,
      'F': 2015, 'G': 2016, 'H': 2017, 'J': 2018, 'K': 2019,
      'L': 2020, 'M': 2021, 'N': 2022, 'P': 2023, 'R': 2024
    };
    return yearMap[yearCode.toUpperCase()] || new Date().getFullYear();
  }
  
  private static getManufacturerData(wmi: string): any {
    const manufacturers = {
      '1HG': { make: 'Honda', model: 'Civic', bodyType: 'Sedan' },
      '1FA': { make: 'Ford', model: 'Focus', bodyType: 'Hatchback' },
      '4T1': { make: 'Toyota', model: 'Camry', bodyType: 'Sedan' },
      'WBA': { make: 'BMW', model: '3 Series', bodyType: 'Sedan' },
      'JTD': { make: 'Toyota', model: 'RAV4', bodyType: 'SUV' }
    };
    return manufacturers[wmi] || { make: 'Unknown', model: 'Unknown' };
  }
}

BillingService

Stripe integration for dealer subscriptions.

typescript
class BillingService {
  private static PLANS = [
    {
      id: 'BASIC',
      name: 'Basic',
      priceUsdCents: 1000,  // $10/month
      description: '10 car listings, basic analytics',
      features: ['10 car listings', 'Basic analytics', 'Standard support'],
      listingLimit: 10,
      durationDays: 30
    },
    {
      id: 'DEALER',
      name: 'Dealer',
      priceUsdCents: 2500,  // $25/month
      description: '50 car listings, lead management',
      features: ['50 car listings', 'Lead management', 'Featured slots', 'Priority support'],
      listingLimit: 50,
      durationDays: 30
    },
    {
      id: 'PRO',
      name: 'Pro',
      priceUsdCents: 6000,  // $60/month
      description: 'Unlimited listings, priority placement',
      features: ['Unlimited listings', 'Priority placement', 'Advanced analytics', 'Account manager'],
      listingLimit: -1,
      durationDays: 30
    }
  ];
  
  // Get plans with localized pricing
  static getPlans(countryCode?: string) {
    const currency = getCurrencyForCountry(countryCode);
    
    return this.PLANS.map(plan => ({
      ...plan,
      displayCurrency: currency.code,
      displaySymbol: currency.symbol,
      displayAmount: convertFromUSD(plan.priceUsdCents, currency),
      displayFormatted: formatLocalAmount(amount, currency)
    }));
  }
  
  // Create Stripe checkout session
  static async createCheckout(opts: {
    dealerId: string;
    email: string;
    planId: string;
    successUrl: string;
    cancelUrl: string;
  }) {
    const plan = this.PLANS.find(p => p.id === opts.planId);
    
    const session = await stripe.checkout.sessions.create({
      payment_method_types: ['card'],
      customer_email: opts.email,
      line_items: [{
        price_data: {
          currency: 'usd',
          product_data: { name: `YeboCars ${plan.name} Plan` },
          unit_amount: plan.priceUsdCents
        },
        quantity: 1
      }],
      mode: 'payment',
      success_url: opts.successUrl,
      cancel_url: opts.cancelUrl,
      metadata: { dealerId: opts.dealerId, planId: opts.planId }
    });
    
    return { sessionId: session.id, url: session.url };
  }
  
  // Handle Stripe webhook
  static async handleWebhookEvent(payload: Buffer, signature: string) {
    const event = stripe.webhooks.constructEvent(
      payload, signature, process.env.STRIPE_WEBHOOK_SECRET
    );
    
    if (event.type === 'checkout.session.completed') {
      const session = event.data.object;
      const { dealerId, planId } = session.metadata;
      
      const plan = this.PLANS.find(p => p.id === planId);
      const expiry = new Date();
      expiry.setDate(expiry.getDate() + plan.durationDays);
      
      await prisma.dealer.update({
        where: { id: dealerId },
        data: {
          plan: planId,
          planExpiry: expiry,
          stripeCustomerId: session.customer
        }
      });
    }
    
    return { received: true, type: event.type };
  }
}

CountryService

Multi-country currency and formatting support.

typescript
class CountryService {
  // Get currency for a country
  static async getCountryCurrency(countryId: string): Promise<Currency> {
    const country = await prisma.country.findUnique({
      where: { id: countryId },
      select: { currency: true }
    });
    return country?.currency;
  }
  
  // Format price for country
  static async formatPriceForCountry(price: number, countryId: string): Promise<string> {
    const currency = await this.getCountryCurrency(countryId);
    return `${currency.symbol}${price.toLocaleString()}`;
  }
}

One chat. Everything done.