[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:
parent
99fadef0ba
commit
06c48678b1
@ -14,9 +14,11 @@ import {
|
|||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
Index,
|
Index,
|
||||||
ManyToOne,
|
ManyToOne,
|
||||||
|
ManyToMany,
|
||||||
JoinColumn,
|
JoinColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import { Tenant } from '../../core/entities/tenant.entity';
|
import { Tenant } from '../../core/entities/tenant.entity';
|
||||||
|
import { User } from './user.entity';
|
||||||
|
|
||||||
@Entity({ schema: 'auth', name: 'companies' })
|
@Entity({ schema: 'auth', name: 'companies' })
|
||||||
@Index('idx_companies_tenant_id', ['tenantId'])
|
@Index('idx_companies_tenant_id', ['tenantId'])
|
||||||
@ -59,6 +61,9 @@ export class Company {
|
|||||||
@JoinColumn({ name: 'parent_company_id' })
|
@JoinColumn({ name: 'parent_company_id' })
|
||||||
parentCompany: Company | null;
|
parentCompany: Company | null;
|
||||||
|
|
||||||
|
@ManyToMany(() => User, (user) => user.companies)
|
||||||
|
users: User[];
|
||||||
|
|
||||||
@CreateDateColumn({ name: 'created_at', type: 'timestamp' })
|
@CreateDateColumn({ name: 'created_at', type: 'timestamp' })
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ export { RefreshToken } from './refresh-token.entity';
|
|||||||
export { Role } from './role.entity';
|
export { Role } from './role.entity';
|
||||||
export { Permission } from './permission.entity';
|
export { Permission } from './permission.entity';
|
||||||
export { UserRole } from './user-role.entity';
|
export { UserRole } from './user-role.entity';
|
||||||
|
export { User, UserStatus } from './user.entity';
|
||||||
export { Session, SessionStatus } from './session.entity';
|
export { Session, SessionStatus } from './session.entity';
|
||||||
export { ApiKey } from './api-key.entity';
|
export { ApiKey } from './api-key.entity';
|
||||||
export { PasswordReset } from './password-reset.entity';
|
export { PasswordReset } from './password-reset.entity';
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import {
|
|||||||
ManyToOne,
|
ManyToOne,
|
||||||
JoinColumn,
|
JoinColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import { User } from '../../core/entities/user.entity';
|
import { User } from './user.entity';
|
||||||
|
|
||||||
@Entity({ schema: 'auth', name: 'password_resets' })
|
@Entity({ schema: 'auth', name: 'password_resets' })
|
||||||
@Index('idx_password_resets_user_id', ['userId'])
|
@Index('idx_password_resets_user_id', ['userId'])
|
||||||
@ -40,7 +40,7 @@ export class PasswordReset {
|
|||||||
@Column({ type: 'inet', nullable: true, name: 'ip_address' })
|
@Column({ type: 'inet', nullable: true, name: 'ip_address' })
|
||||||
ipAddress: string | null;
|
ipAddress: string | null;
|
||||||
|
|
||||||
@ManyToOne(() => User, { onDelete: 'CASCADE' })
|
@ManyToOne(() => User, (user) => user.passwordResets, { onDelete: 'CASCADE' })
|
||||||
@JoinColumn({ name: 'user_id' })
|
@JoinColumn({ name: 'user_id' })
|
||||||
user: User;
|
user: User;
|
||||||
|
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import {
|
|||||||
import { Permission } from './permission.entity';
|
import { Permission } from './permission.entity';
|
||||||
import { UserRole } from './user-role.entity';
|
import { UserRole } from './user-role.entity';
|
||||||
import { Tenant } from '../../core/entities/tenant.entity';
|
import { Tenant } from '../../core/entities/tenant.entity';
|
||||||
|
import { User } from './user.entity';
|
||||||
|
|
||||||
@Entity({ schema: 'auth', name: 'roles' })
|
@Entity({ schema: 'auth', name: 'roles' })
|
||||||
@Index('idx_roles_tenant_id', ['tenantId'])
|
@Index('idx_roles_tenant_id', ['tenantId'])
|
||||||
@ -69,6 +70,9 @@ export class Role {
|
|||||||
@OneToMany(() => UserRole, (userRole) => userRole.role)
|
@OneToMany(() => UserRole, (userRole) => userRole.role)
|
||||||
userRoles: UserRole[];
|
userRoles: UserRole[];
|
||||||
|
|
||||||
|
@ManyToMany(() => User, (user) => user.roles)
|
||||||
|
users: User[];
|
||||||
|
|
||||||
// Audit trail
|
// Audit trail
|
||||||
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import {
|
|||||||
ManyToOne,
|
ManyToOne,
|
||||||
JoinColumn,
|
JoinColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import { User } from '../../core/entities/user.entity';
|
import { User } from './user.entity';
|
||||||
|
|
||||||
export enum SessionStatus {
|
export enum SessionStatus {
|
||||||
ACTIVE = 'active',
|
ACTIVE = 'active',
|
||||||
@ -70,7 +70,7 @@ export class Session {
|
|||||||
@Column({ type: 'jsonb', nullable: true, name: 'device_info' })
|
@Column({ type: 'jsonb', nullable: true, name: 'device_info' })
|
||||||
deviceInfo: Record<string, any> | null;
|
deviceInfo: Record<string, any> | null;
|
||||||
|
|
||||||
@ManyToOne(() => User, { onDelete: 'CASCADE' })
|
@ManyToOne(() => User, (user) => user.sessions, { onDelete: 'CASCADE' })
|
||||||
@JoinColumn({ name: 'user_id' })
|
@JoinColumn({ name: 'user_id' })
|
||||||
user: User;
|
user: User;
|
||||||
|
|
||||||
|
|||||||
167
src/modules/auth/entities/user.entity.ts
Normal file
167
src/modules/auth/entities/user.entity.ts
Normal 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;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user