Compare commits
11 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
524ce67b7c | ||
|
|
e652336485 | ||
|
|
90cfd257aa | ||
|
|
2209a09c26 | ||
|
|
8b30cf2992 | ||
|
|
3c4c2c517d | ||
|
|
9bb122f813 | ||
|
|
ae7b911b16 | ||
|
|
94632dbc07 | ||
|
|
e4b2f335a5 | ||
|
|
11bdf8d53b |
1
.env.example
Normal file
1
.env.example
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
DB_PATH = './db.sqlite'
|
||||||
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
|
@ -1,3 +1,3 @@
|
||||||
{
|
{
|
||||||
"conventionalCommits.scopes": ["model", "chore", "db"]
|
"conventionalCommits.scopes": ["model", "chore", "db", "task", "api"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
24
deno.json
24
deno.json
|
|
@ -1,12 +1,13 @@
|
||||||
{
|
{
|
||||||
"name": "@cohabit/ressources",
|
"name": "@cohabit/resources-manager",
|
||||||
"version": "0.1.1",
|
"version": "0.2.1",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./mod.ts",
|
"./models": "./src/models/mod.ts",
|
||||||
"./models": "./src/models/mod.ts"
|
"./db": "./src/db/mod.ts",
|
||||||
|
"./types": "./types.ts"
|
||||||
},
|
},
|
||||||
"tasks": {
|
"tasks": {
|
||||||
"start": "deno run --unstable-kv ./mod.ts"
|
"start": "deno run ./mod.ts"
|
||||||
},
|
},
|
||||||
"fmt": {
|
"fmt": {
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
|
|
@ -14,16 +15,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,6 +4,7 @@
|
||||||
"specifiers": {
|
"specifiers": {
|
||||||
"jsr:@std/json@^0.223.0": "jsr:@std/json@0.223.0",
|
"jsr:@std/json@^0.223.0": "jsr:@std/json@0.223.0",
|
||||||
"jsr:@std/streams@^0.223.0": "jsr:@std/streams@0.223.0",
|
"jsr:@std/streams@^0.223.0": "jsr:@std/streams@0.223.0",
|
||||||
|
"npm:iterator-polyfill": "npm:iterator-polyfill@1.0.9",
|
||||||
"npm:superjson@1.13.3": "npm:superjson@1.13.3"
|
"npm:superjson@1.13.3": "npm:superjson@1.13.3"
|
||||||
},
|
},
|
||||||
"jsr": {
|
"jsr": {
|
||||||
|
|
@ -28,6 +29,10 @@
|
||||||
"integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==",
|
"integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==",
|
||||||
"dependencies": {}
|
"dependencies": {}
|
||||||
},
|
},
|
||||||
|
"iterator-polyfill@1.0.9": {
|
||||||
|
"integrity": "sha512-YKBrosdKd9nqEaJwpYZjlnax8WtLwCbTo7gnUXrQ3ARA92vD2W8hdOWCRzvotHByDRCvPrFvEFF4M3V53lwAHQ==",
|
||||||
|
"dependencies": {}
|
||||||
|
},
|
||||||
"superjson@1.13.3": {
|
"superjson@1.13.3": {
|
||||||
"integrity": "sha512-mJiVjfd2vokfDxsQPOwJ/PtanO87LhpYY88ubI5dUB1Ab58Txbyje3+jpm+/83R/fevaq/107NNhtYBLuoTrFg==",
|
"integrity": "sha512-mJiVjfd2vokfDxsQPOwJ/PtanO87LhpYY88ubI5dUB1Ab58Txbyje3+jpm+/83R/fevaq/107NNhtYBLuoTrFg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
||||||
56
src/api/hander.ts
Normal file
56
src/api/hander.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
import type { Db } from '../db/mod.ts'
|
||||||
|
import type { Resource } from '../models/mod.ts'
|
||||||
|
import type { UUID } from '../../types.ts'
|
||||||
|
import type { ResourceBuilder, ResourceType } from './types.ts'
|
||||||
|
import { respondJson } from './utils.ts'
|
||||||
|
|
||||||
|
export async function resourceHandler<T extends Resource>(
|
||||||
|
type: ResourceType<T>,
|
||||||
|
Builder: ResourceBuilder<T>,
|
||||||
|
db: Db,
|
||||||
|
req: Request,
|
||||||
|
id?: string,
|
||||||
|
): Promise<Response> {
|
||||||
|
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`))
|
||||||
|
}
|
||||||
81
src/api/server.ts
Normal file
81
src/api/server.ts
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
// /(users|groups|machines)/:?id
|
||||||
|
import { stripTypeS } from '../api/utils.ts'
|
||||||
|
import type { Db } from '../db/mod.ts'
|
||||||
|
import { Credential, Group, Machine, Service, User } from '../models/mod.ts'
|
||||||
|
import type { CredentialCategory } from '../models/src/credential.ts'
|
||||||
|
import { resourceHandler } from './hander.ts'
|
||||||
|
|
||||||
|
export class Api {
|
||||||
|
static init(db: Db, { base = '/' }: { base: `/${string}` }) {
|
||||||
|
return new Api({ db, base })
|
||||||
|
}
|
||||||
|
|
||||||
|
#db: Db
|
||||||
|
#base: `/${string}`
|
||||||
|
|
||||||
|
private constructor({ db, base }: { db: Db; base: `/${string}` }) {
|
||||||
|
this.#db = db
|
||||||
|
this.#base = base
|
||||||
|
}
|
||||||
|
|
||||||
|
serve() {
|
||||||
|
return Deno.serve((req) => {
|
||||||
|
const url = new URL(req.url)
|
||||||
|
|
||||||
|
if (url.pathname.startsWith(this.#base)) {
|
||||||
|
const base = new URL(this.#base, req.url)
|
||||||
|
return router(req, base, this.#db)
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(null, {
|
||||||
|
status: 301,
|
||||||
|
statusText: 'Not allowed base url',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function router(req: Request, base: URL, db: Db): Promise<Response> {
|
||||||
|
const pattern = new URLPattern({ pathname: '/:type/:id?' }, base.href)
|
||||||
|
const match = pattern.exec(req.url)
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
return Promise.resolve(
|
||||||
|
new Response(null, {
|
||||||
|
status: 400,
|
||||||
|
statusText: 'Bad request',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { type, id } = match.pathname.groups as {
|
||||||
|
type: string
|
||||||
|
id: string | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'credentials':
|
||||||
|
return resourceHandler<Credential<CredentialCategory>>(
|
||||||
|
stripTypeS(type),
|
||||||
|
Credential,
|
||||||
|
db,
|
||||||
|
req,
|
||||||
|
id,
|
||||||
|
)
|
||||||
|
case 'groups':
|
||||||
|
return resourceHandler<Group>(stripTypeS(type), Group, db, req, id)
|
||||||
|
case 'machines':
|
||||||
|
return resourceHandler<Machine>(stripTypeS(type), Machine, db, req, id)
|
||||||
|
case 'services':
|
||||||
|
return resourceHandler<Service>(stripTypeS(type), Service, db, req, id)
|
||||||
|
case 'users':
|
||||||
|
return resourceHandler<User>(stripTypeS(type), User, db, req, id)
|
||||||
|
default:
|
||||||
|
return Promise.resolve(
|
||||||
|
new Response(null, {
|
||||||
|
status: 400,
|
||||||
|
statusText: 'Bad request',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/api/types.ts
Normal file
29
src/api/types.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import type {
|
||||||
|
Credential,
|
||||||
|
Group,
|
||||||
|
Machine,
|
||||||
|
Resource,
|
||||||
|
Service,
|
||||||
|
User,
|
||||||
|
} from '../../src/models/mod.ts'
|
||||||
|
import type { JsonValue } from '@std/json'
|
||||||
|
import type { CredentialCategory } from '../models/src/credential.ts'
|
||||||
|
|
||||||
|
export type ResourceBuilder<T extends Resource> = T extends
|
||||||
|
Credential<CredentialCategory> ? typeof Credential
|
||||||
|
: T extends Group ? typeof Group
|
||||||
|
: T extends Machine ? typeof Machine
|
||||||
|
: T extends Service ? typeof Service
|
||||||
|
: T extends User ? typeof User
|
||||||
|
: never
|
||||||
|
|
||||||
|
export type ResourceType<T extends Resource> = T extends
|
||||||
|
Credential<CredentialCategory> ? 'credential'
|
||||||
|
: T extends Group ? 'group'
|
||||||
|
: T extends Machine ? 'machine'
|
||||||
|
: T extends Service ? 'service'
|
||||||
|
: T extends User ? 'user'
|
||||||
|
: never
|
||||||
|
|
||||||
|
export type JsonObject = Record<string, JsonValue>
|
||||||
|
export type JsonStringifiable = { toJSON(): JsonObject } | JsonObject
|
||||||
38
src/api/utils.ts
Normal file
38
src/api/utils.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import type { JsonStringifiable } from '../api/types.ts'
|
||||||
|
|
||||||
|
export function respondJson<T extends JsonStringifiable | Error>(
|
||||||
|
valueOrError: T,
|
||||||
|
): Response {
|
||||||
|
if (valueOrError instanceof Error) {
|
||||||
|
const { name, cause, message } = valueOrError
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
result: 'error',
|
||||||
|
error: { name, cause, message },
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8',
|
||||||
|
},
|
||||||
|
status: 500,
|
||||||
|
statusText: valueOrError.message,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
result: 'ok',
|
||||||
|
value: valueOrError,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stripTypeS<T extends string>(typeS: `${T}s`): T {
|
||||||
|
return typeS.slice(0, -1) as T
|
||||||
|
}
|
||||||
131
src/db/mod.ts
131
src/db/mod.ts
|
|
@ -3,18 +3,18 @@ import {
|
||||||
Group,
|
Group,
|
||||||
Machine,
|
Machine,
|
||||||
type Ref,
|
type Ref,
|
||||||
type Ressource,
|
type Resource,
|
||||||
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 resources (get, list)
|
||||||
//!TODO Purge unused ressources (delete)
|
//!TODO Purge unused resources (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,124 +23,123 @@ 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(): { resource: 'resource' } {
|
||||||
return {
|
return {
|
||||||
ressource: 'ressource',
|
resource: 'resource',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get ressource() {
|
get resource(): ResourceAccessors {
|
||||||
return {
|
return {
|
||||||
credential: this.#ressourceStorage<Credential<CredentialCategory>>(
|
credential: this.#resourceStorage<Credential<CredentialCategory>>(
|
||||||
'credential',
|
'credential',
|
||||||
Credential,
|
Credential,
|
||||||
),
|
),
|
||||||
group: this.#ressourceStorage<Group>('group', Group),
|
group: this.#resourceStorage<Group>('group', Group),
|
||||||
machine: this.#ressourceStorage<Machine>('machine', Machine),
|
machine: this.#resourceStorage<Machine>('machine', Machine),
|
||||||
service: this.#ressourceStorage<Service>('service', Service),
|
service: this.#resourceStorage<Service>('service', Service),
|
||||||
user: this.#ressourceStorage<User>('user', User),
|
user: this.#resourceStorage<User>('user', User),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ressourceStorage<T extends Ressource>(
|
#resourceStorage<T extends Resource>(
|
||||||
type: RessourceType<T>,
|
type: ResourceType<T>,
|
||||||
Builder: RessourceBuilder<T>,
|
Builder: ResourceBuilder<T>,
|
||||||
) {
|
): ResourceStorage<T> {
|
||||||
return {
|
return {
|
||||||
get: (ressource: Pick<T, 'uuid'>) =>
|
get: (resource: Pick<T, 'uuid'>) => this.#get<T>(type, Builder, resource),
|
||||||
this.#get<T>(type, Builder, ressource),
|
set: (resources: T[]) => this.#set<T>(type, resources),
|
||||||
set: (ressources: T[]) => this.#set<T>(type, ressources),
|
delete: (resources: Pick<T, 'uuid'>[]) =>
|
||||||
delete: (ressources: Pick<T, 'uuid'>[]) =>
|
this.#delete<T>(type, resources),
|
||||||
this.#delete<T>(type, ressources),
|
|
||||||
list: (
|
list: (
|
||||||
filter: (ressource: T) => boolean | Promise<boolean> = () => true,
|
filter: (resource: T) => boolean | Promise<boolean> = () => true,
|
||||||
) => this.#list<T>(type, Builder, filter),
|
) => this.#list<T>(type, Builder, filter),
|
||||||
listRef: async (
|
listRef: async (
|
||||||
filter: (ressource: T) => boolean | Promise<boolean> = () => true,
|
filter: (resource: T) => boolean | Promise<boolean> = () => true,
|
||||||
) => {
|
) => {
|
||||||
const ressources: Ref<T>[] = []
|
const resources: Ref<T>[] = []
|
||||||
for await (const ressource of this.#list<T>(type, Builder, filter)) {
|
for await (const resource of this.#list<T>(type, Builder, filter)) {
|
||||||
ressources.push(ressource.toRef() as Ref<T>)
|
resources.push(resource.toRef() as Ref<T>)
|
||||||
}
|
}
|
||||||
return ressources
|
return resources
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async #get<T extends Ressource>(
|
async #get<T extends Resource>(
|
||||||
type: RessourceType<T>,
|
type: ResourceType<T>,
|
||||||
Builder: RessourceBuilder<T>,
|
Builder: ResourceBuilder<T>,
|
||||||
entry: Pick<T, 'uuid'>,
|
entry: Pick<T, 'uuid'>,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const json = await this.#kv.get<RessourceJson<T>>([
|
const json = await this.#kv.get<ResourceJson<T>>([
|
||||||
this.prefix.ressource,
|
this.prefix.resource,
|
||||||
type,
|
type,
|
||||||
entry.uuid,
|
entry.uuid,
|
||||||
])
|
])
|
||||||
if (json.value) {
|
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)
|
return Builder.fromJSON(json.value)
|
||||||
}
|
}
|
||||||
throw new ReferenceError(
|
throw new ReferenceError(
|
||||||
`no ressource.${type} was found with uuid: "${entry.uuid}"`,
|
`no resource.${type} was found with uuid: "${entry.uuid}"`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
#set<T extends Ressource>(type: RessourceType<T>, entries: T[]) {
|
#set<T extends Resource>(type: ResourceType<T>, entries: T[]) {
|
||||||
const atomic = this.#kv.atomic()
|
const atomic = this.#kv.atomic()
|
||||||
|
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
//! TODO check if `refs` exists
|
//! 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())
|
atomic.set(key, entry.toJSON())
|
||||||
}
|
}
|
||||||
|
|
||||||
return atomic.commit()
|
return atomic.commit()
|
||||||
}
|
}
|
||||||
#delete<T extends Ressource>(
|
#delete<T extends Resource>(
|
||||||
type: RessourceType<T>,
|
type: ResourceType<T>,
|
||||||
entries: Pick<T, 'uuid'>[],
|
entries: Pick<T, 'uuid'>[],
|
||||||
): Promise<Deno.KvCommitResult | Deno.KvCommitError> {
|
): Promise<Deno.KvCommitResult | Deno.KvCommitError> {
|
||||||
const atomic = this.#kv.atomic()
|
const atomic = this.#kv.atomic()
|
||||||
|
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
//! TODO check if `refs` exists
|
//! TODO check if `refs` exists
|
||||||
atomic.delete([this.prefix.ressource, type, entry.uuid])
|
atomic.delete([this.prefix.resource, type, entry.uuid])
|
||||||
}
|
}
|
||||||
|
|
||||||
return atomic.commit()
|
return atomic.commit()
|
||||||
}
|
}
|
||||||
async *#list<T extends Ressource>(
|
async *#list<T extends Resource>(
|
||||||
type: RessourceType<T>,
|
type: ResourceType<T>,
|
||||||
Builder: RessourceBuilder<T>,
|
Builder: ResourceBuilder<T>,
|
||||||
filter: (entry: T) => boolean | Promise<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<ResourceJson<T>>({
|
||||||
prefix: [this.prefix.ressource, type],
|
prefix: [this.prefix.resource, type],
|
||||||
})
|
})
|
||||||
for await (const entry of list) {
|
for await (const entry of list) {
|
||||||
const value = entry.value
|
const value = entry.value
|
||||||
//@ts-expect-error Type union of Ressource types for Builder
|
//@ts-expect-error Type union of Resource types for Builder
|
||||||
const ressource = Builder.fromJSON(value) as T
|
const resource = Builder.fromJSON(value) as T
|
||||||
if (await filter(ressource)) {
|
if (await filter(resource)) {
|
||||||
yield ressource
|
yield resource
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type RessourceType<T extends Ressource> = T extends
|
type ResourceType<T extends Resource> = T extends Credential<CredentialCategory>
|
||||||
Credential<CredentialCategory> ? 'credential'
|
? '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
|
type ResourceBuilder<T extends Resource> = T extends
|
||||||
Credential<CredentialCategory> ? 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
|
||||||
|
|
@ -148,4 +147,26 @@ type RessourceBuilder<T extends Ressource> = T extends
|
||||||
: T extends User ? typeof User
|
: T extends User ? typeof User
|
||||||
: never
|
: never
|
||||||
|
|
||||||
type RessourceJson<T extends Ressource> = ReturnType<T['toJSON']>
|
type ResourceJson<T extends Resource> = ReturnType<T['toJSON']>
|
||||||
|
|
||||||
|
type ResourceStorage<T extends Resource> = {
|
||||||
|
get: (resource: Pick<T, 'uuid'>) => Promise<T>
|
||||||
|
set: (resources: T[]) => Promise<Deno.KvCommitResult | Deno.KvCommitError>
|
||||||
|
delete: (
|
||||||
|
resources: Pick<T, 'uuid'>[],
|
||||||
|
) => Promise<Deno.KvCommitResult | Deno.KvCommitError>
|
||||||
|
list: (
|
||||||
|
filter?: (resource: T) => boolean | Promise<boolean>,
|
||||||
|
) => AsyncGenerator<T, void, void>
|
||||||
|
listRef: (
|
||||||
|
filter?: (resource: T) => boolean | Promise<boolean>,
|
||||||
|
) => Promise<Ref<T>[]>
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResourceAccessors = {
|
||||||
|
credential: ResourceStorage<Credential<CredentialCategory>>
|
||||||
|
group: ResourceStorage<Group>
|
||||||
|
machine: ResourceStorage<Machine>
|
||||||
|
service: ResourceStorage<Service>
|
||||||
|
user: ResourceStorage<User>
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
export {
|
export {
|
||||||
Ref,
|
Credential,
|
||||||
type RefResolver,
|
type CredentialCategory,
|
||||||
type RefString,
|
type CredentialStore,
|
||||||
} from '@/src/models/utils/ref.ts'
|
type Passkey,
|
||||||
export { Credential } from '@models/credential.ts'
|
} from './src/credential.ts'
|
||||||
export { Group } from '@models/group.ts'
|
export { Group, type GroupPermissions } from './src/group.ts'
|
||||||
export { Machine } from '@models/machine.ts'
|
export { Machine, type MachineStatus } from './src/machine.ts'
|
||||||
export type { Ressource } from '@models/ressource.ts'
|
export type { Resource, ResourceJson, ResourceRefJson } from './src/resource.ts'
|
||||||
export { Service } from '@models/service.ts'
|
export { Service, type ServiceCategory } from './src/service.ts'
|
||||||
export { User } from '@models/user.ts'
|
export { User } from './src/user.ts'
|
||||||
|
export { Avatar } from './utils/avatar.ts'
|
||||||
|
export { Ref, type RefResolver, type RefString } from './utils/ref.ts'
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import type { Base64String, ToJson, UUID } from '@/types.ts'
|
import type { Base64String, ToJson, UUID } from '../../../types.ts'
|
||||||
import { Ressource } from '@models/ressource.ts'
|
import { Avatar } from '../utils/avatar.ts'
|
||||||
import type { Ref } from '@models'
|
import type { Ref } from '../utils/ref.ts'
|
||||||
import { Avatar } from '@/src/models/utils/avatar.ts'
|
import { Resource, type ResourceJson } from './resource.ts'
|
||||||
|
|
||||||
export class Credential<T extends CredentialCategory> extends Ressource {
|
export class Credential<T extends CredentialCategory> extends Resource {
|
||||||
static fromJSON<T extends CredentialCategory>(
|
static fromJSON<T extends CredentialCategory>(
|
||||||
json: ToJson<Credential<T>>,
|
json: ToJson<Credential<T>>,
|
||||||
): Credential<T> {
|
): Credential<T> {
|
||||||
|
|
@ -48,7 +48,7 @@ export class Credential<T extends CredentialCategory> extends Ressource {
|
||||||
private constructor(
|
private constructor(
|
||||||
{ category, store, ...props }:
|
{ category, store, ...props }:
|
||||||
& Pick<Credential<T>, 'category' | 'store'>
|
& Pick<Credential<T>, 'category' | 'store'>
|
||||||
& Pick<Ressource, 'name' | 'uuid' | 'avatar' | 'createdAt' | 'updatedAt'>,
|
& Pick<Resource, 'name' | 'uuid' | 'avatar' | 'createdAt' | 'updatedAt'>,
|
||||||
) {
|
) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
|
|
@ -65,23 +65,31 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
update<T extends CredentialCategory>(
|
update(
|
||||||
props: Partial<Omit<Credential<T>, 'type' | 'uuid' | 'createdAt'>>,
|
props: Partial<Omit<Credential<T>, 'type' | 'uuid' | 'createdAt'>>,
|
||||||
): Credential<T> {
|
): Credential<T> {
|
||||||
const { updatedAt } = super.update(props)
|
const { updatedAt } = super.update(props)
|
||||||
const credential = new Credential<T>({ ...this, ...props, updatedAt })
|
return new Credential<T>({
|
||||||
return credential
|
uuid: this.uuid,
|
||||||
|
name: this.name,
|
||||||
|
avatar: this.avatar,
|
||||||
|
createdAt: this.createdAt,
|
||||||
|
category: this.category,
|
||||||
|
store: this.store,
|
||||||
|
...props,
|
||||||
|
updatedAt,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON() {
|
toJSON(): ResourceJson<Credential<T>, 'category' | 'store'> {
|
||||||
return {
|
return {
|
||||||
...super.toJSON(),
|
...super.toJSON(),
|
||||||
type: this.type,
|
type: this.type,
|
||||||
|
|
@ -99,7 +107,7 @@ export class Credential<T extends CredentialCategory> extends Ressource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Credential<T extends CredentialCategory> extends Ressource {
|
export interface Credential<T extends CredentialCategory> extends Resource {
|
||||||
type: 'credential'
|
type: 'credential'
|
||||||
category: T
|
category: T
|
||||||
store: Readonly<CredentialStore<T>['store']>
|
store: Readonly<CredentialStore<T>['store']>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
import type { Posix, ToJson, UUID } from '@/types.ts'
|
import type { Posix, ToJson, UUID } from '../../../types.ts'
|
||||||
import { Ressource } from '@models/ressource.ts'
|
import { Avatar } from '../utils/avatar.ts'
|
||||||
import { Ref } from '@models'
|
import { Ref } from '../utils/ref.ts'
|
||||||
import { Avatar } from '@/src/models/utils/avatar.ts'
|
import {
|
||||||
|
Resource,
|
||||||
|
type ResourceJson,
|
||||||
|
type ResourceRefJson,
|
||||||
|
} from './resource.ts'
|
||||||
|
|
||||||
export class Group extends Ressource {
|
export class Group extends Resource {
|
||||||
static fromJSON(
|
static fromJSON(
|
||||||
{ posix, groups, ...json }: ToJson<Group>,
|
{ posix, groups, ...json }: ToJson<Group>,
|
||||||
): Group {
|
): Group {
|
||||||
|
|
@ -50,19 +54,13 @@ 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(
|
||||||
{ posix, permissions, groups, ...props }:
|
{ posix, permissions, groups, ...props }:
|
||||||
& Pick<Group, 'posix' | 'permissions' | 'groups'>
|
& Pick<Group, 'posix' | 'permissions' | 'groups'>
|
||||||
& Pick<Ressource, 'name' | 'avatar' | 'uuid' | 'createdAt' | 'updatedAt'>,
|
& Pick<Resource, 'name' | 'avatar' | 'uuid' | 'createdAt' | 'updatedAt'>,
|
||||||
) {
|
) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
|
|
@ -93,25 +91,37 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
update(props: Partial<Omit<Group, 'type' | 'uuid' | 'createdAt'>>): Group {
|
update(props: Partial<Omit<Group, 'type' | 'uuid' | 'createdAt'>>): Group {
|
||||||
const { updatedAt } = super.update(props)
|
const { updatedAt } = super.update(props)
|
||||||
const group = new Group({ ...this, ...props, updatedAt })
|
return new Group({
|
||||||
return group
|
uuid: this.uuid,
|
||||||
|
name: this.name,
|
||||||
|
avatar: this.avatar,
|
||||||
|
createdAt: this.createdAt,
|
||||||
|
posix: this.posix,
|
||||||
|
permissions: this.permissions,
|
||||||
|
groups: this.groups,
|
||||||
|
...props,
|
||||||
|
updatedAt,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON() {
|
toJSON():
|
||||||
|
& ResourceJson<Group, 'permissions'>
|
||||||
|
& { posix: Posix | null }
|
||||||
|
& ResourceRefJson<Group, Group, 'groups'> {
|
||||||
return {
|
return {
|
||||||
...super.toJSON(),
|
...super.toJSON(),
|
||||||
type: this.type,
|
type: this.type,
|
||||||
|
|
@ -130,15 +140,17 @@ export class Group extends Ressource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Group extends Ressource {
|
export interface Group extends Resource {
|
||||||
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
import { Ref } from '@/src/models/utils/ref.ts'
|
import type { ToJson, UrlString } from '../../../types.ts'
|
||||||
import type { ToJson, UrlString } from '@/types.ts'
|
import { Avatar } from '../utils/avatar.ts'
|
||||||
import type { Group } from '@models/group.ts'
|
import { Ref } from '../utils/ref.ts'
|
||||||
import { Ressource } from '@models/ressource.ts'
|
import type { Group } from './group.ts'
|
||||||
import { Avatar } from '@/src/models/utils/avatar.ts'
|
import {
|
||||||
|
Resource,
|
||||||
|
type ResourceJson,
|
||||||
|
type ResourceRefJson,
|
||||||
|
} from './resource.ts'
|
||||||
|
|
||||||
export class Machine extends Ressource {
|
export class Machine extends Resource {
|
||||||
static fromJSON(
|
static fromJSON(
|
||||||
{ url, ...json }: ToJson<Machine>,
|
{ url, ...json }: ToJson<Machine>,
|
||||||
): Machine {
|
): Machine {
|
||||||
|
|
@ -14,7 +18,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,19 +57,13 @@ 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(
|
||||||
{ tags, url, status, 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' | 'avatar' | 'createdAt' | 'updatedAt'>,
|
& Pick<Resource, 'name' | 'uuid' | 'avatar' | 'createdAt' | 'updatedAt'>,
|
||||||
) {
|
) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
|
|
@ -83,16 +81,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,11 +98,23 @@ export class Machine extends Ressource {
|
||||||
props: Partial<Omit<Machine, 'type' | 'uuid' | 'createdAt'>>,
|
props: Partial<Omit<Machine, 'type' | 'uuid' | 'createdAt'>>,
|
||||||
): Machine {
|
): Machine {
|
||||||
const { updatedAt } = super.update(props)
|
const { updatedAt } = super.update(props)
|
||||||
const machine = new Machine({ ...this, ...props, updatedAt })
|
return new Machine({
|
||||||
return machine
|
uuid: this.uuid,
|
||||||
|
name: this.name,
|
||||||
|
avatar: this.avatar,
|
||||||
|
createdAt: this.createdAt,
|
||||||
|
tags: this.tags,
|
||||||
|
url: this.url,
|
||||||
|
status: this.status,
|
||||||
|
groups: this.groups,
|
||||||
|
...props,
|
||||||
|
updatedAt,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON() {
|
toJSON():
|
||||||
|
& ResourceJson<Machine, 'tags' | 'url' | 'status'>
|
||||||
|
& ResourceRefJson<Machine, Group, 'groups'> {
|
||||||
return {
|
return {
|
||||||
...super.toJSON(),
|
...super.toJSON(),
|
||||||
type: this.type,
|
type: this.type,
|
||||||
|
|
@ -124,16 +134,18 @@ export class Machine extends Ressource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Machine extends Ressource {
|
export interface Machine extends Resource {
|
||||||
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'
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import { Ref } from '@/src/models/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'
|
import { Ref } from '../utils/ref.ts'
|
||||||
|
|
||||||
export class Ressource {
|
export class Resource {
|
||||||
static fromJSON(
|
static fromJSON(
|
||||||
json: ToJson<Ressource>,
|
json: ToJson<Resource>,
|
||||||
): Ressource {
|
): Resource {
|
||||||
return new Ressource({
|
return new Resource({
|
||||||
name: json.name,
|
name: json.name,
|
||||||
avatar: json.avatar,
|
avatar: json.avatar,
|
||||||
uuid: json.uuid,
|
uuid: json.uuid,
|
||||||
|
|
@ -16,16 +16,16 @@ export class Ressource {
|
||||||
}
|
}
|
||||||
|
|
||||||
static create(
|
static create(
|
||||||
{ name, avatar }: Pick<Ressource, 'name' | 'avatar'>,
|
{ name, avatar }: Pick<Resource, 'name' | 'avatar'>,
|
||||||
): Ressource {
|
): Resource {
|
||||||
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, 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')
|
throw new Error('not implemented')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,7 +37,7 @@ export class Ressource {
|
||||||
|
|
||||||
protected constructor(
|
protected constructor(
|
||||||
{ name, avatar, uuid, createdAt, updatedAt }: Pick<
|
{ name, avatar, uuid, createdAt, updatedAt }: Pick<
|
||||||
Ressource,
|
Resource,
|
||||||
'name' | 'avatar' | 'uuid' | 'createdAt' | 'updatedAt'
|
'name' | 'avatar' | 'uuid' | 'createdAt' | 'updatedAt'
|
||||||
>,
|
>,
|
||||||
) {
|
) {
|
||||||
|
|
@ -83,14 +83,22 @@ export class Ressource {
|
||||||
}
|
}
|
||||||
|
|
||||||
update(
|
update(
|
||||||
props: Partial<Omit<Ressource, 'type' | 'uuid' | 'createdAt'>>,
|
props: Partial<Omit<Resource, 'type' | 'uuid' | 'createdAt'>>,
|
||||||
): Ressource {
|
): Resource {
|
||||||
const updated = new Ressource({ ...this, ...props })
|
return new Resource({
|
||||||
updated.#updatedAt = new Date().toISOString() as DateString
|
name: this.name,
|
||||||
return updated
|
avatar: this.avatar,
|
||||||
|
uuid: this.uuid,
|
||||||
|
createdAt: this.createdAt,
|
||||||
|
updatedAt: new Date().toISOString() as DateString,
|
||||||
|
...props,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON() {
|
toJSON(): ResourceJson<
|
||||||
|
Resource,
|
||||||
|
'type' | 'uuid' | 'name' | 'avatar' | 'createdAt' | 'updatedAt'
|
||||||
|
> {
|
||||||
return {
|
return {
|
||||||
type: this.type,
|
type: this.type,
|
||||||
uuid: this.uuid,
|
uuid: this.uuid,
|
||||||
|
|
@ -105,12 +113,12 @@ export class Ressource {
|
||||||
return `Ressource (${JSON.stringify(this)})`
|
return `Ressource (${JSON.stringify(this)})`
|
||||||
}
|
}
|
||||||
|
|
||||||
toRef(): Ref<Ressource> {
|
toRef(): Ref<Resource> {
|
||||||
return Ref.fromRessource(this)
|
return Ref.fromResource(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Ressource {
|
export interface Resource {
|
||||||
type: 'user' | 'machine' | 'service' | 'group' | 'credential'
|
type: 'user' | 'machine' | 'service' | 'group' | 'credential'
|
||||||
uuid: UUID
|
uuid: UUID
|
||||||
name: string
|
name: string
|
||||||
|
|
@ -118,3 +126,24 @@ export interface Ressource {
|
||||||
createdAt: DateString
|
createdAt: DateString
|
||||||
updatedAt: DateString
|
updatedAt: DateString
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ResourceJson<T extends Resource, K extends keyof T> = Readonly<
|
||||||
|
Pick<
|
||||||
|
T,
|
||||||
|
| 'type'
|
||||||
|
| 'uuid'
|
||||||
|
| 'name'
|
||||||
|
| 'avatar'
|
||||||
|
| 'createdAt'
|
||||||
|
| 'updatedAt'
|
||||||
|
| K
|
||||||
|
>
|
||||||
|
>
|
||||||
|
|
||||||
|
export type ResourceRefJson<
|
||||||
|
T extends Resource,
|
||||||
|
Ref extends Resource,
|
||||||
|
K extends keyof T,
|
||||||
|
> = {
|
||||||
|
[k in K]: Readonly<ReturnType<ReturnType<Ref['toRef']>['toString']>[]>
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
import { Ref } from '@/src/models/utils/ref.ts'
|
import type { ToJson, UrlString } from '../../../types.ts'
|
||||||
import type { ToJson, UrlString } from '@/types.ts'
|
import { Avatar } from '../utils/avatar.ts'
|
||||||
import type { Group } from '@models/group.ts'
|
import { Ref } from '../utils/ref.ts'
|
||||||
import { Ressource } from '@models/ressource.ts'
|
import type { Group } from './group.ts'
|
||||||
import { Avatar } from '@/src/models/utils/avatar.ts'
|
import {
|
||||||
|
Resource,
|
||||||
|
type ResourceJson,
|
||||||
|
type ResourceRefJson,
|
||||||
|
} from './resource.ts'
|
||||||
|
|
||||||
export class Service extends Ressource {
|
export class Service extends Resource {
|
||||||
static fromJSON(
|
static fromJSON(
|
||||||
{ groups, ...json }: ToJson<Service>,
|
{ groups, ...json }: ToJson<Service>,
|
||||||
): Service {
|
): Service {
|
||||||
|
|
@ -52,7 +56,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[]
|
||||||
|
|
@ -62,7 +66,7 @@ export class Service extends Ressource {
|
||||||
tags,
|
tags,
|
||||||
url,
|
url,
|
||||||
groups,
|
groups,
|
||||||
...ressource
|
...resource
|
||||||
}: Pick<
|
}: Pick<
|
||||||
Service,
|
Service,
|
||||||
| 'uuid'
|
| 'uuid'
|
||||||
|
|
@ -75,7 +79,7 @@ export class Service extends Ressource {
|
||||||
| 'groups'
|
| 'groups'
|
||||||
| 'avatar'
|
| 'avatar'
|
||||||
>) {
|
>) {
|
||||||
super(ressource)
|
super(resource)
|
||||||
|
|
||||||
if (!['web', 'fs', 'git'].includes(category)) {
|
if (!['web', 'fs', 'git'].includes(category)) {
|
||||||
throw new TypeError(
|
throw new TypeError(
|
||||||
|
|
@ -92,19 +96,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,11 +116,23 @@ export class Service extends Ressource {
|
||||||
props: Partial<Omit<Service, 'type' | 'uuid' | 'createdAt'>>,
|
props: Partial<Omit<Service, 'type' | 'uuid' | 'createdAt'>>,
|
||||||
): Service {
|
): Service {
|
||||||
const { updatedAt } = super.update(props)
|
const { updatedAt } = super.update(props)
|
||||||
const service = new Service({ ...this, ...props, updatedAt })
|
return new Service({
|
||||||
return service
|
uuid: this.uuid,
|
||||||
|
name: this.name,
|
||||||
|
createdAt: this.createdAt,
|
||||||
|
avatar: this.avatar,
|
||||||
|
category: this.category,
|
||||||
|
url: this.url,
|
||||||
|
tags: this.tags,
|
||||||
|
groups: this.groups,
|
||||||
|
...props,
|
||||||
|
updatedAt,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON() {
|
toJSON():
|
||||||
|
& ResourceJson<Service, 'category' | 'tags' | 'url'>
|
||||||
|
& ResourceRefJson<Service, Group, 'groups'> {
|
||||||
return {
|
return {
|
||||||
...super.toJSON(),
|
...super.toJSON(),
|
||||||
type: this.type,
|
type: this.type,
|
||||||
|
|
@ -136,10 +152,12 @@ export class Service extends Ressource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Service extends Ressource {
|
export interface Service extends Resource {
|
||||||
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'
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
import { Ref } from '@/src/models/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 { Avatar } from '../utils/avatar.ts'
|
||||||
import type { Credential, CredentialCategory } from '@models/credential.ts'
|
import { Ref } from '../utils/ref.ts'
|
||||||
import type { Group } from '@models/group.ts'
|
import type { Credential, CredentialCategory } from './credential.ts'
|
||||||
import { Ressource } from '@models/ressource.ts'
|
import type { Group } from './group.ts'
|
||||||
import { Avatar } from '@/src/models/utils/avatar.ts'
|
import {
|
||||||
|
Resource,
|
||||||
|
type ResourceJson,
|
||||||
|
type ResourceRefJson,
|
||||||
|
} from './resource.ts'
|
||||||
|
|
||||||
export class User extends Ressource {
|
export class User extends Resource {
|
||||||
static fromJSON(json: ToJson<User>): User {
|
static fromJSON(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`)
|
||||||
|
|
@ -112,7 +116,7 @@ export class User extends Ressource {
|
||||||
groups,
|
groups,
|
||||||
posix,
|
posix,
|
||||||
credentials,
|
credentials,
|
||||||
...ressource
|
...resource
|
||||||
}: Pick<
|
}: Pick<
|
||||||
User,
|
User,
|
||||||
| 'uuid'
|
| 'uuid'
|
||||||
|
|
@ -127,7 +131,7 @@ export class User extends Ressource {
|
||||||
| 'avatar'
|
| 'avatar'
|
||||||
| 'credentials'
|
| 'credentials'
|
||||||
>) {
|
>) {
|
||||||
super(ressource)
|
super(resource)
|
||||||
this.#lastname = lastname
|
this.#lastname = lastname
|
||||||
this.#firstname = firstname
|
this.#firstname = firstname
|
||||||
if (!regex.mailAddress) {
|
if (!regex.mailAddress) {
|
||||||
|
|
@ -150,37 +154,54 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
update(props: Partial<Omit<User, 'type' | 'uuid' | 'createdAt'>>): User {
|
update(props: Partial<Omit<User, 'type' | 'uuid' | 'createdAt'>>): User {
|
||||||
const { updatedAt } = super.update(props)
|
const { updatedAt } = super.update(props)
|
||||||
const user = new User({ ...this, ...props, updatedAt })
|
return new User({
|
||||||
return user
|
uuid: this.uuid,
|
||||||
|
name: this.name,
|
||||||
|
avatar: this.avatar,
|
||||||
|
createdAt: this.createdAt,
|
||||||
|
lastname: this.lastname,
|
||||||
|
firstname: this.firstname,
|
||||||
|
mail: this.mail,
|
||||||
|
groups: this.groups,
|
||||||
|
posix: this.posix,
|
||||||
|
credentials: this.credentials,
|
||||||
|
...props,
|
||||||
|
updatedAt,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON() {
|
toJSON():
|
||||||
|
& ResourceJson<User, 'lastname' | 'firstname' | 'login' | 'mail'>
|
||||||
|
& { posix: Posix | null }
|
||||||
|
& ResourceRefJson<User, Group, 'groups'>
|
||||||
|
& ResourceRefJson<User, Credential<CredentialCategory>, 'credentials'> {
|
||||||
return {
|
return {
|
||||||
...super.toJSON(),
|
...super.toJSON(),
|
||||||
|
type: this.type,
|
||||||
lastname: this.lastname,
|
lastname: this.lastname,
|
||||||
firstname: this.firstname,
|
firstname: this.firstname,
|
||||||
login: this.login,
|
login: this.login,
|
||||||
|
|
@ -201,7 +222,7 @@ export class User extends Ressource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface User extends Ressource {
|
export interface User extends Resource {
|
||||||
type: 'user'
|
type: 'user'
|
||||||
lastname: string
|
lastname: string
|
||||||
firstname: string
|
firstname: string
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -1,71 +1,77 @@
|
||||||
import type { Db } from '@/mod.ts'
|
import type { UUID } from '../../../types.ts'
|
||||||
import type { UUID } from '@/types.ts'
|
import type { Db } from '../../db/mod.ts'
|
||||||
import {
|
import {
|
||||||
Credential,
|
Credential,
|
||||||
Group,
|
Group,
|
||||||
Machine,
|
Machine,
|
||||||
type Ressource,
|
type Resource,
|
||||||
Service,
|
Service,
|
||||||
User,
|
User,
|
||||||
} from '@models'
|
} from '../mod.ts'
|
||||||
|
|
||||||
export type RefString<T extends Ressource> =
|
export type RefString<T extends Resource> =
|
||||||
| `@ref/${T['type']}#${UUID}`
|
| `@ref/${T['type']}#${UUID}`
|
||||||
| Ref<T>
|
| Ref<T>
|
||||||
export type RefResolver<T extends Ressource> = (
|
export type RefResolver<T extends Resource> = (
|
||||||
ref: RefString<T>,
|
ref: RefString<T>,
|
||||||
) => T | Promise<T>
|
) => T | Promise<T>
|
||||||
export class Ref<T extends Ressource> extends String {
|
export class Ref<T extends Resource> extends String {
|
||||||
static #toString<T extends Ressource>(
|
static #toString<T extends Resource>(
|
||||||
{ uuid, type }: { uuid: UUID; type: T['type'] },
|
{ uuid, type }: { uuid: UUID; type: T['type'] },
|
||||||
) {
|
) {
|
||||||
return `@ref/${type}#${uuid}` as const
|
return `@ref/${type}#${uuid}` as const
|
||||||
}
|
}
|
||||||
|
|
||||||
static parse<T extends Ressource>(string: RefString<T>) {
|
static parse<T extends Resource>(
|
||||||
|
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]
|
||||||
|
|
||||||
return { type, uuid } as const
|
return { type, uuid } as const
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromRessource<T extends Ressource>(ressource: T): Ref<T> {
|
static fromResource<T extends Resource>(resource: T): Ref<T> {
|
||||||
return new Ref<T>(ressource)
|
return new Ref<T>(resource)
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromString<T extends Ressource>(string: RefString<T>) {
|
static fromString<T extends Resource>(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(
|
||||||
return <T extends Ressource>(ref: RefString<T>): Promise<T> => {
|
db: Db,
|
||||||
|
): <T extends Resource>(ref: RefString<T>) => Promise<T> {
|
||||||
|
return <T extends Resource>(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
|
||||||
return db.ressource[type].get({ uuid })
|
return db.resource[type].get({ uuid })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static restResolver(endpoint: string | URL) {
|
static restResolver(
|
||||||
return async <T extends Ressource>(ref: RefString<T>) => {
|
endpoint: string | URL,
|
||||||
|
): <T extends Resource>(ref: RefString<T>) => Promise<T> {
|
||||||
|
return async <T extends Resource>(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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue