feat(model): add user model

This commit is contained in:
Julien Oculi 2024-05-13 16:55:10 +02:00
parent 3f37648dfd
commit f7f3b48ad1

184
src/models/src/user.ts Normal file
View file

@ -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>,
): 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>(credential)
),
)
const groups = Object.freeze(
json.groups.map((group) => Ref.fromString<Group>(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<Group>[]
#posix?: Posix
#avatar: URL
#credentials: readonly Ref<Credential>[]
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<Omit<User, 'type' | 'uuid' | 'createdAt'>>): 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<Group>[]
posix: Posix | undefined
avatar: URL
credentials: readonly Ref<Credential>[]
}