feat: Add admin vehicle management and profile vehicles display (refs #11)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 4m34s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 37s
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

- Add GET /api/admin/stats endpoint for Total Vehicles widget
- Add GET /api/admin/users/:auth0Sub/vehicles endpoint for user vehicle list
- Update AdminUsersPage with Total Vehicles stat and expandable vehicle rows
- Add My Vehicles section to SettingsPage (desktop) and MobileSettingsScreen
- Update AdminUsersMobileScreen with stats header and vehicle expansion
- Add defense-in-depth admin checks and error handling
- Update admin README documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Eric Gullickson
2026-01-04 13:18:38 -06:00
parent 2ec208e25a
commit 4fc5b391e1
10 changed files with 639 additions and 67 deletions

View File

@@ -640,4 +640,72 @@ export class UserProfileRepository {
client.release();
}
}
/**
* Get total count of active vehicles across all users
* Used by admin stats endpoint for dashboard widget
*/
async getTotalVehicleCount(): Promise<number> {
const query = `
SELECT COUNT(*) as total
FROM vehicles
WHERE is_active = true
AND deleted_at IS NULL
`;
try {
const result = await this.pool.query(query);
return parseInt(result.rows[0]?.total || '0', 10);
} catch (error) {
logger.error('Error getting total vehicle count', { error });
throw error;
}
}
/**
* Get total count of active users
* Used by admin stats endpoint for dashboard widget
*/
async getTotalUserCount(): Promise<number> {
const query = `
SELECT COUNT(*) as total
FROM user_profiles
WHERE deactivated_at IS NULL
`;
try {
const result = await this.pool.query(query);
return parseInt(result.rows[0]?.total || '0', 10);
} catch (error) {
logger.error('Error getting total user count', { error });
throw error;
}
}
/**
* Get vehicles for a user (admin view)
* Returns only year, make, model for privacy
*/
async getUserVehiclesForAdmin(auth0Sub: string): Promise<Array<{ year: number; make: string; model: string }>> {
const query = `
SELECT year, make, model
FROM vehicles
WHERE user_id = $1
AND is_active = true
AND deleted_at IS NULL
ORDER BY year DESC, make ASC, model ASC
`;
try {
const result = await this.pool.query(query, [auth0Sub]);
return result.rows.map(row => ({
year: row.year,
make: row.make,
model: row.model,
}));
} catch (error) {
logger.error('Error getting user vehicles for admin', { error, auth0Sub });
throw error;
}
}
}