feat: add needs-vehicle-selection endpoint (refs #60)
- Add GET /api/subscriptions/needs-vehicle-selection endpoint
- Returns { needsSelection, vehicleCount, maxAllowed }
- Checks: free tier, >2 vehicles, no existing selections
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,7 +15,9 @@ import {
|
||||
BillingCycle,
|
||||
SubscriptionStatus,
|
||||
UpdateSubscriptionData,
|
||||
NeedsVehicleSelectionResponse,
|
||||
} from './subscriptions.types';
|
||||
import { VehiclesRepository } from '../../vehicles/data/vehicles.repository';
|
||||
|
||||
interface StripeWebhookEvent {
|
||||
id: string;
|
||||
@@ -27,6 +29,7 @@ interface StripeWebhookEvent {
|
||||
|
||||
export class SubscriptionsService {
|
||||
private userProfileRepository: UserProfileRepository;
|
||||
private vehiclesRepository: VehiclesRepository;
|
||||
|
||||
constructor(
|
||||
private repository: SubscriptionsRepository,
|
||||
@@ -34,6 +37,7 @@ export class SubscriptionsService {
|
||||
pool: Pool
|
||||
) {
|
||||
this.userProfileRepository = new UserProfileRepository(pool);
|
||||
this.vehiclesRepository = new VehiclesRepository(pool);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,6 +61,65 @@ export class SubscriptionsService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user needs to make a vehicle selection after auto-downgrade
|
||||
* Returns true if: user is on free tier, has >2 vehicles, and hasn't made selections
|
||||
*/
|
||||
async checkNeedsVehicleSelection(userId: string): Promise<NeedsVehicleSelectionResponse> {
|
||||
const FREE_TIER_VEHICLE_LIMIT = 2;
|
||||
|
||||
try {
|
||||
// Get current subscription
|
||||
const subscription = await this.repository.findByUserId(userId);
|
||||
|
||||
// No subscription or not on free tier = no selection needed
|
||||
if (!subscription || subscription.tier !== 'free') {
|
||||
return {
|
||||
needsSelection: false,
|
||||
vehicleCount: 0,
|
||||
maxAllowed: FREE_TIER_VEHICLE_LIMIT,
|
||||
};
|
||||
}
|
||||
|
||||
// Count user's active vehicles
|
||||
const vehicleCount = await this.vehiclesRepository.countByUserId(userId);
|
||||
|
||||
// If within limit, no selection needed
|
||||
if (vehicleCount <= FREE_TIER_VEHICLE_LIMIT) {
|
||||
return {
|
||||
needsSelection: false,
|
||||
vehicleCount,
|
||||
maxAllowed: FREE_TIER_VEHICLE_LIMIT,
|
||||
};
|
||||
}
|
||||
|
||||
// Check if user already has vehicle selections
|
||||
const existingSelections = await this.repository.findVehicleSelectionsByUserId(userId);
|
||||
|
||||
// If user already made selections, no selection needed
|
||||
if (existingSelections.length > 0) {
|
||||
return {
|
||||
needsSelection: false,
|
||||
vehicleCount,
|
||||
maxAllowed: FREE_TIER_VEHICLE_LIMIT,
|
||||
};
|
||||
}
|
||||
|
||||
// User is on free tier, has >2 vehicles, and hasn't made selections
|
||||
return {
|
||||
needsSelection: true,
|
||||
vehicleCount,
|
||||
maxAllowed: FREE_TIER_VEHICLE_LIMIT,
|
||||
};
|
||||
} catch (error: any) {
|
||||
logger.error('Failed to check needs vehicle selection', {
|
||||
userId,
|
||||
error: error.message,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new subscription (Stripe customer + initial free tier record)
|
||||
*/
|
||||
|
||||
@@ -131,3 +131,10 @@ export interface UpdateSubscriptionData {
|
||||
export interface UpdateDonationData {
|
||||
status?: DonationStatus;
|
||||
}
|
||||
|
||||
// Needs vehicle selection check response
|
||||
export interface NeedsVehicleSelectionResponse {
|
||||
needsSelection: boolean;
|
||||
vehicleCount: number;
|
||||
maxAllowed: number;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user