[ST-P0-001] feat: Add User entity and fix auth relationships

- Create user.entity.ts with all fields from erp-core
- Update index.ts to export User and UserStatus
- Add bidirectional ManyToMany relationship in role.entity.ts
- Add bidirectional ManyToMany relationship in company.entity.ts
- Fix session.entity.ts import to use local user.entity
- Fix password-reset.entity.ts import to use local user.entity

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Adrian Flores Cortes 2026-02-03 07:19:38 -06:00
parent 99fadef0ba
commit 06c48678b1
6 changed files with 181 additions and 4 deletions

View File

@ -14,9 +14,11 @@ import {
UpdateDateColumn,
Index,
ManyToOne,
ManyToMany,
JoinColumn,
} from 'typeorm';
import { Tenant } from '../../core/entities/tenant.entity';
import { User } from './user.entity';
@Entity({ schema: 'auth', name: 'companies' })
@Index('idx_companies_tenant_id', ['tenantId'])
@ -59,6 +61,9 @@ export class Company {
@JoinColumn({ name: 'parent_company_id' })
parentCompany: Company | null;
@ManyToMany(() => User, (user) => user.companies)
users: User[];
@CreateDateColumn({ name: 'created_at', type: 'timestamp' })
createdAt: Date;

View File

@ -6,6 +6,7 @@ export { RefreshToken } from './refresh-token.entity';
export { Role } from './role.entity';
export { Permission } from './permission.entity';
export { UserRole } from './user-role.entity';
export { User, UserStatus } from './user.entity';
export { Session, SessionStatus } from './session.entity';
export { ApiKey } from './api-key.entity';
export { PasswordReset } from './password-reset.entity';

View File

@ -15,7 +15,7 @@ import {
ManyToOne,
JoinColumn,
} from 'typeorm';
import { User } from '../../core/entities/user.entity';
import { User } from './user.entity';
@Entity({ schema: 'auth', name: 'password_resets' })
@Index('idx_password_resets_user_id', ['userId'])
@ -40,7 +40,7 @@ export class PasswordReset {
@Column({ type: 'inet', nullable: true, name: 'ip_address' })
ipAddress: string | null;
@ManyToOne(() => User, { onDelete: 'CASCADE' })
@ManyToOne(() => User, (user) => user.passwordResets, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'user_id' })
user: User;

View File

@ -22,6 +22,7 @@ import {
import { Permission } from './permission.entity';
import { UserRole } from './user-role.entity';
import { Tenant } from '../../core/entities/tenant.entity';
import { User } from './user.entity';
@Entity({ schema: 'auth', name: 'roles' })
@Index('idx_roles_tenant_id', ['tenantId'])
@ -69,6 +70,9 @@ export class Role {
@OneToMany(() => UserRole, (userRole) => userRole.role)
userRoles: UserRole[];
@ManyToMany(() => User, (user) => user.roles)
users: User[];
// Audit trail
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
createdAt: Date;

View File

@ -15,7 +15,7 @@ import {
ManyToOne,
JoinColumn,
} from 'typeorm';
import { User } from '../../core/entities/user.entity';
import { User } from './user.entity';
export enum SessionStatus {
ACTIVE = 'active',
@ -70,7 +70,7 @@ export class Session {
@Column({ type: 'jsonb', nullable: true, name: 'device_info' })
deviceInfo: Record<string, any> | null;
@ManyToOne(() => User, { onDelete: 'CASCADE' })
@ManyToOne(() => User, (user) => user.sessions, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'user_id' })
user: User;

View File

@ -0,0 +1,167 @@
/**
* User Entity
* Usuarios del sistema con autenticacion y permisos
* Compatible con erp-core user.entity
*
* @module Auth
*/
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
Index,
ManyToOne,
ManyToMany,
JoinColumn,
JoinTable,
OneToMany,
} from 'typeorm';
import { Tenant } from '../../core/entities/tenant.entity';
import { Role } from './role.entity';
import { Company } from './company.entity';
import { Session } from './session.entity';
import { PasswordReset } from './password-reset.entity';
export enum UserStatus {
ACTIVE = 'active',
INACTIVE = 'inactive',
SUSPENDED = 'suspended',
PENDING_VERIFICATION = 'pending_verification',
}
@Entity({ schema: 'auth', name: 'users' })
@Index('idx_users_tenant_id', ['tenantId'])
@Index('idx_users_email', ['email'])
@Index('idx_users_status', ['status'], { where: 'deleted_at IS NULL' })
@Index('idx_users_email_tenant', ['tenantId', 'email'])
@Index('idx_users_created_at', ['createdAt'])
export class User {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'uuid', nullable: false, name: 'tenant_id' })
tenantId: string;
@Column({ type: 'varchar', length: 255, nullable: false })
email: string;
@Column({ type: 'varchar', length: 255, nullable: false, name: 'password_hash' })
passwordHash: string;
@Column({ type: 'varchar', length: 255, nullable: false, name: 'full_name' })
fullName: string;
@Column({ type: 'varchar', length: 500, nullable: true, name: 'avatar_url' })
avatarUrl: string | null;
@Column({
type: 'enum',
enum: UserStatus,
default: UserStatus.ACTIVE,
nullable: false,
})
status: UserStatus;
@Column({ type: 'boolean', default: false, nullable: false, name: 'is_superuser' })
isSuperuser: boolean;
@Column({ name: 'is_superadmin', default: false })
isSuperadmin: boolean;
@Column({ name: 'mfa_enabled', default: false })
mfaEnabled: boolean;
@Column({ name: 'mfa_secret_encrypted', type: 'text', nullable: true })
mfaSecretEncrypted: string;
@Column({ name: 'mfa_backup_codes', type: 'text', array: true, nullable: true })
mfaBackupCodes: string[];
@Column({ name: 'oauth_provider', length: 50, nullable: true })
oauthProvider: string;
@Column({ name: 'oauth_provider_id', length: 255, nullable: true })
oauthProviderId: string;
@Column({
type: 'timestamp',
nullable: true,
name: 'email_verified_at',
})
emailVerifiedAt: Date | null;
@Column({ type: 'timestamp', nullable: true, name: 'last_login_at' })
lastLoginAt: Date | null;
@Column({ type: 'inet', nullable: true, name: 'last_login_ip' })
lastLoginIp: string | null;
@Column({ type: 'integer', default: 0, name: 'login_count' })
loginCount: number;
@Column({ type: 'varchar', length: 10, default: 'es' })
language: string;
@Column({ type: 'varchar', length: 50, default: 'America/Mexico_City' })
timezone: string;
@Column({ type: 'jsonb', default: {} })
settings: Record<string, any>;
// Relaciones
@ManyToOne(() => Tenant, (tenant) => tenant.users, {
onDelete: 'CASCADE',
})
@JoinColumn({ name: 'tenant_id' })
tenant: Tenant;
@ManyToMany(() => Role, (role) => role.users)
@JoinTable({
name: 'user_roles',
schema: 'auth',
joinColumn: { name: 'user_id', referencedColumnName: 'id' },
inverseJoinColumn: { name: 'role_id', referencedColumnName: 'id' },
})
roles: Role[];
@ManyToMany(() => Company, (company) => company.users)
@JoinTable({
name: 'user_companies',
schema: 'auth',
joinColumn: { name: 'user_id', referencedColumnName: 'id' },
inverseJoinColumn: { name: 'company_id', referencedColumnName: 'id' },
})
companies: Company[];
@OneToMany(() => Session, (session) => session.user)
sessions: Session[];
@OneToMany(() => PasswordReset, (passwordReset) => passwordReset.user)
passwordResets: PasswordReset[];
// Auditoria
@CreateDateColumn({ name: 'created_at', type: 'timestamp' })
createdAt: Date;
@Column({ type: 'uuid', nullable: true, name: 'created_by' })
createdBy: string | null;
@UpdateDateColumn({
name: 'updated_at',
type: 'timestamp',
nullable: true,
})
updatedAt: Date | null;
@Column({ type: 'uuid', nullable: true, name: 'updated_by' })
updatedBy: string | null;
@Column({ type: 'timestamp', nullable: true, name: 'deleted_at' })
deletedAt: Date | null;
@Column({ type: 'uuid', nullable: true, name: 'deleted_by' })
deletedBy: string | null;
}