mirror of
https://github.com/samiyev/puaros.git
synced 2025-12-27 23:06:54 +05:00
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.
252 lines
5.8 KiB
TypeScript
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")
|
|
}
|
|
}
|
|
}
|