fix: update

Signed-off-by: Avior <git@avior.me>
This commit is contained in:
2024-10-09 17:47:31 +02:00
parent 7fa18d682d
commit e38bc9b0b2
69 changed files with 3482 additions and 2071 deletions

View File

@ -1,26 +1,26 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { objectLoop } from '@dzeio/object-util'
import archiver from 'archiver'
import fs from 'fs/promises'
import file_system from 'fs'
import type DaoAdapter from '../DaoAdapter'
import type { DBPull } from '../DaoAdapter'
import { type Query } from '../Query'
import type Schema from '../Schema'
import { isSchemaItem, type Implementation, type Model } from '../Schema'
import type Schema from 'libs/Schema'
import type { Model, ModelInfer } from 'libs/Schema'
import type SchemaBuffer from 'libs/Schema/Items/SchemaBuffer'
import type SchemaNumber from 'libs/Schema/Items/SchemaNumber'
import type SchemaString from 'libs/Schema/Items/SchemaString'
import fileSystem from 'node:fs'
import fs from 'node:fs/promises'
import type { Query } from '../Query'
import type DaoAdapter from './DaoAdapter'
import type { DBPull } from './DaoAdapter'
interface FS extends Model {
filename: StringConstructor
path: StringConstructor
filename: SchemaString
path: SchemaString
// eslint-disable-next-line no-undef
data: BufferConstructor
type: StringConstructor
size: NumberConstructor
data: SchemaBuffer
type: SchemaString
size: SchemaNumber
}
export default class FSAdapter<T extends FS> implements DaoAdapter<T> {
private id!: string
export default class FSAdapter<T extends FS> implements DaoAdapter<Schema<T>> {
public constructor(
public readonly schema: Schema<T>,
@ -33,7 +33,7 @@ export default class FSAdapter<T extends FS> implements DaoAdapter<T> {
}
// TODO: make it clearer what it does
public async create(obj: Partial<Implementation<T>>): Promise<Implementation<T> | null> {
public async create(obj: Partial<ModelInfer<T>>): Promise<ModelInfer<T> | null> {
const realPath = this.getFullPath(obj.path!)
const finalFolder = realPath.slice(0, realPath.lastIndexOf('/'))
@ -51,49 +51,16 @@ export default class FSAdapter<T extends FS> implements DaoAdapter<T> {
} else {
await fs.writeFile(realPath, data as string)
}
return obj as Implementation<T>
return obj as ModelInfer<T>
} else {
console.log('making the final directory', realPath)
await fs.mkdir(realPath)
return obj as Implementation<T>
return obj as ModelInfer<T>
}
}
public async createZippedBufferFromDirectory(directoryPath: string) {
const archive = archiver('zip', {zlib: {level: 9}})
archive.on('error', function(err) {
throw err
})
archive.on('warning', function(err) {
if (err.code === 'ENOENT') {
console.log('warning: ', err)
} else {
throw err
}
})
const fileName = `${this.basePath}/zip/${directoryPath.split(this.basePath)[1]}.zip`
fs.mkdir(fileName.slice(0, fileName.lastIndexOf('/')), {recursive: true})
const output = file_system.createWriteStream(fileName)
archive.pipe(output)
archive.directory(directoryPath, false)
const timeout = (cb: (value: (value: unknown) => void) => void, interval: number) => () =>
new Promise((resolve) => {
setTimeout(() => cb(resolve), interval)
})
const onTimeout = (seconds: number) => timeout((resolve) =>
resolve(`Timed out while zipping ${directoryPath}`), seconds * 1000)()
const error = await Promise.race([archive.finalize(), onTimeout(60)])
if (typeof error === 'string') {
console.log('Error:', error)
return null
}
return await fs.readFile(fileName)
}
// eslint-disable-next-line complexity
public async read(query?: Query<Implementation<T>> | undefined, toZip?: boolean): Promise<DBPull<T>> {
public async read(query?: Query<ModelInfer<T>> | undefined): Promise<DBPull<Schema<T>>> {
const localPath = query?.path as string ?? ''
@ -104,38 +71,24 @@ export default class FSAdapter<T extends FS> implements DaoAdapter<T> {
try {
const stats = await fs.stat(realPath)
const files: Array<Implementation<T>> = []
const files: Array<ModelInfer<T>> = []
if (stats.isDirectory()) {
const dirFiles = await fs.readdir(realPath)
// eslint-disable-next-line max-depth
if (toZip === true) { // put queried file/folder in a zip file
const buffer = await this.createZippedBufferFromDirectory(realPath)
// eslint-disable-next-line max-depth
if (buffer !== null) {
files.push({
path: localPath,
filename: localPath.slice(localPath.lastIndexOf('/') + 1),
data: buffer,
type: 'file',
size: buffer.length,
} as any)
}
} else { // return every sub files
// eslint-disable-next-line max-depth
for await (const file of dirFiles) {
files.push(await this.readFile(localPath + '/' + file))
}
for await (const file of dirFiles) {
files.push(await this.readFile(`${localPath}/${file}`))
}
} else {
files.push(await this.readFile(localPath))
}
const pageLimit = query?.$limit ?? Infinity
const pageOffset = query?.$offset ?? 0
return {
rows: files.length,
rowsTotal: files.length,
page: 0,
pageTotal: 1,
data: files
page: Math.floor(pageOffset / pageLimit),
pageTotal: Math.max(1, Math.ceil(files.length / pageLimit)),
data: files.slice(pageOffset, pageOffset + pageLimit)
}
} catch {
return {
@ -148,23 +101,32 @@ export default class FSAdapter<T extends FS> implements DaoAdapter<T> {
}
}
public async update(_obj: Implementation<T>): Promise<Implementation<T> | null> {
public async update(_obj: ModelInfer<T>): Promise<ModelInfer<T> | null> {
throw new Error('not implemented')
}
public async patch(_id: string, _obj: Partial<Implementation<T>>): Promise<Implementation<T> | null> {
public async patch(_id: string, _obj: Partial<ModelInfer<T>>): Promise<ModelInfer<T> | null> {
throw new Error('not implemented')
}
public async delete(_obj: Implementation<T>): Promise<boolean> {
throw new Error('not implemented')
public async delete(obj: ModelInfer<T>): Promise<boolean> {
const localPath = obj?.path as string ?? ''
const realPath = this.getFullPath(localPath)
try {
await fs.stat(realPath)
await fs.rm(realPath, { recursive: true, force: true })
return true
} catch {
console.error('Could not remove file', localPath)
return false
}
}
private getFullPath(localPath?: string): string {
if (localPath && !localPath?.startsWith('/')) {
console.warn('Your path should start with a "/", adding it')
localPath = ('/' + localPath) as any
localPath = (`/${localPath}`)
}
let realPath = this.basePath + (localPath ? localPath : '')
@ -176,7 +138,7 @@ export default class FSAdapter<T extends FS> implements DaoAdapter<T> {
return realPath
}
private async readFile(localPath: string): Promise<Implementation<T>> {
private async readFile(localPath: string): Promise<ModelInfer<T>> {
const path = this.getFullPath(localPath)
console.log('reading file at', path)
@ -184,7 +146,7 @@ export default class FSAdapter<T extends FS> implements DaoAdapter<T> {
const type = stats.isFile() ? 'file' : 'directory'
console.log('file is a', type)
const obj: Implementation<T> = {
const obj: ModelInfer<T> = {
path: localPath,
filename: localPath.slice(localPath.lastIndexOf('/') + 1),
data: type === 'file' ? await fs.readFile(path) : '',
@ -193,10 +155,10 @@ export default class FSAdapter<T extends FS> implements DaoAdapter<T> {
} as any
objectLoop(this.schema.model, (item, key) => {
if (isSchemaItem(item) && item.database?.created) {
if (item.attributes.includes('db:created')) {
// @ts-expect-error things get validated anyway
obj[key] = stats.ctime
} else if (isSchemaItem(item) && item.database?.updated) {
} else if (item.attributes.includes('db:updated')) {
// @ts-expect-error things get validated anyway
obj[key] = stats.mtime
}