import { IDeckRepository } from '../../../Domain/IRepository/IDeckRepository'; import { IUserRepository } from '../../../Domain/IRepository/IUserRepository'; import { IOrganizationRepository } from '../../../Domain/IRepository/IOrganizationRepository'; import { CreateDeckCommand } from './CreateDeckCommand'; import { ShortDeckDto } from '../../DTOs/DeckDto'; import { DeckAggregate, State, CType } from '../../../Domain/Deck/DeckAggregate'; import { UserState } from '../../../Domain/User/UserAggregate'; import { DeckMapper } from '../../DTOs/Mappers/DeckMapper'; import { AdminBypassService } from '../../Services/AdminBypassService'; import { logRequest } from '../../Services/Logger'; export class CreateDeckCommandHandler { constructor( private readonly deckRepo: IDeckRepository, private readonly userRepo: IUserRepository, private readonly orgRepo: IOrganizationRepository ) {} async execute(cmd: CreateDeckCommand): Promise { try { // 1. Get user details const user = await this.userRepo.findById(cmd.userid); if (!user) { throw new Error('User not found'); } // 2. ADMIN BYPASS - Skip all restrictions if (AdminBypassService.shouldBypassRestrictions(user.state)) { AdminBypassService.logAdminBypass( 'CREATE_DECK_BYPASS', user.id, 'new-deck', { deckName: cmd.name, deckType: cmd.type, cardCount: cmd.cards.length, ctype: cmd.ctype } ); return this.createDeck(cmd); } // 3. Check deck count limits for regular users const userDeckCount = await this.deckRepo.countActiveByUserId(cmd.userid); const maxDecks = user.state === UserState.VERIFIED_PREMIUM ? 12 : 8; if (userDeckCount >= maxDecks) { throw new Error(`Deck limit exceeded. Maximum ${maxDecks} decks allowed for your account type.`); } // 4. Organizational deck restrictions if (cmd.ctype === CType.ORGANIZATION) { // Only premium users can create organizational decks if (user.state !== UserState.VERIFIED_PREMIUM) { throw new Error('Only premium users can create organizational decks.'); } // User must belong to an organization if (!user.orgid) { throw new Error('You must be a member of an organization to create organizational decks.'); } // Check organization limits const org = await this.orgRepo.findById(user.orgid); if (!org) { throw new Error('Organization not found.'); } if (org.maxOrganizationalDecks === null) { throw new Error('Organization deck limit not configured. Contact administrator.'); } const userOrgDeckCount = await this.deckRepo.countOrganizationalByUserId(cmd.userid); if (userOrgDeckCount >= org.maxOrganizationalDecks) { throw new Error(`Organization deck limit exceeded. Maximum ${org.maxOrganizationalDecks} organizational decks allowed.`); } } // 5. Create deck with restrictions passed return this.createDeck(cmd); } catch (error) { if (error instanceof Error) { throw error; // Re-throw known errors with original message } throw new Error('Failed to create deck'); } } /** * Private method to create deck after all validations */ private async createDeck(cmd: CreateDeckCommand): Promise { const deck = new DeckAggregate(); deck.name = cmd.name; deck.type = cmd.type; deck.userid = cmd.userid; deck.cards = cmd.cards; deck.ctype = cmd.ctype ?? CType.PUBLIC; deck.state = State.ACTIVE; // Set organization reference for organizational decks if (cmd.ctype === CType.ORGANIZATION) { const user = await this.userRepo.findById(cmd.userid); if (user?.orgid) { const org = await this.orgRepo.findById(user.orgid); if (org) { deck.organization = org; } } } const created = await this.deckRepo.create(deck); logRequest('Deck created successfully', undefined, undefined, { deckId: created.id, userId: cmd.userid, deckName: cmd.name, deckType: cmd.type, ctype: cmd.ctype, cardCount: cmd.cards.length }); return DeckMapper.toShortDto(created); } }