Compare commits
23 commits
dda72bf67d
...
28a517323b
Author | SHA1 | Date | |
---|---|---|---|
Julien Oculi | 28a517323b | ||
Julien Oculi | 873f4d23dc | ||
Julien Oculi | 3e3fd59fe7 | ||
Julien Oculi | 5ecebbec14 | ||
Julien Oculi | 1fb1f1ce15 | ||
Julien Oculi | aefdacbf59 | ||
Julien Oculi | 2028c0134a | ||
Julien Oculi | 3b378b0968 | ||
Julien Oculi | 8214dcd625 | ||
Julien Oculi | fa88ab2f68 | ||
Julien Oculi | 626bc3c0f6 | ||
Julien Oculi | ba678692a5 | ||
Julien Oculi | 9520d222ae | ||
Julien Oculi | a5b31dd967 | ||
Julien Oculi | f0899797d8 | ||
Julien Oculi | 6d08698d10 | ||
Julien Oculi | fda7b03e59 | ||
Julien Oculi | 42006cc18e | ||
Julien Oculi | 33868a3995 | ||
Julien Oculi | cbd14996d4 | ||
Julien Oculi | 92a6ae01a3 | ||
Julien Oculi | 98444ea685 | ||
Julien Oculi | 3c773bca6b |
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,6 +1,8 @@
|
||||||
# Directories
|
# Directories
|
||||||
docs
|
docs
|
||||||
|
tmp
|
||||||
|
temp
|
||||||
|
|
||||||
# Files
|
# Files
|
||||||
.env
|
.env
|
||||||
*.sqlite
|
*.sqlite*
|
||||||
|
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"conventionalCommits.scopes": ["model", "chore", "db"]
|
||||||
|
}
|
|
@ -1,5 +1,10 @@
|
||||||
{
|
{
|
||||||
"name": "@cohabit/ressources",
|
"name": "@cohabit/ressources",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"exports": {
|
||||||
|
".": "./mod.ts",
|
||||||
|
"./models": "./src/models/mod.ts"
|
||||||
|
},
|
||||||
"tasks": {
|
"tasks": {
|
||||||
"start": "deno run --unstable-kv ./mod.ts"
|
"start": "deno run --unstable-kv ./mod.ts"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,4 +1,12 @@
|
||||||
import { Credential, Group, Machine, Ressource, Service, User } from '@models'
|
import {
|
||||||
|
Credential,
|
||||||
|
Group,
|
||||||
|
Machine,
|
||||||
|
type Ressource,
|
||||||
|
Service,
|
||||||
|
User,
|
||||||
|
} from '@models'
|
||||||
|
import type { CredentialCategory } from '@models/credential.ts'
|
||||||
|
|
||||||
//!TODO link ressources (get, list)
|
//!TODO link ressources (get, list)
|
||||||
//!TODO Purge unused ressources (delete)
|
//!TODO Purge unused ressources (delete)
|
||||||
|
@ -26,7 +34,10 @@ export class Db {
|
||||||
|
|
||||||
get ressource() {
|
get ressource() {
|
||||||
return {
|
return {
|
||||||
credential: this.#ressourceStorage<Credential>('credential', Credential),
|
credential: this.#ressourceStorage<Credential<CredentialCategory>>(
|
||||||
|
'credential',
|
||||||
|
Credential,
|
||||||
|
),
|
||||||
group: this.#ressourceStorage<Group>('group', Group),
|
group: this.#ressourceStorage<Group>('group', Group),
|
||||||
machine: this.#ressourceStorage<Machine>('machine', Machine),
|
machine: this.#ressourceStorage<Machine>('machine', Machine),
|
||||||
service: this.#ressourceStorage<Service>('service', Service),
|
service: this.#ressourceStorage<Service>('service', Service),
|
||||||
|
@ -44,8 +55,9 @@ export class Db {
|
||||||
set: (ressources: T[]) => this.#set<T>(type, ressources),
|
set: (ressources: T[]) => this.#set<T>(type, ressources),
|
||||||
delete: (ressources: Pick<T, 'uuid'>[]) =>
|
delete: (ressources: Pick<T, 'uuid'>[]) =>
|
||||||
this.#delete<T>(type, ressources),
|
this.#delete<T>(type, ressources),
|
||||||
list: (filter: (ressource: T) => boolean = () => true) =>
|
list: (
|
||||||
this.#list<T>(type, Builder, filter),
|
filter: (ressource: T) => boolean | Promise<boolean> = () => true,
|
||||||
|
) => this.#list<T>(type, Builder, filter),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +106,7 @@ export class Db {
|
||||||
async *#list<T extends Ressource>(
|
async *#list<T extends Ressource>(
|
||||||
type: RessourceType<T>,
|
type: RessourceType<T>,
|
||||||
Builder: RessourceBuilder<T>,
|
Builder: RessourceBuilder<T>,
|
||||||
filter: (entry: T) => boolean,
|
filter: (entry: T) => boolean | Promise<boolean>,
|
||||||
): AsyncGenerator<T, void, void> {
|
): AsyncGenerator<T, void, void> {
|
||||||
const list = this.#kv.list<RessourceJson<T>>({
|
const list = this.#kv.list<RessourceJson<T>>({
|
||||||
prefix: [this.prefix.ressource, type],
|
prefix: [this.prefix.ressource, type],
|
||||||
|
@ -103,22 +115,23 @@ export class Db {
|
||||||
const value = entry.value
|
const value = entry.value
|
||||||
//@ts-expect-error Type union of Ressource types for Builder
|
//@ts-expect-error Type union of Ressource types for Builder
|
||||||
const ressource = Builder.fromJSON(value) as T
|
const ressource = Builder.fromJSON(value) as T
|
||||||
if (filter(ressource)) {
|
if (await filter(ressource)) {
|
||||||
yield ressource
|
yield ressource
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type RessourceType<T extends Ressource> = T extends Credential ? 'credential'
|
type RessourceType<T extends Ressource> = T extends
|
||||||
|
Credential<CredentialCategory> ? 'credential'
|
||||||
: T extends Group ? 'group'
|
: T extends Group ? 'group'
|
||||||
: T extends Machine ? 'machine'
|
: T extends Machine ? 'machine'
|
||||||
: T extends Service ? 'service'
|
: T extends Service ? 'service'
|
||||||
: T extends User ? 'user'
|
: T extends User ? 'user'
|
||||||
: never
|
: never
|
||||||
|
|
||||||
type RessourceBuilder<T extends Ressource> = T extends Credential
|
type RessourceBuilder<T extends Ressource> = T extends
|
||||||
? typeof Credential
|
Credential<CredentialCategory> ? typeof Credential
|
||||||
: T extends Group ? typeof Group
|
: T extends Group ? typeof Group
|
||||||
: T extends Machine ? typeof Machine
|
: T extends Machine ? typeof Machine
|
||||||
: T extends Service ? typeof Service
|
: T extends Service ? typeof Service
|
||||||
|
|
|
@ -1,27 +1,54 @@
|
||||||
import { ToJson } from '@/types.ts'
|
import type { Base64String, ToJson, UUID } from '@/types.ts'
|
||||||
import { Ressource } from '@models/ressource.ts'
|
import { Ressource } from '@models/ressource.ts'
|
||||||
|
import type { Ref } from '@models'
|
||||||
|
import { Avatar } from '@/src/models/utils/avatar.ts'
|
||||||
|
|
||||||
export class Credential extends Ressource {
|
export class Credential<T extends CredentialCategory> extends Ressource {
|
||||||
static fromJSON(
|
static fromJSON<T extends CredentialCategory>(
|
||||||
json: ToJson<Credential>,
|
json: ToJson<Credential<T>>,
|
||||||
): Credential {
|
): Credential<T> {
|
||||||
return new Credential(json)
|
return new Credential(json)
|
||||||
}
|
}
|
||||||
|
|
||||||
static create(
|
static create<T extends CredentialCategory>(
|
||||||
{ category, store, name }: Pick<Credential, 'category' | 'store' | 'name'>,
|
{ category, store, name, avatar }: Pick<
|
||||||
): Credential {
|
Credential<T>,
|
||||||
const { uuid, createdAt, updatedAt } = super.create({ name })
|
'category' | 'store' | 'name' | 'avatar'
|
||||||
return new Credential({ uuid, createdAt, updatedAt, name, category, store })
|
>,
|
||||||
|
): Credential<T> {
|
||||||
|
const { uuid, createdAt, updatedAt } = super.create({ name, avatar })
|
||||||
|
return new Credential({
|
||||||
|
uuid,
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
name,
|
||||||
|
category,
|
||||||
|
store,
|
||||||
|
avatar,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#category: 'password' | 'ssh' | 'passkey'
|
static load<T extends CredentialCategory>({
|
||||||
#store: CredentialCategory<Credential['category']>
|
category,
|
||||||
|
store,
|
||||||
|
name,
|
||||||
|
avatar = Avatar.fromEmoji('🔑'),
|
||||||
|
}: {
|
||||||
|
name: Credential<T>['name']
|
||||||
|
category: T
|
||||||
|
store: Credential<T>['store']
|
||||||
|
avatar?: Credential<T>['avatar']
|
||||||
|
}): Credential<T> {
|
||||||
|
return this.create({ category, store, name, avatar })
|
||||||
|
}
|
||||||
|
|
||||||
|
#category: T
|
||||||
|
#store: Readonly<CredentialStore<T>>
|
||||||
|
|
||||||
private constructor(
|
private constructor(
|
||||||
{ category, store, ...props }:
|
{ category, store, ...props }:
|
||||||
& Pick<Credential, 'category' | 'store'>
|
& Pick<Credential<T>, 'category' | 'store'>
|
||||||
& Pick<Ressource, 'name' | 'uuid' | 'createdAt' | 'updatedAt'>,
|
& Pick<Ressource, 'name' | 'uuid' | 'avatar' | 'createdAt' | 'updatedAt'>,
|
||||||
) {
|
) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
|
@ -43,14 +70,14 @@ export class Credential extends Ressource {
|
||||||
}
|
}
|
||||||
|
|
||||||
get store() {
|
get store() {
|
||||||
return Object.freeze(this.#store)
|
return this.#store
|
||||||
}
|
}
|
||||||
|
|
||||||
update(
|
update<T extends CredentialCategory>(
|
||||||
props: Partial<Omit<Credential, 'type' | 'uuid' | 'createdAt'>>,
|
props: Partial<Omit<Credential<T>, 'type' | 'uuid' | 'createdAt'>>,
|
||||||
): Credential {
|
): Credential<T> {
|
||||||
const { updatedAt } = super.update(props)
|
const { updatedAt } = super.update(props)
|
||||||
const credential = new Credential({ ...this, ...props, updatedAt })
|
const credential = new Credential<T>({ ...this, ...props, updatedAt })
|
||||||
return credential
|
return credential
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,40 +93,55 @@ export class Credential extends Ressource {
|
||||||
toString(): string {
|
toString(): string {
|
||||||
return `Credential (${JSON.stringify(this)})`
|
return `Credential (${JSON.stringify(this)})`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toRef(): Ref<Credential<T>> {
|
||||||
|
return super.toRef() as Ref<Credential<T>>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Credential extends Ressource {
|
export interface Credential<T extends CredentialCategory> extends Ressource {
|
||||||
type: 'credential'
|
type: 'credential'
|
||||||
category: 'password' | 'ssh' | 'passkey'
|
category: T
|
||||||
store: Readonly<CredentialCategory<Credential['category']>>
|
store: Readonly<CredentialStore<T>>
|
||||||
}
|
}
|
||||||
|
|
||||||
type CredentialCategory<T extends 'password' | 'ssh' | 'passkey'> = T extends
|
export type CredentialCategory = 'password' | 'ssh' | 'passkey'
|
||||||
'password' ? {
|
|
||||||
|
export type CredentialStore<T extends CredentialCategory> = T extends 'password'
|
||||||
|
? {
|
||||||
store: {
|
store: {
|
||||||
hash: string //hex or b64 of Uint8Array
|
hash: Base64String
|
||||||
alg: string
|
alg: string
|
||||||
salt: string //hex or b64 of Uint8Array
|
salt: Base64String
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
: T extends 'ssh' ? {
|
: T extends 'ssh' ? {
|
||||||
store: {
|
store: {
|
||||||
publicKey: string
|
publicKey: Base64String
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
: T extends 'passkey' ? {
|
: T extends 'passkey' ? {
|
||||||
store: Record<string, unknown>
|
store: Passkey
|
||||||
}
|
}
|
||||||
: never
|
: never
|
||||||
|
|
||||||
/*
|
/** Passkey store */
|
||||||
PassKey store:
|
export type Passkey = {
|
||||||
{
|
/** User UUID */
|
||||||
publicKey: Uint8Array
|
user: UUID
|
||||||
keyId: string
|
/** WebAuthn registration key id */
|
||||||
transport: string
|
webAuthnUserID: string
|
||||||
counter: number
|
/** Passkey credential unique id */
|
||||||
|
id: string
|
||||||
|
/** Passkey user public key */
|
||||||
|
publicKey: Base64String
|
||||||
|
/** Number of times the authenticator has been used */
|
||||||
|
counter: number
|
||||||
|
/** Whether the passkey is single-device or multi-device */
|
||||||
|
deviceType: 'singleDevice' | 'multiDevice'
|
||||||
|
/** Whether the passkey has been backed up in some way */
|
||||||
|
backedUp: boolean
|
||||||
|
/** Passkey physical transport layer */
|
||||||
|
transports?:
|
||||||
|
('ble' | 'cable' | 'hybrid' | 'internal' | 'nfc' | 'smart-card' | 'usb')[]
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
//new Uint8Array(Object.values(JSON.parse(JSON.stringify(new Uint8Array([1, 2, 3])))))
|
|
||||||
|
|
|
@ -1,33 +1,68 @@
|
||||||
import { Posix, ToJson, UUID } from '@/types.ts'
|
import type { Posix, ToJson, UUID } from '@/types.ts'
|
||||||
import { Ressource } from '@models/ressource.ts'
|
import { Ressource } from '@models/ressource.ts'
|
||||||
|
import { Ref } from '@models'
|
||||||
|
import { Avatar } from '@/src/models/utils/avatar.ts'
|
||||||
|
|
||||||
export class Group extends Ressource {
|
export class Group extends Ressource {
|
||||||
static fromJSON(
|
static fromJSON(
|
||||||
{ posix, ...json }: ToJson<Group>,
|
{ posix, groups, ...json }: ToJson<Group>,
|
||||||
): Group {
|
): Group {
|
||||||
return new Group({ ...json, posix: posix ?? undefined })
|
return new Group({
|
||||||
|
...json,
|
||||||
|
posix: posix ?? undefined,
|
||||||
|
groups: groups.map((group) => Ref.fromString<Group>(group)),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static create(
|
static create(
|
||||||
{ posix, permissions, name }: Pick<Group, 'posix' | 'permissions' | 'name'>,
|
{ posix, permissions, name, avatar, groups }: Pick<
|
||||||
|
Group,
|
||||||
|
'posix' | 'permissions' | 'name' | 'avatar' | 'groups'
|
||||||
|
>,
|
||||||
): Group {
|
): Group {
|
||||||
const { uuid, createdAt, updatedAt } = super.create({ name })
|
const { uuid, createdAt, updatedAt } = super.create({ name, avatar })
|
||||||
return new Group({ uuid, createdAt, updatedAt, name, posix, permissions })
|
return new Group({
|
||||||
|
uuid,
|
||||||
|
avatar,
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
name,
|
||||||
|
posix,
|
||||||
|
permissions,
|
||||||
|
groups,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
static load({
|
||||||
|
name,
|
||||||
|
groups = [],
|
||||||
|
permissions = {},
|
||||||
|
posix,
|
||||||
|
avatar = Avatar.fromEmoji('👥'),
|
||||||
|
}: {
|
||||||
|
name: Group['name']
|
||||||
|
permissions?: Group['permissions']
|
||||||
|
posix?: Group['posix']
|
||||||
|
avatar?: Group['avatar']
|
||||||
|
groups?: Group['groups']
|
||||||
|
}): Group {
|
||||||
|
return this.create({ name, groups, permissions, posix, avatar })
|
||||||
}
|
}
|
||||||
|
|
||||||
#posix?: Posix
|
#posix?: Posix
|
||||||
#permissions: {
|
#permissions: Readonly<{
|
||||||
[serviceOrMachine: UUID]: {
|
[serviceOrMachine: UUID]: {
|
||||||
read: boolean
|
read: boolean
|
||||||
write: boolean
|
write: boolean
|
||||||
execute: boolean
|
execute: boolean
|
||||||
}
|
}
|
||||||
} = {}
|
}>
|
||||||
|
#groups: readonly Ref<Group>[]
|
||||||
|
|
||||||
private constructor(
|
private constructor(
|
||||||
{ posix, permissions, ...props }:
|
{ posix, permissions, groups, ...props }:
|
||||||
& Pick<Group, 'posix' | 'permissions'>
|
& Pick<Group, 'posix' | 'permissions' | 'groups'>
|
||||||
& Pick<Ressource, 'name' | 'uuid' | 'createdAt' | 'updatedAt'>,
|
& Pick<Ressource, 'name' | 'avatar' | 'uuid' | 'createdAt' | 'updatedAt'>,
|
||||||
) {
|
) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
|
@ -50,7 +85,8 @@ export class Group extends Ressource {
|
||||||
this.#posix = posix
|
this.#posix = posix
|
||||||
}
|
}
|
||||||
|
|
||||||
this.permissions = Object.freeze(permissions)
|
this.#permissions = Object.freeze(permissions)
|
||||||
|
this.#groups = Object.freeze(groups)
|
||||||
}
|
}
|
||||||
|
|
||||||
get type(): 'group' {
|
get type(): 'group' {
|
||||||
|
@ -62,7 +98,11 @@ export class Group extends Ressource {
|
||||||
}
|
}
|
||||||
|
|
||||||
get permissions() {
|
get permissions() {
|
||||||
return Object.freeze(this.#permissions)
|
return this.#permissions
|
||||||
|
}
|
||||||
|
|
||||||
|
get groups() {
|
||||||
|
return this.#groups
|
||||||
}
|
}
|
||||||
|
|
||||||
update(props: Partial<Omit<Group, 'type' | 'uuid' | 'createdAt'>>): Group {
|
update(props: Partial<Omit<Group, 'type' | 'uuid' | 'createdAt'>>): Group {
|
||||||
|
@ -77,17 +117,23 @@ export class Group extends Ressource {
|
||||||
type: this.type,
|
type: this.type,
|
||||||
posix: this.posix ?? null,
|
posix: this.posix ?? null,
|
||||||
permissions: this.permissions,
|
permissions: this.permissions,
|
||||||
|
groups: this.groups.map((group) => group.toJSON()),
|
||||||
} as const
|
} as const
|
||||||
}
|
}
|
||||||
|
|
||||||
toString(): string {
|
toString(): string {
|
||||||
return `Group (${JSON.stringify(this)})`
|
return `Group (${JSON.stringify(this)})`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toRef(): Ref<Group> {
|
||||||
|
return super.toRef() as Ref<Group>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Group extends Ressource {
|
export interface Group extends Ressource {
|
||||||
type: 'group'
|
type: 'group'
|
||||||
posix: Posix | undefined
|
posix: Posix | undefined
|
||||||
|
groups: readonly Ref<Group>[]
|
||||||
permissions: Readonly<{
|
permissions: Readonly<{
|
||||||
[serviceOrMachine: UUID]: {
|
[serviceOrMachine: UUID]: {
|
||||||
read: boolean
|
read: boolean
|
||||||
|
|
|
@ -1,29 +1,58 @@
|
||||||
import { ToJson } from '@/types.ts'
|
|
||||||
import { Group } from '@models/group.ts'
|
|
||||||
import { Ressource } from '@models/ressource.ts'
|
|
||||||
import { Ref } from '@/src/models/utils/ref.ts'
|
import { Ref } from '@/src/models/utils/ref.ts'
|
||||||
|
import type { ToJson, UrlString } from '@/types.ts'
|
||||||
|
import type { Group } from '@models/group.ts'
|
||||||
|
import { Ressource } from '@models/ressource.ts'
|
||||||
|
import { Avatar } from '@/src/models/utils/avatar.ts'
|
||||||
|
|
||||||
export class Machine extends Ressource {
|
export class Machine extends Ressource {
|
||||||
static fromJSON(
|
static fromJSON(
|
||||||
json: ToJson<Machine>,
|
{ url, ...json }: ToJson<Machine>,
|
||||||
): Machine {
|
): Machine {
|
||||||
const url = new URL(json.url)
|
|
||||||
const avatar = new URL(json.avatar)
|
|
||||||
const groups = json.groups.map((group) => Ref.fromString<Group>(group))
|
const groups = json.groups.map((group) => Ref.fromString<Group>(group))
|
||||||
|
|
||||||
return new Machine({ ...json, url, avatar, groups })
|
return new Machine({ ...json, url, groups })
|
||||||
}
|
}
|
||||||
|
|
||||||
static create(
|
static create(
|
||||||
{ tags, url, status, avatar, groups, ...props }:
|
{ tags, url, status, groups, avatar }: Pick<
|
||||||
& Pick<Machine, 'tags' | 'url' | 'status' | 'avatar' | 'groups'>
|
Machine,
|
||||||
& Pick<Ressource, 'name' | 'uuid' | 'createdAt' | 'updatedAt'>,
|
'name' | 'tags' | 'url' | 'status' | 'avatar' | 'groups'
|
||||||
|
>,
|
||||||
): Machine {
|
): Machine {
|
||||||
return new Machine({ ...props, tags, url, status, avatar, groups })
|
const { uuid, createdAt, updatedAt } = super.create({ name, avatar })
|
||||||
|
return new Machine({
|
||||||
|
uuid,
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
avatar,
|
||||||
|
name,
|
||||||
|
tags,
|
||||||
|
url,
|
||||||
|
status,
|
||||||
|
groups,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
static load({
|
||||||
|
name,
|
||||||
|
avatar = Avatar.fromEmoji('🖨️'),
|
||||||
|
tags = [],
|
||||||
|
url,
|
||||||
|
status = 'unknown',
|
||||||
|
groups = [],
|
||||||
|
}: {
|
||||||
|
name: Machine['name']
|
||||||
|
avatar?: Machine['avatar']
|
||||||
|
tags?: Machine['tags']
|
||||||
|
url: Machine['url']
|
||||||
|
status?: Machine['status']
|
||||||
|
groups?: Machine['groups']
|
||||||
|
}): Machine {
|
||||||
|
return this.create({ name, avatar, tags, url, status, groups })
|
||||||
}
|
}
|
||||||
|
|
||||||
#tags: readonly string[]
|
#tags: readonly string[]
|
||||||
#url: URL
|
#url: UrlString
|
||||||
#status:
|
#status:
|
||||||
| 'ready'
|
| 'ready'
|
||||||
| 'busy'
|
| 'busy'
|
||||||
|
@ -31,25 +60,23 @@ export class Machine extends Ressource {
|
||||||
| 'discontinued'
|
| 'discontinued'
|
||||||
| 'error'
|
| 'error'
|
||||||
| 'unknown'
|
| 'unknown'
|
||||||
#avatar: URL
|
|
||||||
#groups: readonly Ref<Group>[]
|
#groups: readonly Ref<Group>[]
|
||||||
|
|
||||||
private constructor(
|
private constructor(
|
||||||
{ tags, url, status, avatar, groups, ...props }:
|
{ tags, url, status, groups, ...props }:
|
||||||
& Pick<Machine, 'tags' | 'url' | 'status' | 'avatar' | 'groups'>
|
& Pick<Machine, 'tags' | 'url' | 'status' | 'avatar' | 'groups'>
|
||||||
& Pick<Ressource, 'name' | 'uuid' | 'createdAt' | 'updatedAt'>,
|
& Pick<Ressource, 'name' | 'uuid' | 'avatar' | 'createdAt' | 'updatedAt'>,
|
||||||
) {
|
) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.#tags = Object.freeze(tags)
|
this.#tags = Object.freeze(tags)
|
||||||
this.#url = new URL(url)
|
this.#url = new URL(url).href as UrlString
|
||||||
if (!['working', 'pending', 'ready', 'unavailable'].includes(status)) {
|
if (!['working', 'pending', 'ready', 'unavailable'].includes(status)) {
|
||||||
throw new TypeError(
|
throw new TypeError(
|
||||||
`status is "${status}" but ('ready' | 'busy' | 'unavailable' | 'discontinued' | 'error' | 'unknown') is required`,
|
`status is "${status}" but ('ready' | 'busy' | 'unavailable' | 'discontinued' | 'error' | 'unknown') is required`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
this.#status = status
|
this.#status = status
|
||||||
this.#avatar = new URL(avatar)
|
|
||||||
this.#groups = Object.freeze(groups)
|
this.#groups = Object.freeze(groups)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,17 +84,14 @@ export class Machine extends Ressource {
|
||||||
return 'machine'
|
return 'machine'
|
||||||
}
|
}
|
||||||
get tags() {
|
get tags() {
|
||||||
return this.#tags.slice()
|
return this.#tags
|
||||||
}
|
}
|
||||||
get url() {
|
get url() {
|
||||||
return new URL(this.#url)
|
return this.#url
|
||||||
}
|
}
|
||||||
get status() {
|
get status() {
|
||||||
return this.#status
|
return this.#status
|
||||||
}
|
}
|
||||||
get avatar() {
|
|
||||||
return new URL(this.#avatar)
|
|
||||||
}
|
|
||||||
get groups() {
|
get groups() {
|
||||||
return this.#groups
|
return this.#groups
|
||||||
}
|
}
|
||||||
|
@ -84,10 +108,9 @@ export class Machine extends Ressource {
|
||||||
return {
|
return {
|
||||||
...super.toJSON(),
|
...super.toJSON(),
|
||||||
type: this.type,
|
type: this.type,
|
||||||
tags: this.tags.slice(),
|
tags: this.tags,
|
||||||
url: this.url.toJSON(),
|
url: this.url,
|
||||||
status: this.status,
|
status: this.status,
|
||||||
avatar: this.avatar.toJSON(),
|
|
||||||
groups: Object.freeze(this.groups.map((group) => group.toJSON())),
|
groups: Object.freeze(this.groups.map((group) => group.toJSON())),
|
||||||
} as const
|
} as const
|
||||||
}
|
}
|
||||||
|
@ -95,12 +118,16 @@ export class Machine extends Ressource {
|
||||||
toString(): string {
|
toString(): string {
|
||||||
return `Machine (${JSON.stringify(this)})`
|
return `Machine (${JSON.stringify(this)})`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toRef(): Ref<Machine> {
|
||||||
|
return super.toRef() as Ref<Machine>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Machine extends Ressource {
|
export interface Machine extends Ressource {
|
||||||
type: 'machine'
|
type: 'machine'
|
||||||
tags: string[]
|
tags: readonly string[]
|
||||||
url: URL
|
url: UrlString
|
||||||
status:
|
status:
|
||||||
| 'ready'
|
| 'ready'
|
||||||
| 'busy'
|
| 'busy'
|
||||||
|
@ -108,6 +135,5 @@ export interface Machine extends Ressource {
|
||||||
| 'discontinued'
|
| 'discontinued'
|
||||||
| 'error'
|
| 'error'
|
||||||
| 'unknown'
|
| 'unknown'
|
||||||
avatar: URL
|
|
||||||
groups: readonly Ref<Group>[]
|
groups: readonly Ref<Group>[]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Ref } from '@/src/models/utils/ref.ts'
|
import { Ref } from '@/src/models/utils/ref.ts'
|
||||||
import { DateString, ToJson, UUID } from '@/types.ts'
|
import type { DateString, ToJson, UrlString, UUID } from '@/types.ts'
|
||||||
import { regex } from '@/utils.ts'
|
import { regex } from '@/utils.ts'
|
||||||
|
|
||||||
export class Ressource {
|
export class Ressource {
|
||||||
|
@ -8,29 +8,37 @@ export class Ressource {
|
||||||
): Ressource {
|
): Ressource {
|
||||||
return new Ressource({
|
return new Ressource({
|
||||||
name: json.name,
|
name: json.name,
|
||||||
|
avatar: json.avatar,
|
||||||
uuid: json.uuid,
|
uuid: json.uuid,
|
||||||
createdAt: json.createdAt,
|
createdAt: json.createdAt,
|
||||||
updatedAt: json.updatedAt,
|
updatedAt: json.updatedAt,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static create({ name }: Pick<Ressource, 'name'>): Ressource {
|
static create(
|
||||||
|
{ name, avatar }: Pick<Ressource, 'name' | 'avatar'>,
|
||||||
|
): Ressource {
|
||||||
const uuid = crypto.randomUUID() as UUID
|
const uuid = crypto.randomUUID() as UUID
|
||||||
const createdAt = new Date().toISOString() as DateString
|
const createdAt = new Date().toISOString() as DateString
|
||||||
const updatedAt = createdAt
|
const updatedAt = createdAt
|
||||||
|
|
||||||
return new Ressource({ name, uuid, createdAt, updatedAt })
|
return new Ressource({ name, avatar, uuid, createdAt, updatedAt })
|
||||||
|
}
|
||||||
|
|
||||||
|
static load(_options: never): Ressource {
|
||||||
|
throw new Error('not implemented')
|
||||||
}
|
}
|
||||||
|
|
||||||
#name: string
|
#name: string
|
||||||
#uuid: UUID
|
#uuid: UUID
|
||||||
#createdAt: DateString
|
#createdAt: DateString
|
||||||
#updatedAt: DateString
|
#updatedAt: DateString
|
||||||
|
#avatar: UrlString
|
||||||
|
|
||||||
protected constructor(
|
protected constructor(
|
||||||
{ name, uuid, createdAt, updatedAt }: Pick<
|
{ name, avatar, uuid, createdAt, updatedAt }: Pick<
|
||||||
Ressource,
|
Ressource,
|
||||||
'name' | 'uuid' | 'createdAt' | 'updatedAt'
|
'name' | 'avatar' | 'uuid' | 'createdAt' | 'updatedAt'
|
||||||
>,
|
>,
|
||||||
) {
|
) {
|
||||||
this.#name = name
|
this.#name = name
|
||||||
|
@ -39,6 +47,7 @@ export class Ressource {
|
||||||
`the following uuid "${uuid}" is not a valid UUID V4`,
|
`the following uuid "${uuid}" is not a valid UUID V4`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
this.#avatar = new URL(avatar).href as UrlString
|
||||||
this.#uuid = uuid
|
this.#uuid = uuid
|
||||||
if (!regex.isoDateString.test(createdAt)) {
|
if (!regex.isoDateString.test(createdAt)) {
|
||||||
throw new SyntaxError(
|
throw new SyntaxError(
|
||||||
|
@ -63,6 +72,9 @@ export class Ressource {
|
||||||
get name(): string {
|
get name(): string {
|
||||||
return this.#name
|
return this.#name
|
||||||
}
|
}
|
||||||
|
get avatar(): UrlString {
|
||||||
|
return this.#avatar
|
||||||
|
}
|
||||||
get createdAt(): DateString {
|
get createdAt(): DateString {
|
||||||
return this.#createdAt
|
return this.#createdAt
|
||||||
}
|
}
|
||||||
|
@ -83,6 +95,7 @@ export class Ressource {
|
||||||
type: this.type,
|
type: this.type,
|
||||||
uuid: this.uuid,
|
uuid: this.uuid,
|
||||||
name: this.name,
|
name: this.name,
|
||||||
|
avatar: this.avatar,
|
||||||
createdAt: this.createdAt,
|
createdAt: this.createdAt,
|
||||||
updatedAt: this.updatedAt,
|
updatedAt: this.updatedAt,
|
||||||
} as const
|
} as const
|
||||||
|
@ -101,6 +114,7 @@ export interface Ressource {
|
||||||
type: 'user' | 'machine' | 'service' | 'group' | 'credential'
|
type: 'user' | 'machine' | 'service' | 'group' | 'credential'
|
||||||
uuid: UUID
|
uuid: UUID
|
||||||
name: string
|
name: string
|
||||||
|
avatar: UrlString
|
||||||
createdAt: DateString
|
createdAt: DateString
|
||||||
updatedAt: DateString
|
updatedAt: DateString
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,26 @@
|
||||||
import { ToJson } from '@/types.ts'
|
|
||||||
import { Group } from '@models/group.ts'
|
|
||||||
import { Ressource } from '@models/ressource.ts'
|
|
||||||
import { Ref } from '@/src/models/utils/ref.ts'
|
import { Ref } from '@/src/models/utils/ref.ts'
|
||||||
|
import type { ToJson, UrlString } from '@/types.ts'
|
||||||
|
import type { Group } from '@models/group.ts'
|
||||||
|
import { Ressource } from '@models/ressource.ts'
|
||||||
|
import { Avatar } from '@/src/models/utils/avatar.ts'
|
||||||
|
|
||||||
export class Service extends Ressource {
|
export class Service extends Ressource {
|
||||||
static fromJSON(
|
static fromJSON(
|
||||||
{ groups, url, avatar, ...json }: ToJson<Service>,
|
{ groups, ...json }: ToJson<Service>,
|
||||||
): Service {
|
): Service {
|
||||||
return new Service({
|
return new Service({
|
||||||
groups: groups.map((group) => Ref.fromString<Group>(group)),
|
groups: groups.map((group) => Ref.fromString<Group>(group)),
|
||||||
url: new URL(url),
|
|
||||||
avatar: new URL(avatar),
|
|
||||||
...json,
|
...json,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static create(
|
static create(
|
||||||
{ category, url, groups, avatar, name }: Pick<
|
{ category, tags, url, groups, avatar, name }: Pick<
|
||||||
Service,
|
Service,
|
||||||
'category' | 'url' | 'groups' | 'avatar' | 'name'
|
'category' | 'tags' | 'url' | 'groups' | 'avatar' | 'name'
|
||||||
>,
|
>,
|
||||||
): Service {
|
): Service {
|
||||||
const { uuid, createdAt, updatedAt } = super.create({ name })
|
const { uuid, createdAt, updatedAt } = super.create({ name, avatar })
|
||||||
return new Service({
|
return new Service({
|
||||||
uuid,
|
uuid,
|
||||||
createdAt,
|
createdAt,
|
||||||
|
@ -29,21 +28,40 @@ export class Service extends Ressource {
|
||||||
name,
|
name,
|
||||||
category,
|
category,
|
||||||
url,
|
url,
|
||||||
|
tags,
|
||||||
groups,
|
groups,
|
||||||
avatar,
|
avatar,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#category: 'bin' | 'web' | 'fs' | 'git'
|
static load({
|
||||||
#url: URL
|
name,
|
||||||
|
avatar = Avatar.fromEmoji('⚙️'),
|
||||||
|
tags = [],
|
||||||
|
url,
|
||||||
|
category,
|
||||||
|
groups = [],
|
||||||
|
}: {
|
||||||
|
name: Service['name']
|
||||||
|
url: Service['url']
|
||||||
|
avatar?: Service['avatar']
|
||||||
|
tags?: Service['tags']
|
||||||
|
category: Service['category']
|
||||||
|
groups?: Service['groups']
|
||||||
|
}): Service {
|
||||||
|
return this.create({ name, avatar, tags, url, category, groups })
|
||||||
|
}
|
||||||
|
|
||||||
|
#category: 'web' | 'fs' | 'git'
|
||||||
|
#url: UrlString
|
||||||
#groups: readonly Ref<Group>[]
|
#groups: readonly Ref<Group>[]
|
||||||
#avatar: URL
|
#tags: readonly string[]
|
||||||
|
|
||||||
private constructor({
|
private constructor({
|
||||||
category,
|
category,
|
||||||
|
tags,
|
||||||
url,
|
url,
|
||||||
groups,
|
groups,
|
||||||
avatar,
|
|
||||||
...ressource
|
...ressource
|
||||||
}: Pick<
|
}: Pick<
|
||||||
Service,
|
Service,
|
||||||
|
@ -53,20 +71,21 @@ export class Service extends Ressource {
|
||||||
| 'name'
|
| 'name'
|
||||||
| 'category'
|
| 'category'
|
||||||
| 'url'
|
| 'url'
|
||||||
|
| 'tags'
|
||||||
| 'groups'
|
| 'groups'
|
||||||
| 'avatar'
|
| 'avatar'
|
||||||
>) {
|
>) {
|
||||||
super(ressource)
|
super(ressource)
|
||||||
|
|
||||||
if (!['bin', 'web', 'fs', 'git'].includes(category)) {
|
if (!['web', 'fs', 'git'].includes(category)) {
|
||||||
throw new TypeError(
|
throw new TypeError(
|
||||||
`category is "${category}" but ('bin' | 'web' | 'fs' | 'git') is required`,
|
`category is "${category}" but ('web' | 'fs' | 'git') is required`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
this.#category = category
|
this.#category = category
|
||||||
this.#url = url
|
this.#url = new URL(url).href as UrlString
|
||||||
this.#groups = Object.freeze(groups)
|
this.#groups = Object.freeze(groups)
|
||||||
this.#avatar = avatar
|
this.#tags = Object.freeze(tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
get type(): 'service' {
|
get type(): 'service' {
|
||||||
|
@ -77,16 +96,16 @@ export class Service extends Ressource {
|
||||||
return this.#category
|
return this.#category
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get tags() {
|
||||||
|
return this.#tags
|
||||||
|
}
|
||||||
|
|
||||||
get url() {
|
get url() {
|
||||||
return new URL(this.#url)
|
return this.#url
|
||||||
}
|
}
|
||||||
|
|
||||||
get groups() {
|
get groups() {
|
||||||
return Object.freeze(this.#groups)
|
return this.#groups
|
||||||
}
|
|
||||||
|
|
||||||
get avatar() {
|
|
||||||
return new URL(this.#avatar)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
update(
|
update(
|
||||||
|
@ -102,21 +121,25 @@ export class Service extends Ressource {
|
||||||
...super.toJSON(),
|
...super.toJSON(),
|
||||||
type: this.type,
|
type: this.type,
|
||||||
category: this.category,
|
category: this.category,
|
||||||
url: this.url.toJSON(),
|
tags: this.tags,
|
||||||
|
url: this.url,
|
||||||
groups: this.groups.map((group) => group.toJSON()),
|
groups: this.groups.map((group) => group.toJSON()),
|
||||||
avatar: this.avatar.toJSON(),
|
|
||||||
} as const
|
} as const
|
||||||
}
|
}
|
||||||
|
|
||||||
toString(): string {
|
toString(): string {
|
||||||
return `Service (${JSON.stringify(this)})`
|
return `Service (${JSON.stringify(this)})`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toRef(): Ref<Service> {
|
||||||
|
return super.toRef() as Ref<Service>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Service extends Ressource {
|
export interface Service extends Ressource {
|
||||||
type: 'service'
|
type: 'service'
|
||||||
category: 'bin' | 'web' | 'fs' | 'git'
|
category: 'web' | 'fs' | 'git'
|
||||||
url: URL
|
tags: readonly string[]
|
||||||
|
url: UrlString
|
||||||
groups: readonly Ref<Group>[]
|
groups: readonly Ref<Group>[]
|
||||||
avatar: URL
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
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'
|
import { Ref } from '@/src/models/utils/ref.ts'
|
||||||
|
import type { Login, MailAddress, Posix, ToJson } from '@/types.ts'
|
||||||
|
import { regex, toLogin } from '@/utils.ts'
|
||||||
|
import type { Credential, CredentialCategory } from '@models/credential.ts'
|
||||||
|
import type { Group } from '@models/group.ts'
|
||||||
|
import { Ressource } from '@models/ressource.ts'
|
||||||
|
import { Avatar } from '@/src/models/utils/avatar.ts'
|
||||||
|
|
||||||
export class User extends Ressource {
|
export class User extends Ressource {
|
||||||
static fromJSON(
|
static fromJSON(json: ToJson<User>): User {
|
||||||
json: ToJson<User>,
|
|
||||||
): User {
|
|
||||||
if (json.type !== 'user') {
|
if (json.type !== 'user') {
|
||||||
throw new TypeError(`type is "${json.type}" but "user" is required`)
|
throw new TypeError(`type is "${json.type}" but "user" is required`)
|
||||||
}
|
}
|
||||||
|
@ -22,7 +21,10 @@ export class User extends Ressource {
|
||||||
`firstname type is "${typeof json.firstname}" but "string" is required`,
|
`firstname type is "${typeof json.firstname}" but "string" is required`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (typeof json.login !== 'string' && /(\w|_)+\.(\w|_)+/.test(json.login)) {
|
if (
|
||||||
|
typeof json.login !== 'string' &&
|
||||||
|
/(\w|_)+\.(\w|_)+/.test(json.login)
|
||||||
|
) {
|
||||||
throw new TypeError(
|
throw new TypeError(
|
||||||
`login is "${json.login}" but "\${string}.\${string}" is required`,
|
`login is "${json.login}" but "\${string}.\${string}" is required`,
|
||||||
)
|
)
|
||||||
|
@ -35,20 +37,20 @@ export class User extends Ressource {
|
||||||
|
|
||||||
const credentials = Object.freeze(
|
const credentials = Object.freeze(
|
||||||
json.credentials.map((credential) =>
|
json.credentials.map((credential) =>
|
||||||
Ref.fromString<Credential>(credential)
|
Ref.fromString<Credential<CredentialCategory>>(credential)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
const groups = Object.freeze(
|
const groups = Object.freeze(
|
||||||
json.groups.map((group) => Ref.fromString<Group>(group)),
|
json.groups.map((group) => Ref.fromString<Group>(group)),
|
||||||
)
|
)
|
||||||
const avatar = new URL(json.avatar)
|
|
||||||
const posix = json.posix ?? undefined
|
const posix = json.posix ?? undefined
|
||||||
|
|
||||||
return new User({ ...json, posix, credentials, groups, avatar })
|
return new User({ ...json, posix, credentials, groups })
|
||||||
}
|
}
|
||||||
|
|
||||||
static create({
|
static create({
|
||||||
name,
|
name,
|
||||||
|
avatar,
|
||||||
...props
|
...props
|
||||||
}: Pick<
|
}: Pick<
|
||||||
User,
|
User,
|
||||||
|
@ -61,8 +63,38 @@ export class User extends Ressource {
|
||||||
| 'avatar'
|
| 'avatar'
|
||||||
| 'credentials'
|
| 'credentials'
|
||||||
>): User {
|
>): User {
|
||||||
const { uuid, createdAt, updatedAt } = super.create({ name })
|
const { uuid, createdAt, updatedAt } = super.create({ name, avatar })
|
||||||
return new User({ name, uuid, createdAt, updatedAt, ...props })
|
return new User({ name, avatar, uuid, createdAt, updatedAt, ...props })
|
||||||
|
}
|
||||||
|
|
||||||
|
static load({
|
||||||
|
lastname,
|
||||||
|
firstname,
|
||||||
|
mail,
|
||||||
|
groups = [],
|
||||||
|
posix,
|
||||||
|
avatar = Avatar.fromEmoji('👤'),
|
||||||
|
credentials = [],
|
||||||
|
}: {
|
||||||
|
lastname: User['lastname']
|
||||||
|
firstname: User['firstname']
|
||||||
|
mail: User['mail']
|
||||||
|
groups?: User['groups']
|
||||||
|
posix?: User['posix']
|
||||||
|
avatar?: User['avatar']
|
||||||
|
credentials?: User['credentials']
|
||||||
|
}): User {
|
||||||
|
const name = `${firstname} ${lastname}`
|
||||||
|
return this.create({
|
||||||
|
name,
|
||||||
|
lastname,
|
||||||
|
firstname,
|
||||||
|
mail,
|
||||||
|
groups,
|
||||||
|
posix,
|
||||||
|
avatar,
|
||||||
|
credentials,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#lastname: string
|
#lastname: string
|
||||||
|
@ -71,8 +103,7 @@ export class User extends Ressource {
|
||||||
#mail: MailAddress
|
#mail: MailAddress
|
||||||
#groups: readonly Ref<Group>[]
|
#groups: readonly Ref<Group>[]
|
||||||
#posix?: Posix
|
#posix?: Posix
|
||||||
#avatar: URL
|
#credentials: readonly Ref<Credential<CredentialCategory>>[]
|
||||||
#credentials: readonly Ref<Credential>[]
|
|
||||||
|
|
||||||
private constructor({
|
private constructor({
|
||||||
lastname,
|
lastname,
|
||||||
|
@ -80,7 +111,6 @@ export class User extends Ressource {
|
||||||
mail,
|
mail,
|
||||||
groups,
|
groups,
|
||||||
posix,
|
posix,
|
||||||
avatar,
|
|
||||||
credentials,
|
credentials,
|
||||||
...ressource
|
...ressource
|
||||||
}: Pick<
|
}: Pick<
|
||||||
|
@ -113,7 +143,6 @@ export class User extends Ressource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.#posix = posix
|
this.#posix = posix
|
||||||
this.#avatar = avatar
|
|
||||||
this.#login = toLogin({ firstname, lastname })
|
this.#login = toLogin({ firstname, lastname })
|
||||||
this.#credentials = Object.freeze(credentials)
|
this.#credentials = Object.freeze(credentials)
|
||||||
}
|
}
|
||||||
|
@ -139,9 +168,6 @@ export class User extends Ressource {
|
||||||
get posix() {
|
get posix() {
|
||||||
return this.#posix
|
return this.#posix
|
||||||
}
|
}
|
||||||
get avatar() {
|
|
||||||
return this.#avatar
|
|
||||||
}
|
|
||||||
get credentials() {
|
get credentials() {
|
||||||
return this.#credentials
|
return this.#credentials
|
||||||
}
|
}
|
||||||
|
@ -161,7 +187,7 @@ export class User extends Ressource {
|
||||||
mail: this.mail,
|
mail: this.mail,
|
||||||
groups: this.groups.map((group) => group.toJSON()),
|
groups: this.groups.map((group) => group.toJSON()),
|
||||||
posix: this.posix ?? null,
|
posix: this.posix ?? null,
|
||||||
avatar: this.avatar.toJSON(),
|
avatar: this.avatar,
|
||||||
credentials: this.credentials.map((credential) => credential.toJSON()),
|
credentials: this.credentials.map((credential) => credential.toJSON()),
|
||||||
} as const
|
} as const
|
||||||
}
|
}
|
||||||
|
@ -169,6 +195,10 @@ export class User extends Ressource {
|
||||||
toString(): string {
|
toString(): string {
|
||||||
return `User (${JSON.stringify(this)})`
|
return `User (${JSON.stringify(this)})`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toRef(): Ref<User> {
|
||||||
|
return super.toRef() as Ref<User>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface User extends Ressource {
|
export interface User extends Ressource {
|
||||||
|
@ -179,6 +209,5 @@ export interface User extends Ressource {
|
||||||
mail: MailAddress
|
mail: MailAddress
|
||||||
groups: readonly Ref<Group>[]
|
groups: readonly Ref<Group>[]
|
||||||
posix: Posix | undefined
|
posix: Posix | undefined
|
||||||
avatar: URL
|
credentials: readonly Ref<Credential<CredentialCategory>>[]
|
||||||
credentials: readonly Ref<Credential>[]
|
|
||||||
}
|
}
|
||||||
|
|
8
src/models/utils/avatar.ts
Normal file
8
src/models/utils/avatar.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import type { UrlString } from '@/types.ts'
|
||||||
|
|
||||||
|
export class Avatar {
|
||||||
|
static fromEmoji(emoji: string): UrlString {
|
||||||
|
return `data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%2210 0 100 100%22><text y=%22.90em%22 font-size=%2290%22>${emoji}</text></svg>`
|
||||||
|
}
|
||||||
|
private constructor() {}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { Db } from '@/mod.ts'
|
import type { Db } from '@/mod.ts'
|
||||||
import type { UUID } from '@/types.ts'
|
import type { UUID } from '@/types.ts'
|
||||||
import {
|
import {
|
||||||
Credential,
|
Credential,
|
||||||
|
@ -38,8 +38,9 @@ export class Ref<T extends Ressource> extends String {
|
||||||
}
|
}
|
||||||
|
|
||||||
static dbResolver(db: Db) {
|
static dbResolver(db: Db) {
|
||||||
return <T extends Ressource>(ref: RefString<T>) => {
|
return <T extends Ressource>(ref: RefString<T>): Promise<T> => {
|
||||||
const { type, uuid } = Ref.parse(ref)
|
const { type, uuid } = Ref.parse(ref)
|
||||||
|
//@ts-expect-error force type casting to fix
|
||||||
return db.ressource[type].get({ uuid })
|
return db.ressource[type].get({ uuid })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
8
types.ts
8
types.ts
|
@ -26,3 +26,11 @@ export type Posix = {
|
||||||
home: string
|
home: string
|
||||||
shell: string
|
shell: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Base64String = `${string}`
|
||||||
|
export type UrlString = `${
|
||||||
|
| 'data:'
|
||||||
|
| 'file://'
|
||||||
|
| 'http://'
|
||||||
|
| 'https://'
|
||||||
|
| 'git:'}${string}`
|
||||||
|
|
Loading…
Reference in a new issue