feat: Add tier-based vehicle limit enforcement (refs #23)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 4m37s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 38s
Deploy to Staging / Verify Staging (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 4m37s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 38s
Deploy to Staging / Verify Staging (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
Backend: - Add VEHICLE_LIMITS configuration to feature-tiers.ts - Add getVehicleLimit, canAddVehicle helper functions - Implement transaction-based limit check with FOR UPDATE locking - Add VehicleLimitExceededError and 403 TIER_REQUIRED response - Add countByUserId to VehiclesRepository - Add comprehensive tests for all limit logic Frontend: - Add getResourceLimit, isAtResourceLimit to useTierAccess hook - Create VehicleLimitDialog component with mobile/desktop modes - Add useVehicleLimitCheck shared hook for limit state - Update VehiclesPage with limit checks and lock icon - Update VehiclesMobileScreen with limit checks - Add tests for VehicleLimitDialog Implements vehicle limits per tier (Free: 2, Pro: 5, Enterprise: unlimited) with race condition prevention and consistent UX across mobile/desktop. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import { VehiclesService } from '../domain/vehicles.service';
|
||||
import { VehiclesService, VehicleLimitExceededError } from '../domain/vehicles.service';
|
||||
import { VehiclesRepository } from '../data/vehicles.repository';
|
||||
import { pool } from '../../../core/config/database';
|
||||
import { logger } from '../../../core/logging/logger';
|
||||
@@ -21,7 +21,7 @@ export class VehiclesController {
|
||||
|
||||
constructor() {
|
||||
const repository = new VehiclesRepository(pool);
|
||||
this.vehiclesService = new VehiclesService(repository);
|
||||
this.vehiclesService = new VehiclesService(repository, pool);
|
||||
this.nhtsaClient = new NHTSAClient(pool);
|
||||
}
|
||||
|
||||
@@ -62,25 +62,40 @@ export class VehiclesController {
|
||||
|
||||
const userId = (request as any).user.sub;
|
||||
const vehicle = await this.vehiclesService.createVehicle(request.body, userId);
|
||||
|
||||
|
||||
return reply.code(201).send(vehicle);
|
||||
} catch (error: any) {
|
||||
logger.error('Error creating vehicle', { error, userId: (request as any).user?.sub });
|
||||
|
||||
|
||||
if (error instanceof VehicleLimitExceededError) {
|
||||
return reply.code(403).send({
|
||||
error: 'TIER_REQUIRED',
|
||||
requiredTier: error.tier === 'free' ? 'pro' : 'enterprise',
|
||||
currentTier: error.tier,
|
||||
feature: 'vehicle.addBeyondLimit',
|
||||
featureName: 'Additional Vehicles',
|
||||
upgradePrompt: error.upgradePrompt,
|
||||
context: {
|
||||
limit: error.limit,
|
||||
count: error.currentCount
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (error.message === 'Invalid VIN format') {
|
||||
return reply.code(400).send({
|
||||
error: 'Bad Request',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (error.message === 'Vehicle with this VIN already exists') {
|
||||
return reply.code(400).send({
|
||||
error: 'Bad Request',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return reply.code(500).send({
|
||||
error: 'Internal server error',
|
||||
message: 'Failed to create vehicle'
|
||||
|
||||
Reference in New Issue
Block a user