metis-desk/modules/identity/src/api/domain/user.aggregate.ts

98 lines
2.4 KiB
TypeScript
Raw Normal View History

import type { EmailAddress, Name, UniqueID } from "@repo/rdx-ddd";
import { AggregateRoot } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import { IdentityError } from "./identity-error";
2026-10-09 08:38:19 +00:00
import type { PasswordHash } from "./value-objects/password-hash.vo";
export type UserProps = Readonly<{
name: Name;
email: EmailAddress;
passwordHash: PasswordHash;
active: boolean;
isInitialAdmin: boolean;
authVersion: number;
}>;
2026-10-10 10:01:58 +00:00
export class User extends AggregateRoot<UserProps> {
static create(props: UserProps, id?: UniqueID): Result<User, IdentityError> {
if (
!Number.isSafeInteger(props.authVersion) ||
props.authVersion < 1 ||
props.name.toPrimitive().length > 100
) {
return Result.fail(new IdentityError("INVALID_REQUEST"));
}
if (props.isInitialAdmin && !props.active) {
return Result.fail(new IdentityError("ADMIN_PROTECTED"));
}
2026-10-10 10:01:58 +00:00
return Result.ok(new User(props, id));
}
2026-10-10 10:01:58 +00:00
static rehydrate(props: UserProps, id: UniqueID): User {
return new User(props, id);
}
2026-10-10 10:01:58 +00:00
get name(): Name {
return this.props.name;
}
2026-10-10 10:01:58 +00:00
get email(): EmailAddress {
return this.props.email;
}
2026-10-10 10:01:58 +00:00
get passwordHash(): PasswordHash {
return this.props.passwordHash;
}
2026-10-10 10:01:58 +00:00
get active(): boolean {
return this.props.active;
}
2026-10-10 10:01:58 +00:00
get isInitialAdmin(): boolean {
return this.props.isInitialAdmin;
}
2026-10-10 10:01:58 +00:00
get authVersion(): number {
return this.props.authVersion;
}
2026-10-10 10:01:58 +00:00
update(
values: Readonly<{ name: Name; email: EmailAddress; active: boolean }>
): Result<User, IdentityError> {
if (values.name.toPrimitive().length > 100) {
return Result.fail(new IdentityError("INVALID_REQUEST"));
}
if (this.isInitialAdmin && !values.active) {
return Result.fail(new IdentityError("ADMIN_PROTECTED"));
}
if (
this.name.equals(values.name) &&
this.email.equals(values.email) &&
this.active === values.active
) {
return Result.ok(this);
}
2026-10-10 10:01:58 +00:00
return Result.ok(
new User(
{
...this.props,
...values,
authVersion: this.authVersion + (values.active === this.active ? 0 : 1),
},
this.id
)
);
}
2026-10-10 10:01:58 +00:00
changePassword(passwordHash: PasswordHash): User {
if (this.passwordHash.equals(passwordHash)) {
return this;
}
2026-10-10 10:01:58 +00:00
return new User({ ...this.props, passwordHash, authVersion: this.authVersion + 1 }, this.id);
}
}