diff --git a/src/models/src/user.ts b/src/models/src/user.ts new file mode 100644 index 0000000..b18d4c9 --- /dev/null +++ b/src/models/src/user.ts @@ -0,0 +1,184 @@ +import { Login, MailAddress, Posix, ToJson } from '@/types.ts' +import { regex, toLogin } from '@/utils.ts' +import { Credential } from '@models/credential.ts' +import { Group } from '@models/group.ts' +import { Ressource } from '@models/ressource.ts' +import { Ref } from '@/src/models/utils/ref.ts' + +export class User extends Ressource { + static fromJSON( + json: ToJson, + ): User { + if (json.type !== 'user') { + throw new TypeError(`type is "${json.type}" but "user" is required`) + } + if (typeof json.lastname !== 'string') { + throw new TypeError( + `lastname type is "${typeof json.lastname}" but "string" is required`, + ) + } + if (typeof json.firstname !== 'string') { + throw new TypeError( + `firstname type is "${typeof json.firstname}" but "string" is required`, + ) + } + if (typeof json.login !== 'string' && /(\w|_)+\.(\w|_)+/.test(json.login)) { + throw new TypeError( + `login is "${json.login}" but "\${string}.\${string}" is required`, + ) + } + if (typeof json.mail !== 'string' && /\S+@\S+\.\S+/.test(json.mail)) { + throw new TypeError( + `mail is "${json.mail}" but "\${string}@\${string}.\${string}" is required`, + ) + } + + const credentials = Object.freeze( + json.credentials.map((credential) => + Ref.fromString(credential) + ), + ) + const groups = Object.freeze( + json.groups.map((group) => Ref.fromString(group)), + ) + const avatar = new URL(json.avatar) + const posix = json.posix ?? undefined + + return new User({ ...json, posix, credentials, groups, avatar }) + } + + static create({ + name, + ...props + }: Pick< + User, + | 'name' + | 'lastname' + | 'firstname' + | 'mail' + | 'groups' + | 'posix' + | 'avatar' + | 'credentials' + >): User { + const { uuid, createdAt, updatedAt } = super.create({ name }) + return new User({ name, uuid, createdAt, updatedAt, ...props }) + } + + #lastname: string + #firstname: string + #login: Login + #mail: MailAddress + #groups: readonly Ref[] + #posix?: Posix + #avatar: URL + #credentials: readonly Ref[] + + private constructor({ + lastname, + firstname, + mail, + groups, + posix, + avatar, + credentials, + ...ressource + }: Pick< + User, + | 'uuid' + | 'createdAt' + | 'updatedAt' + | 'name' + | 'lastname' + | 'firstname' + | 'mail' + | 'groups' + | 'posix' + | 'avatar' + | 'credentials' + >) { + super(ressource) + this.#lastname = lastname + this.#firstname = firstname + if (!regex.mailAddress) { + throw new TypeError(`"${mail}" is not a valid mail address`) + } + this.#mail = mail + this.#groups = Object.freeze(groups) + if (posix) { + if (!Number.isInteger(posix?.id) && posix.id < 0) { + throw new TypeError( + `posix.id must be a positive integer, received ${posix.id}`, + ) + } + } + this.#posix = posix + this.#avatar = avatar + this.#login = toLogin({ firstname, lastname }) + this.#credentials = Object.freeze(credentials) + } + + get type(): 'user' { + return 'user' + } + get firstname() { + return this.#firstname + } + get lastname() { + return this.#lastname + } + get login() { + return this.#login + } + get mail() { + return this.#mail + } + get groups() { + return this.#groups + } + get posix() { + return this.#posix + } + get avatar() { + return this.#avatar + } + get credentials() { + return this.#credentials + } + + update(props: Partial>): User { + const { updatedAt } = super.update(props) + const user = new User({ ...this, ...props, updatedAt }) + return user + } + + toJSON() { + return { + ...super.toJSON(), + lastname: this.lastname, + firstname: this.firstname, + login: this.login, + mail: this.mail, + groups: this.groups.map((group) => group.toJSON()), + posix: this.posix ?? null, + avatar: this.avatar.toJSON(), + credentials: this.credentials.map((credential) => credential.toJSON()), + } as const + } + + toString(): string { + return `User (${JSON.stringify(this)})` + } +} + +export interface User extends Ressource { + type: 'user' + lastname: string + firstname: string + login: Login + mail: MailAddress + groups: readonly Ref[] + posix: Posix | undefined + avatar: URL + credentials: readonly Ref[] +}