Compare commits

..

4 commits

14 changed files with 119 additions and 127 deletions

1
.env.example Normal file
View file

@ -0,0 +1 @@
DB_PATH = './db.sqlite'

View file

@ -1,6 +1,6 @@
{ {
"name": "@cohabit/ressources", "name": "@cohabit/ressources",
"version": "0.1.1", "version": "0.1.2",
"exports": { "exports": {
".": "./mod.ts", ".": "./mod.ts",
"./models": "./src/models/mod.ts" "./models": "./src/models/mod.ts"
@ -14,16 +14,11 @@
"semiColons": false "semiColons": false
}, },
"imports": { "imports": {
"@/": "./",
"@models": "./src/models/mod.ts",
"@models/": "./src/models/src/",
"@api": "./src/api/server.ts",
"@api/": "./src/api/",
"@db": "./src/db/mod.ts",
"@db/": "./src/db/",
"@std/json": "jsr:@std/json@^0.223.0" "@std/json": "jsr:@std/json@^0.223.0"
}, },
"exclude": [ "exclude": [
"./docs" "./docs",
] "./tmp"
],
"unstable": ["kv"]
} }

4
mod.ts
View file

@ -1,2 +1,2 @@
export * from '@models' export * from './src/models/mod.ts'
export { Db } from '@db' export { Db } from './src/db/mod.ts'

View file

@ -6,15 +6,15 @@ import {
type Ressource, type Ressource,
Service, Service,
User, User,
} from '@models' } from '../models/mod.ts'
import type { CredentialCategory } from '@models/credential.ts' import type { CredentialCategory } from '../models/src/credential.ts'
//!TODO link ressources (get, list) //!TODO link ressources (get, list)
//!TODO Purge unused ressources (delete) //!TODO Purge unused ressources (delete)
export class Db { export class Db {
#kv: Deno.Kv #kv: Deno.Kv
static async init(path?: string) { static async init(path?: string): Promise<Db> {
const kv = await Deno.openKv(path ?? Deno.env.get('DB_PATH')) const kv = await Deno.openKv(path ?? Deno.env.get('DB_PATH'))
return new Db(kv) return new Db(kv)
} }
@ -23,11 +23,11 @@ export class Db {
this.#kv = kv this.#kv = kv
} }
get storage() { get storage(): Deno.Kv {
return this.#kv return this.#kv
} }
get prefix() { get prefix(): { ressource: 'ressource' } {
return { return {
ressource: 'ressource', ressource: 'ressource',
} }

View file

@ -1,11 +1,7 @@
export { export { Ref, type RefResolver, type RefString } from './utils/ref.ts'
Ref, export { Credential } from './src/credential.ts'
type RefResolver, export { Group } from './src/group.ts'
type RefString, export { Machine } from './src/machine.ts'
} from '@/src/models/utils/ref.ts' export type { Ressource } from './src/ressource.ts'
export { Credential } from '@models/credential.ts' export { Service } from './src/service.ts'
export { Group } from '@models/group.ts' export { User } from './src/user.ts'
export { Machine } from '@models/machine.ts'
export type { Ressource } from '@models/ressource.ts'
export { Service } from '@models/service.ts'
export { User } from '@models/user.ts'

View file

@ -1,7 +1,7 @@
import type { Base64String, ToJson, UUID } from '@/types.ts' import type { Base64String, ToJson, UUID } from '../../../types.ts'
import { Ressource } from '@models/ressource.ts' import { Ressource } from './ressource.ts'
import type { Ref } from '@models' import type { Ref } from '../utils/ref.ts'
import { Avatar } from '@/src/models/utils/avatar.ts' import { Avatar } from '../utils/avatar.ts'
export class Credential<T extends CredentialCategory> extends Ressource { export class Credential<T extends CredentialCategory> extends Ressource {
static fromJSON<T extends CredentialCategory>( static fromJSON<T extends CredentialCategory>(
@ -65,11 +65,11 @@ export class Credential<T extends CredentialCategory> extends Ressource {
return 'credential' return 'credential'
} }
get category() { get category(): T {
return this.#category return this.#category
} }
get store() { get store(): Readonly<CredentialStore<T>['store']> {
return this.#store return this.#store
} }

View file

@ -1,7 +1,7 @@
import type { Posix, ToJson, UUID } from '@/types.ts' import type { Posix, ToJson, UUID } from '../../../types.ts'
import { Ressource } from '@models/ressource.ts' import { Ressource } from './ressource.ts'
import { Ref } from '@models' import { Ref } from '../utils/ref.ts'
import { Avatar } from '@/src/models/utils/avatar.ts' import { Avatar } from '../utils/avatar.ts'
export class Group extends Ressource { export class Group extends Ressource {
static fromJSON( static fromJSON(
@ -50,13 +50,7 @@ export class Group extends Ressource {
} }
#posix?: Posix #posix?: Posix
#permissions: Readonly<{ #permissions: Readonly<GroupPermissions>
[serviceOrMachine: UUID]: {
read: boolean
write: boolean
execute: boolean
}
}>
#groups: readonly Ref<Group>[] #groups: readonly Ref<Group>[]
private constructor( private constructor(
@ -93,15 +87,15 @@ export class Group extends Ressource {
return 'group' return 'group'
} }
get posix() { get posix(): Posix | undefined {
return this.#posix return this.#posix
} }
get permissions() { get permissions(): Readonly<GroupPermissions> {
return this.#permissions return this.#permissions
} }
get groups() { get groups(): readonly Ref<Group>[] {
return this.#groups return this.#groups
} }
@ -134,11 +128,13 @@ export interface Group extends Ressource {
type: 'group' type: 'group'
posix: Posix | undefined posix: Posix | undefined
groups: readonly Ref<Group>[] groups: readonly Ref<Group>[]
permissions: Readonly<{ permissions: Readonly<GroupPermissions>
[serviceOrMachine: UUID]: { }
read: boolean
write: boolean export type GroupPermissions = {
execute: boolean [serviceOrMachine: UUID]: {
} read: boolean
}> write: boolean
execute: boolean
}
} }

View file

@ -1,8 +1,8 @@
import { Ref } from '@/src/models/utils/ref.ts' import { Ref } from '../utils/ref.ts'
import type { ToJson, UrlString } from '@/types.ts' import type { ToJson, UrlString } from '../../../types.ts'
import type { Group } from '@models/group.ts' import type { Group } from './group.ts'
import { Ressource } from '@models/ressource.ts' import { Ressource } from './ressource.ts'
import { Avatar } from '@/src/models/utils/avatar.ts' import { Avatar } from '../utils/avatar.ts'
export class Machine extends Ressource { export class Machine extends Ressource {
static fromJSON( static fromJSON(
@ -14,7 +14,7 @@ export class Machine extends Ressource {
} }
static create( static create(
{ tags, url, status, groups, avatar }: Pick< { tags, url, status, groups, avatar, name }: Pick<
Machine, Machine,
'name' | 'tags' | 'url' | 'status' | 'avatar' | 'groups' 'name' | 'tags' | 'url' | 'status' | 'avatar' | 'groups'
>, >,
@ -53,13 +53,7 @@ export class Machine extends Ressource {
#tags: readonly string[] #tags: readonly string[]
#url: UrlString #url: UrlString
#status: #status: MachineStatus
| 'ready'
| 'busy'
| 'unavailable'
| 'discontinued'
| 'error'
| 'unknown'
#groups: readonly Ref<Group>[] #groups: readonly Ref<Group>[]
private constructor( private constructor(
@ -83,16 +77,16 @@ export class Machine extends Ressource {
get type(): 'machine' { get type(): 'machine' {
return 'machine' return 'machine'
} }
get tags() { get tags(): readonly string[] {
return this.#tags return this.#tags
} }
get url() { get url(): UrlString {
return this.#url return this.#url
} }
get status() { get status(): MachineStatus {
return this.#status return this.#status
} }
get groups() { get groups(): readonly Ref<Group>[] {
return this.#groups return this.#groups
} }
@ -128,12 +122,14 @@ export interface Machine extends Ressource {
type: 'machine' type: 'machine'
tags: readonly string[] tags: readonly string[]
url: UrlString url: UrlString
status: status: MachineStatus
| 'ready'
| 'busy'
| 'unavailable'
| 'discontinued'
| 'error'
| 'unknown'
groups: readonly Ref<Group>[] groups: readonly Ref<Group>[]
} }
export type MachineStatus =
| 'ready'
| 'busy'
| 'unavailable'
| 'discontinued'
| 'error'
| 'unknown'

View file

@ -1,6 +1,6 @@
import { Ref } from '@/src/models/utils/ref.ts' import { Ref } from '../utils/ref.ts'
import type { DateString, ToJson, UrlString, 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 {
static fromJSON( static fromJSON(

View file

@ -1,8 +1,8 @@
import { Ref } from '@/src/models/utils/ref.ts' import { Ref } from '../utils/ref.ts'
import type { ToJson, UrlString } from '@/types.ts' import type { ToJson, UrlString } from '../../../types.ts'
import type { Group } from '@models/group.ts' import type { Group } from './group.ts'
import { Ressource } from '@models/ressource.ts' import { Ressource } from './ressource.ts'
import { Avatar } from '@/src/models/utils/avatar.ts' import { Avatar } from '../utils/avatar.ts'
export class Service extends Ressource { export class Service extends Ressource {
static fromJSON( static fromJSON(
@ -52,7 +52,7 @@ export class Service extends Ressource {
return this.create({ name, avatar, tags, url, category, groups }) return this.create({ name, avatar, tags, url, category, groups })
} }
#category: 'web' | 'fs' | 'git' #category: ServiceCategory
#url: UrlString #url: UrlString
#groups: readonly Ref<Group>[] #groups: readonly Ref<Group>[]
#tags: readonly string[] #tags: readonly string[]
@ -92,19 +92,19 @@ export class Service extends Ressource {
return 'service' return 'service'
} }
get category() { get category(): ServiceCategory {
return this.#category return this.#category
} }
get tags() { get tags(): readonly string[] {
return this.#tags return this.#tags
} }
get url() { get url(): UrlString {
return this.#url return this.#url
} }
get groups() { get groups(): readonly Ref<Group>[] {
return this.#groups return this.#groups
} }
@ -138,8 +138,10 @@ export class Service extends Ressource {
export interface Service extends Ressource { export interface Service extends Ressource {
type: 'service' type: 'service'
category: 'web' | 'fs' | 'git' category: ServiceCategory
tags: readonly string[] tags: readonly string[]
url: UrlString url: UrlString
groups: readonly Ref<Group>[] groups: readonly Ref<Group>[]
} }
export type ServiceCategory = 'web' | 'fs' | 'git'

View file

@ -1,10 +1,10 @@
import { Ref } from '@/src/models/utils/ref.ts' import { Ref } from '../utils/ref.ts'
import type { Login, MailAddress, Posix, ToJson } from '@/types.ts' import type { Login, MailAddress, Posix, ToJson } from '../../../types.ts'
import { regex, toLogin } from '@/utils.ts' import { regex, toLogin } from '../../../utils.ts'
import type { Credential, CredentialCategory } from '@models/credential.ts' import type { Credential, CredentialCategory } from './credential.ts'
import type { Group } from '@models/group.ts' import type { Group } from './group.ts'
import { Ressource } from '@models/ressource.ts' import { Ressource } from './ressource.ts'
import { Avatar } from '@/src/models/utils/avatar.ts' import { Avatar } from '../utils/avatar.ts'
export class User extends Ressource { export class User extends Ressource {
static fromJSON(json: ToJson<User>): User { static fromJSON(json: ToJson<User>): User {
@ -150,25 +150,25 @@ export class User extends Ressource {
get type(): 'user' { get type(): 'user' {
return 'user' return 'user'
} }
get firstname() { get firstname(): string {
return this.#firstname return this.#firstname
} }
get lastname() { get lastname(): string {
return this.#lastname return this.#lastname
} }
get login() { get login(): Login {
return this.#login return this.#login
} }
get mail() { get mail(): MailAddress {
return this.#mail return this.#mail
} }
get groups() { get groups(): readonly Ref<Group>[] {
return this.#groups return this.#groups
} }
get posix() { get posix(): Posix | undefined {
return this.#posix return this.#posix
} }
get credentials() { get credentials(): readonly Ref<Credential<CredentialCategory>>[] {
return this.#credentials return this.#credentials
} }

View file

@ -1,4 +1,4 @@
import type { UrlString } from '@/types.ts' import type { UrlString } from '../../../types.ts'
export class Avatar { export class Avatar {
static fromEmoji(emoji: string): UrlString { static fromEmoji(emoji: string): UrlString {

View file

@ -1,5 +1,5 @@
import type { Db } from '@/mod.ts' import type { Db } from '../../db/mod.ts'
import type { UUID } from '@/types.ts' import type { UUID } from '../../../types.ts'
import { import {
Credential, Credential,
Group, Group,
@ -7,7 +7,7 @@ import {
type Ressource, type Ressource,
Service, Service,
User, User,
} from '@models' } from '../mod.ts'
export type RefString<T extends Ressource> = export type RefString<T extends Ressource> =
| `@ref/${T['type']}#${UUID}` | `@ref/${T['type']}#${UUID}`
@ -22,7 +22,9 @@ export class Ref<T extends Ressource> extends String {
return `@ref/${type}#${uuid}` as const return `@ref/${type}#${uuid}` as const
} }
static parse<T extends Ressource>(string: RefString<T>) { static parse<T extends Ressource>(
string: RefString<T>,
): { type: T['type']; uuid: UUID } {
const [_, value] = string.split('/') const [_, value] = string.split('/')
const [type, uuid] = value.split('#') as [T['type'], UUID] const [type, uuid] = value.split('#') as [T['type'], UUID]
@ -33,11 +35,13 @@ export class Ref<T extends Ressource> extends String {
return new Ref<T>(ressource) return new Ref<T>(ressource)
} }
static fromString<T extends Ressource>(string: RefString<T>) { static fromString<T extends Ressource>(string: RefString<T>): Ref<T> {
return new Ref<T>(Ref.parse(string)) return new Ref<T>(Ref.parse(string))
} }
static dbResolver(db: Db) { static dbResolver(
db: Db,
): <T extends Ressource>(ref: RefString<T>) => Promise<T> {
return <T extends Ressource>(ref: RefString<T>): Promise<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 //@ts-expect-error force type casting to fix
@ -45,27 +49,29 @@ export class Ref<T extends Ressource> extends String {
} }
} }
static restResolver(endpoint: string | URL) { static restResolver(
return async <T extends Ressource>(ref: RefString<T>) => { endpoint: string | URL,
): <T extends Ressource>(ref: RefString<T>) => Promise<T> {
return async <T extends Ressource>(ref: RefString<T>): Promise<T> => {
const { type, uuid } = Ref.parse(ref) const { type, uuid } = Ref.parse(ref)
const url = new URL(`${type}s/${uuid}`, endpoint) const url = new URL(`${type}s/${uuid}`, endpoint)
const response = await fetch(url) const response = await fetch(url)
const json = await response.json() const json = await response.json()
if (type === 'user') { if (type === 'user') {
return User.fromJSON(json) return User.fromJSON(json) as unknown as T
} }
if (type === 'machine') { if (type === 'machine') {
return Machine.fromJSON(json) return Machine.fromJSON(json) as unknown as T
} }
if (type === 'service') { if (type === 'service') {
return Service.fromJSON(json) return Service.fromJSON(json) as unknown as T
} }
if (type === 'group') { if (type === 'group') {
return Group.fromJSON(json) return Group.fromJSON(json) as unknown as T
} }
if (type === 'credential') { if (type === 'credential') {
return Credential.fromJSON(json) return Credential.fromJSON(json) as unknown as T
} }
throw new TypeError(`unknown ref type "${type}"`) throw new TypeError(`unknown ref type "${type}"`)
@ -81,23 +87,23 @@ export class Ref<T extends Ressource> extends String {
#type: T['type'] #type: T['type']
#uuid: UUID #uuid: UUID
get type() { get type(): T['type'] {
return this.#type return this.#type
} }
get uuid() { get uuid(): UUID {
return this.#uuid return this.#uuid
} }
ref(resolver: RefResolver<T>) { ref(resolver: RefResolver<T>): T | Promise<T> {
return resolver(this.toString()) return resolver(this.toString())
} }
toString() { toString(): `@ref/${T['type']}#${UUID}` {
return Ref.#toString<T>({ uuid: this.uuid, type: this.type }) return Ref.#toString<T>({ uuid: this.uuid, type: this.type })
} }
toJSON() { toJSON(): `@ref/${T['type']}#${UUID}` {
return this.toString() return this.toString()
} }
} }

View file

@ -1,4 +1,4 @@
import type { Login } from '@/types.ts' import type { Login } from './types.ts'
export function toLogin({ export function toLogin({
firstname, firstname,