Files
puaros/packages/guardian/examples/good-architecture/domain/aggregates/User.ts
imfozilbek 03705b5264 feat(guardian): add guardian package - code quality analyzer
Add @puaros/guardian package v0.1.0 - code quality guardian for vibe coders and enterprise teams.

Features:
- Hardcode detection (magic numbers, magic strings)
- Circular dependency detection
- Naming convention enforcement (Clean Architecture)
- Architecture violation detection
- CLI tool with comprehensive reporting
- 159 tests with 80%+ coverage
- Smart suggestions for fixes
- Built for AI-assisted development

Built with Clean Architecture and DDD principles.
Works with Claude, GPT, Copilot, Cursor, and any AI coding assistant.
2025-11-24 02:54:39 +05:00

252 lines
5.8 KiB
TypeScript

import { BaseEntity } from "../../../../src/domain/entities/BaseEntity"
import { Email } from "../value-objects/Email"
import { UserId } from "../value-objects/UserId"
import { UserCreatedEvent } from "../events/UserCreatedEvent"
/**
* User Aggregate Root
*
* DDD Patterns:
* - Aggregate Root: consistency boundary
* - Rich Domain Model: contains business logic
* - Domain Events: publishes UserCreatedEvent
*
* SOLID Principles:
* - SRP: manages user identity and state
* - OCP: extensible through events
* - DIP: depends on abstractions (Email, UserId)
*
* Business Rules (Invariants):
* - Email must be unique (enforced by repository)
* - User must have valid email
* - Blocked users cannot be activated directly
* - Only active users can be blocked
*/
export class User extends BaseEntity {
private readonly _userId: UserId
private readonly _email: Email
private readonly _firstName: string
private readonly _lastName: string
private _isActive: boolean
private _isBlocked: boolean
private readonly _registeredAt: Date
private _lastLoginAt?: Date
private constructor(
userId: UserId,
email: Email,
firstName: string,
lastName: string,
isActive: boolean,
isBlocked: boolean,
registeredAt: Date,
lastLoginAt?: Date,
) {
super(userId.value)
this._userId = userId
this._email = email
this._firstName = firstName
this._lastName = lastName
this._isActive = isActive
this._isBlocked = isBlocked
this._registeredAt = registeredAt
this._lastLoginAt = lastLoginAt
this.validateInvariants()
}
/**
* Factory method: Create new user (business operation)
*
* DDD: Named constructor that represents business intent
* Clean Code: Intention-revealing method name
*/
public static create(email: Email, firstName: string, lastName: string): User {
const userId = UserId.create()
const now = new Date()
const user = new User(userId, email, firstName, lastName, true, false, now)
user.addDomainEvent(
new UserCreatedEvent({
userId: userId.value,
email: email.value,
registeredAt: now,
}),
)
return user
}
/**
* Factory method: Reconstitute from persistence
*
* DDD: Separate creation from reconstitution
* No events raised - already happened
*/
public static reconstitute(
userId: UserId,
email: Email,
firstName: string,
lastName: string,
isActive: boolean,
isBlocked: boolean,
registeredAt: Date,
lastLoginAt?: Date,
): User {
return new User(
userId,
email,
firstName,
lastName,
isActive,
isBlocked,
registeredAt,
lastLoginAt,
)
}
/**
* Business Operation: Activate user
*
* DDD: Business logic in domain
* SOLID SRP: User manages its own state
*/
public activate(): void {
if (this._isBlocked) {
throw new Error("Cannot activate blocked user. Unblock first.")
}
if (this._isActive) {
return
}
this._isActive = true
this.touch()
}
/**
* Business Operation: Deactivate user
*/
public deactivate(): void {
if (!this._isActive) {
return
}
this._isActive = false
this.touch()
}
/**
* Business Operation: Block user
*
* Business Rule: Only active users can be blocked
*/
public block(reason: string): void {
if (!this._isActive) {
throw new Error("Cannot block inactive user")
}
if (this._isBlocked) {
return
}
this._isBlocked = true
this._isActive = false
this.touch()
}
/**
* Business Operation: Unblock user
*/
public unblock(): void {
if (!this._isBlocked) {
return
}
this._isBlocked = false
this.touch()
}
/**
* Business Operation: Record login
*/
public recordLogin(): void {
if (!this._isActive) {
throw new Error("Inactive user cannot login")
}
if (this._isBlocked) {
throw new Error("Blocked user cannot login")
}
this._lastLoginAt = new Date()
this.touch()
}
/**
* Business Query: Check if user can login
*/
public canLogin(): boolean {
return this._isActive && !this._isBlocked
}
/**
* Getters: Read-only access to state
*/
public get userId(): UserId {
return this._userId
}
public get email(): Email {
return this._email
}
public get firstName(): string {
return this._firstName
}
public get lastName(): string {
return this._lastName
}
public get fullName(): string {
return `${this._firstName} ${this._lastName}`
}
public get isActive(): boolean {
return this._isActive
}
public get isBlocked(): boolean {
return this._isBlocked
}
public get registeredAt(): Date {
return this._registeredAt
}
public get lastLoginAt(): Date | undefined {
return this._lastLoginAt
}
/**
* Invariant validation
*
* DDD: Enforce business rules
*/
private validateInvariants(): void {
if (!this._firstName?.trim()) {
throw new Error("First name is required")
}
if (!this._lastName?.trim()) {
throw new Error("Last name is required")
}
if (this._isBlocked && this._isActive) {
throw new Error("Blocked user cannot be active")
}
}
}