YeboCars Services
Complete documentation of every service in the YeboCars API.
Service Overview
| Service | Purpose | Key Methods |
|---|---|---|
| CarService | Car CRUD operations | create, getCars, getById, update, delete |
| AIService | AI search and recommendations | searchNaturalLanguage, getRecommendations |
| VINService | VIN decoding | lookupVIN, decodeModelYear |
| BillingService | Stripe subscriptions | createCheckout, handleWebhook |
| DealerService | Dealer management | create, approve, getApplications |
| CountryService | Multi-country support | getCurrency, formatPrice |
| MessageService | User messaging | send, getConversations |
| NotificationService | Push notifications | create, 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
│
▼
REJECTEDResponse 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.
Natural Language Search
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()}`;
}
}