fix: sync subscription tier on admin override (refs #58)
Add adminOverrideTier() method to SubscriptionsService that atomically updates both subscriptions.tier and user_profiles.subscription_tier using database transactions. Changes: - SubscriptionsRepository: Add updateTierByUserId() and createForAdminOverride() methods with transaction support - SubscriptionsService: Add adminOverrideTier() method with transaction wrapping for atomic dual-table updates - UsersController: Replace userProfileService.updateSubscriptionTier() with subscriptionsService.adminOverrideTier() This ensures admin tier changes properly sync to both database tables, fixing the Settings page "Current Plan" display mismatch. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,9 @@ import { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import { UserProfileService } from '../../user-profile/domain/user-profile.service';
|
||||
import { UserProfileRepository } from '../../user-profile/data/user-profile.repository';
|
||||
import { AdminRepository } from '../data/admin.repository';
|
||||
import { SubscriptionsService } from '../../subscriptions/domain/subscriptions.service';
|
||||
import { SubscriptionsRepository } from '../../subscriptions/data/subscriptions.repository';
|
||||
import { StripeClient } from '../../subscriptions/external/stripe/stripe.client';
|
||||
import { pool } from '../../../core/config/database';
|
||||
import { logger } from '../../../core/logging/logger';
|
||||
import {
|
||||
@@ -28,15 +31,22 @@ import { AdminService } from '../domain/admin.service';
|
||||
export class UsersController {
|
||||
private userProfileService: UserProfileService;
|
||||
private adminService: AdminService;
|
||||
private subscriptionsService: SubscriptionsService;
|
||||
private userProfileRepository: UserProfileRepository;
|
||||
private adminRepository: AdminRepository;
|
||||
|
||||
constructor() {
|
||||
this.userProfileRepository = new UserProfileRepository(pool);
|
||||
const adminRepository = new AdminRepository(pool);
|
||||
this.adminRepository = new AdminRepository(pool);
|
||||
const subscriptionsRepository = new SubscriptionsRepository(pool);
|
||||
const stripeClient = new StripeClient();
|
||||
|
||||
this.userProfileService = new UserProfileService(this.userProfileRepository);
|
||||
this.userProfileService.setAdminRepository(adminRepository);
|
||||
this.adminService = new AdminService(adminRepository);
|
||||
this.userProfileService.setAdminRepository(this.adminRepository);
|
||||
this.adminService = new AdminService(this.adminRepository);
|
||||
// Admin feature depends on Subscriptions for tier management
|
||||
// This is intentional - admin has oversight capabilities
|
||||
this.subscriptionsService = new SubscriptionsService(subscriptionsRepository, stripeClient, pool);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -226,6 +236,8 @@ export class UsersController {
|
||||
|
||||
/**
|
||||
* PATCH /api/admin/users/:auth0Sub/tier - Update subscription tier
|
||||
* Uses subscriptionsService.adminOverrideTier() to sync both subscriptions.tier
|
||||
* and user_profiles.subscription_tier atomically
|
||||
*/
|
||||
async updateTier(
|
||||
request: FastifyRequest<{ Params: UserAuth0SubInput; Body: UpdateTierInput }>,
|
||||
@@ -261,12 +273,39 @@ export class UsersController {
|
||||
const { auth0Sub } = paramsResult.data;
|
||||
const { subscriptionTier } = bodyResult.data;
|
||||
|
||||
const updatedUser = await this.userProfileService.updateSubscriptionTier(
|
||||
// Verify user exists before attempting tier change
|
||||
const currentUser = await this.userProfileService.getUserDetails(auth0Sub);
|
||||
if (!currentUser) {
|
||||
return reply.code(404).send({
|
||||
error: 'Not found',
|
||||
message: 'User not found',
|
||||
});
|
||||
}
|
||||
|
||||
const previousTier = currentUser.subscriptionTier;
|
||||
|
||||
// Use subscriptionsService to update both tables atomically
|
||||
await this.subscriptionsService.adminOverrideTier(auth0Sub, subscriptionTier);
|
||||
|
||||
// Log audit action
|
||||
await this.adminRepository.logAuditAction(
|
||||
actorId,
|
||||
'UPDATE_TIER',
|
||||
auth0Sub,
|
||||
subscriptionTier,
|
||||
actorId
|
||||
'user_profile',
|
||||
currentUser.id,
|
||||
{ previousTier, newTier: subscriptionTier }
|
||||
);
|
||||
|
||||
logger.info('User subscription tier updated via admin', {
|
||||
auth0Sub,
|
||||
previousTier,
|
||||
newTier: subscriptionTier,
|
||||
actorAuth0Sub: actorId,
|
||||
});
|
||||
|
||||
// Return updated user profile
|
||||
const updatedUser = await this.userProfileService.getUserDetails(auth0Sub);
|
||||
return reply.code(200).send(updatedUser);
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
|
||||
Reference in New Issue
Block a user