diff --git a/README.md b/README.md index 9ff6e33..7a5df4e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ -# Coh@bit ressources +# KM - Coh@bit resources manager -Server de gestion des ressources de cohabit. +Système de gestion des ressources de cohabit. diff --git a/deno.json b/deno.json index 8aa7014..da85801 100644 --- a/deno.json +++ b/deno.json @@ -1,5 +1,5 @@ { - "name": "@cohabit/ressources", + "name": "@cohabit/resources-manager", "version": "0.1.2", "exports": { ".": "./mod.ts", diff --git a/src/api/routes/users.ts b/src/api/routes/users.ts new file mode 100644 index 0000000..c546d55 --- /dev/null +++ b/src/api/routes/users.ts @@ -0,0 +1,55 @@ +import type { Db, Resource } from '../../../mod.ts' +import type { UUID } from '../../../types.ts' +import type { ResourceBuilder, ResourceType } from '../types.ts' +import { respondJson } from '../utils.ts' + +export async function resourceHandler( + type: ResourceType, + Builder: ResourceBuilder, + db: Db, + req: Request, + id?: string, +): Promise { + if (req.method === 'POST') { + try { + const json = await req.json() as T + try { + //@ts-expect-error Extends from Resource + const resource = Builder.create(json) + //@ts-expect-error not a generic + const result = await db.resource[type].set(resource) + if (result.ok) { + //@ts-expect-error generic to fix + return respondJson(resource) + } else { + return respondJson(new Error(`can't insert ${resource} in db`)) + } + } catch (error) { + return respondJson(error) + } + } catch (error) { + return respondJson(error) + } + } + if (req.method === 'GET') { + if (id === undefined) { + return respondJson(new Error('missing "id" for "GET"')) + } + try { + const resource = await db.resource[type].get({ uuid: id as UUID }) + //@ts-expect-error generic to fix + return respondJson(resource) + } catch { + return respondJson( + new Error(`can't find any ${type} with the current uuid ${id}`), + ) + } + } + if (req.method === 'PATCH') { + throw new Error('not implemented') + } + if (req.method === 'DELETE') { + throw new Error('not implemented') + } + return respondJson(new Error(`method "${req.method}" is not allowed`)) +} diff --git a/src/db/mod.ts b/src/db/mod.ts index b8f92b3..ca1171b 100644 --- a/src/db/mod.ts +++ b/src/db/mod.ts @@ -3,14 +3,14 @@ import { Group, Machine, type Ref, - type Ressource, + type Resource, Service, User, } from '../models/mod.ts' import type { CredentialCategory } from '../models/src/credential.ts' -//!TODO link ressources (get, list) -//!TODO Purge unused ressources (delete) +//!TODO link resources (get, list) +//!TODO Purge unused resources (delete) export class Db { #kv: Deno.Kv @@ -27,112 +27,112 @@ export class Db { return this.#kv } - get prefix(): { ressource: 'ressource' } { + get prefix(): { resource: 'resource' } { return { - ressource: 'ressource', + resource: 'resource', } } - get ressource() { + get resource() { return { - credential: this.#ressourceStorage>( + credential: this.#resourceStorage>( 'credential', Credential, ), - group: this.#ressourceStorage('group', Group), - machine: this.#ressourceStorage('machine', Machine), - service: this.#ressourceStorage('service', Service), - user: this.#ressourceStorage('user', User), + group: this.#resourceStorage('group', Group), + machine: this.#resourceStorage('machine', Machine), + service: this.#resourceStorage('service', Service), + user: this.#resourceStorage('user', User), } } - #ressourceStorage( - type: RessourceType, - Builder: RessourceBuilder, + #resourceStorage( + type: ResourceType, + Builder: ResourceBuilder, ) { return { - get: (ressource: Pick) => - this.#get(type, Builder, ressource), - set: (ressources: T[]) => this.#set(type, ressources), - delete: (ressources: Pick[]) => - this.#delete(type, ressources), + get: (resource: Pick) => + this.#get(type, Builder, resource), + set: (resources: T[]) => this.#set(type, resources), + delete: (resources: Pick[]) => + this.#delete(type, resources), list: ( - filter: (ressource: T) => boolean | Promise = () => true, + filter: (resource: T) => boolean | Promise = () => true, ) => this.#list(type, Builder, filter), listRef: async ( - filter: (ressource: T) => boolean | Promise = () => true, + filter: (resource: T) => boolean | Promise = () => true, ) => { - const ressources: Ref[] = [] - for await (const ressource of this.#list(type, Builder, filter)) { - ressources.push(ressource.toRef() as Ref) + const resources: Ref[] = [] + for await (const resource of this.#list(type, Builder, filter)) { + resources.push(resource.toRef() as Ref) } - return ressources + return resources }, } } - async #get( - type: RessourceType, - Builder: RessourceBuilder, + async #get( + type: ResourceType, + Builder: ResourceBuilder, entry: Pick, ): Promise { - const json = await this.#kv.get>([ - this.prefix.ressource, + const json = await this.#kv.get>([ + this.prefix.resource, type, entry.uuid, ]) if (json.value) { - //@ts-expect-error Type union of Ressource types for Builder + //@ts-expect-error Type union of Resource types for Builder return Builder.fromJSON(json.value) } throw new ReferenceError( - `no ressource.${type} was found with uuid: "${entry.uuid}"`, + `no resource.${type} was found with uuid: "${entry.uuid}"`, ) } - #set(type: RessourceType, entries: T[]) { + #set(type: ResourceType, entries: T[]) { const atomic = this.#kv.atomic() for (const entry of entries) { //! TODO check if `refs` exists - const key = [this.prefix.ressource, type, entry.uuid] + const key = [this.prefix.resource, type, entry.uuid] atomic.set(key, entry.toJSON()) } return atomic.commit() } - #delete( - type: RessourceType, + #delete( + type: ResourceType, entries: Pick[], ): Promise { const atomic = this.#kv.atomic() for (const entry of entries) { //! TODO check if `refs` exists - atomic.delete([this.prefix.ressource, type, entry.uuid]) + atomic.delete([this.prefix.resource, type, entry.uuid]) } return atomic.commit() } - async *#list( - type: RessourceType, - Builder: RessourceBuilder, + async *#list( + type: ResourceType, + Builder: ResourceBuilder, filter: (entry: T) => boolean | Promise, ): AsyncGenerator { - const list = this.#kv.list>({ - prefix: [this.prefix.ressource, type], + const list = this.#kv.list>({ + prefix: [this.prefix.resource, type], }) for await (const entry of list) { const value = entry.value - //@ts-expect-error Type union of Ressource types for Builder - const ressource = Builder.fromJSON(value) as T - if (await filter(ressource)) { - yield ressource + //@ts-expect-error Type union of Resource types for Builder + const resource = Builder.fromJSON(value) as T + if (await filter(resource)) { + yield resource } } } } -type RessourceType = T extends +type ResourceType = T extends Credential ? 'credential' : T extends Group ? 'group' : T extends Machine ? 'machine' @@ -140,7 +140,7 @@ type RessourceType = T extends : T extends User ? 'user' : never -type RessourceBuilder = T extends +type ResourceBuilder = T extends Credential ? typeof Credential : T extends Group ? typeof Group : T extends Machine ? typeof Machine @@ -148,4 +148,4 @@ type RessourceBuilder = T extends : T extends User ? typeof User : never -type RessourceJson = ReturnType +type ResourceJson = ReturnType diff --git a/src/models/mod.ts b/src/models/mod.ts index d6200e4..5d39305 100644 --- a/src/models/mod.ts +++ b/src/models/mod.ts @@ -1,7 +1,8 @@ -export { Ref, type RefResolver, type RefString } from './utils/ref.ts' export { Credential } from './src/credential.ts' export { Group } from './src/group.ts' export { Machine } from './src/machine.ts' -export type { Ressource } from './src/ressource.ts' +export type { Resource } from './src/resource.ts' export { Service } from './src/service.ts' export { User } from './src/user.ts' +export { Ref, type RefResolver, type RefString } from './utils/ref.ts' + diff --git a/src/models/src/credential.ts b/src/models/src/credential.ts index 2722e7c..69b9cec 100644 --- a/src/models/src/credential.ts +++ b/src/models/src/credential.ts @@ -1,9 +1,9 @@ import type { Base64String, ToJson, UUID } from '../../../types.ts' -import { Ressource } from './ressource.ts' -import type { Ref } from '../utils/ref.ts' import { Avatar } from '../utils/avatar.ts' +import type { Ref } from '../utils/ref.ts' +import { Resource } from './resource.ts' -export class Credential extends Ressource { +export class Credential extends Resource { static fromJSON( json: ToJson>, ): Credential { @@ -48,7 +48,7 @@ export class Credential extends Ressource { private constructor( { category, store, ...props }: & Pick, 'category' | 'store'> - & Pick, + & Pick, ) { super(props) @@ -107,7 +107,7 @@ export class Credential extends Ressource { } } -export interface Credential extends Ressource { +export interface Credential extends Resource { type: 'credential' category: T store: Readonly['store']> diff --git a/src/models/src/group.ts b/src/models/src/group.ts index 1174aac..b281574 100644 --- a/src/models/src/group.ts +++ b/src/models/src/group.ts @@ -1,9 +1,9 @@ import type { Posix, ToJson, UUID } from '../../../types.ts' -import { Ressource } from './ressource.ts' -import { Ref } from '../utils/ref.ts' import { Avatar } from '../utils/avatar.ts' +import { Ref } from '../utils/ref.ts' +import { Resource } from './resource.ts' -export class Group extends Ressource { +export class Group extends Resource { static fromJSON( { posix, groups, ...json }: ToJson, ): Group { @@ -56,7 +56,7 @@ export class Group extends Ressource { private constructor( { posix, permissions, groups, ...props }: & Pick - & Pick, + & Pick, ) { super(props) @@ -133,7 +133,7 @@ export class Group extends Ressource { } } -export interface Group extends Ressource { +export interface Group extends Resource { type: 'group' posix: Posix | undefined groups: readonly Ref[] diff --git a/src/models/src/machine.ts b/src/models/src/machine.ts index 9cae7e7..a1fee0e 100644 --- a/src/models/src/machine.ts +++ b/src/models/src/machine.ts @@ -1,10 +1,10 @@ -import { Ref } from '../utils/ref.ts' import type { ToJson, UrlString } from '../../../types.ts' -import type { Group } from './group.ts' -import { Ressource } from './ressource.ts' import { Avatar } from '../utils/avatar.ts' +import { Ref } from '../utils/ref.ts' +import type { Group } from './group.ts' +import { Resource } from './resource.ts' -export class Machine extends Ressource { +export class Machine extends Resource { static fromJSON( { url, ...json }: ToJson, ): Machine { @@ -59,7 +59,7 @@ export class Machine extends Ressource { private constructor( { tags, url, status, groups, ...props }: & Pick - & Pick, + & Pick, ) { super(props) @@ -128,7 +128,7 @@ export class Machine extends Ressource { } } -export interface Machine extends Ressource { +export interface Machine extends Resource { type: 'machine' tags: readonly string[] url: UrlString diff --git a/src/models/src/ressource.ts b/src/models/src/resource.ts similarity index 82% rename from src/models/src/ressource.ts rename to src/models/src/resource.ts index a903833..9885795 100644 --- a/src/models/src/ressource.ts +++ b/src/models/src/resource.ts @@ -1,12 +1,12 @@ -import { Ref } from '../utils/ref.ts' import type { DateString, ToJson, UrlString, UUID } from '../../../types.ts' import { regex } from '../../../utils.ts' +import { Ref } from '../utils/ref.ts' -export class Ressource { +export class Resource { static fromJSON( - json: ToJson, - ): Ressource { - return new Ressource({ + json: ToJson, + ): Resource { + return new Resource({ name: json.name, avatar: json.avatar, uuid: json.uuid, @@ -16,16 +16,16 @@ export class Ressource { } static create( - { name, avatar }: Pick, - ): Ressource { + { name, avatar }: Pick, + ): Resource { const uuid = crypto.randomUUID() as UUID const createdAt = new Date().toISOString() as DateString const updatedAt = createdAt - return new Ressource({ name, avatar, uuid, createdAt, updatedAt }) + return new Resource({ name, avatar, uuid, createdAt, updatedAt }) } - static load(_options: never): Ressource { + static load(_options: never): Resource { throw new Error('not implemented') } @@ -37,7 +37,7 @@ export class Ressource { protected constructor( { name, avatar, uuid, createdAt, updatedAt }: Pick< - Ressource, + Resource, 'name' | 'avatar' | 'uuid' | 'createdAt' | 'updatedAt' >, ) { @@ -83,9 +83,9 @@ export class Ressource { } update( - props: Partial>, - ): Ressource { - return new Ressource({ + props: Partial>, + ): Resource { + return new Resource({ name: this.name, avatar: this.avatar, uuid: this.uuid, @@ -110,12 +110,12 @@ export class Ressource { return `Ressource (${JSON.stringify(this)})` } - toRef(): Ref { - return Ref.fromRessource(this) + toRef(): Ref { + return Ref.fromResource(this) } } -export interface Ressource { +export interface Resource { type: 'user' | 'machine' | 'service' | 'group' | 'credential' uuid: UUID name: string diff --git a/src/models/src/service.ts b/src/models/src/service.ts index b8130eb..0c7136a 100644 --- a/src/models/src/service.ts +++ b/src/models/src/service.ts @@ -1,10 +1,10 @@ -import { Ref } from '../utils/ref.ts' import type { ToJson, UrlString } from '../../../types.ts' -import type { Group } from './group.ts' -import { Ressource } from './ressource.ts' import { Avatar } from '../utils/avatar.ts' +import { Ref } from '../utils/ref.ts' +import type { Group } from './group.ts' +import { Resource } from './resource.ts' -export class Service extends Ressource { +export class Service extends Resource { static fromJSON( { groups, ...json }: ToJson, ): Service { @@ -62,7 +62,7 @@ export class Service extends Ressource { tags, url, groups, - ...ressource + ...resource }: Pick< Service, | 'uuid' @@ -75,7 +75,7 @@ export class Service extends Ressource { | 'groups' | 'avatar' >) { - super(ressource) + super(resource) if (!['web', 'fs', 'git'].includes(category)) { throw new TypeError( @@ -146,7 +146,7 @@ export class Service extends Ressource { } } -export interface Service extends Ressource { +export interface Service extends Resource { type: 'service' category: ServiceCategory tags: readonly string[] diff --git a/src/models/src/user.ts b/src/models/src/user.ts index 7613f81..e3adbcb 100644 --- a/src/models/src/user.ts +++ b/src/models/src/user.ts @@ -1,12 +1,12 @@ -import { Ref } from '../utils/ref.ts' import type { Login, MailAddress, Posix, ToJson } from '../../../types.ts' import { regex, toLogin } from '../../../utils.ts' +import { Avatar } from '../utils/avatar.ts' +import { Ref } from '../utils/ref.ts' import type { Credential, CredentialCategory } from './credential.ts' import type { Group } from './group.ts' -import { Ressource } from './ressource.ts' -import { Avatar } from '../utils/avatar.ts' +import { Resource } from './resource.ts' -export class User extends Ressource { +export class User extends Resource { static fromJSON(json: ToJson): User { if (json.type !== 'user') { throw new TypeError(`type is "${json.type}" but "user" is required`) @@ -112,7 +112,7 @@ export class User extends Ressource { groups, posix, credentials, - ...ressource + ...resource }: Pick< User, | 'uuid' @@ -127,7 +127,7 @@ export class User extends Ressource { | 'avatar' | 'credentials' >) { - super(ressource) + super(resource) this.#lastname = lastname this.#firstname = firstname if (!regex.mailAddress) { @@ -213,7 +213,7 @@ export class User extends Ressource { } } -export interface User extends Ressource { +export interface User extends Resource { type: 'user' lastname: string firstname: string diff --git a/src/models/utils/ref.ts b/src/models/utils/ref.ts index 847c3d9..1f541e0 100644 --- a/src/models/utils/ref.ts +++ b/src/models/utils/ref.ts @@ -1,28 +1,28 @@ -import type { Db } from '../../db/mod.ts' import type { UUID } from '../../../types.ts' +import type { Db } from '../../db/mod.ts' import { Credential, Group, Machine, - type Ressource, + type Resource, Service, User, } from '../mod.ts' -export type RefString = +export type RefString = | `@ref/${T['type']}#${UUID}` | Ref -export type RefResolver = ( +export type RefResolver = ( ref: RefString, ) => T | Promise -export class Ref extends String { - static #toString( +export class Ref extends String { + static #toString( { uuid, type }: { uuid: UUID; type: T['type'] }, ) { return `@ref/${type}#${uuid}` as const } - static parse( + static parse( string: RefString, ): { type: T['type']; uuid: UUID } { const [_, value] = string.split('/') @@ -31,28 +31,28 @@ export class Ref extends String { return { type, uuid } as const } - static fromRessource(ressource: T): Ref { - return new Ref(ressource) + static fromResource(resource: T): Ref { + return new Ref(resource) } - static fromString(string: RefString): Ref { + static fromString(string: RefString): Ref { return new Ref(Ref.parse(string)) } static dbResolver( db: Db, - ): (ref: RefString) => Promise { - return (ref: RefString): Promise => { + ): (ref: RefString) => Promise { + return (ref: RefString): Promise => { const { type, uuid } = Ref.parse(ref) //@ts-expect-error force type casting to fix - return db.ressource[type].get({ uuid }) + return db.resource[type].get({ uuid }) } } static restResolver( endpoint: string | URL, - ): (ref: RefString) => Promise { - return async (ref: RefString): Promise => { + ): (ref: RefString) => Promise { + return async (ref: RefString): Promise => { const { type, uuid } = Ref.parse(ref) const url = new URL(`${type}s/${uuid}`, endpoint) const response = await fetch(url)