mirror of
https://github.com/samiyev/puaros.git
synced 2025-12-27 23:06:54 +05:00
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.
This commit is contained in:
@@ -0,0 +1,251 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user