feat: Add base to project

Signed-off-by: Avior <git@avior.me>
This commit is contained in:
2024-05-16 16:45:50 +02:00
parent 9b2d412a9e
commit f50ec828fb
36 changed files with 5248 additions and 1698 deletions

View File

@ -0,0 +1,207 @@
/* 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'
interface FS extends Model {
filename: StringConstructor
path: StringConstructor
// eslint-disable-next-line no-undef
data: BufferConstructor
type: StringConstructor
size: NumberConstructor
}
export default class FSAdapter<T extends FS> implements DaoAdapter<T> {
private id!: string
public constructor(
public readonly schema: Schema<T>,
public readonly basePath: string
) {
if (basePath.endsWith('/')) {
console.warn('the base path should not end wiath a "/", removing it')
basePath = basePath.slice(0, basePath.lastIndexOf('/'))
}
}
// TODO: make it clearer what it does
public async create(obj: Partial<Implementation<T>>): Promise<Implementation<T> | null> {
const realPath = this.getFullPath(obj.path!)
const finalFolder = realPath.slice(0, realPath.lastIndexOf('/'))
console.log('making the directory', finalFolder)
await fs.mkdir(finalFolder, { recursive: true })
if (obj.type === 'file') {
console.log('getting the data', finalFolder)
const data = obj.data
console.log('writing to', realPath)
if ((data as any) instanceof Buffer) {
await fs.writeFile(realPath, data as Buffer)
} else {
await fs.writeFile(realPath, data as string)
}
return obj as Implementation<T>
} else {
console.log('making the final directory', realPath)
await fs.mkdir(realPath)
return obj as Implementation<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>> {
const localPath = query?.path as string ?? ''
const realPath = this.getFullPath(localPath)
console.log('get the full path', realPath)
try {
const stats = await fs.stat(realPath)
const files: Array<Implementation<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))
}
}
} else {
files.push(await this.readFile(localPath))
}
return {
rows: files.length,
rowsTotal: files.length,
page: 0,
pageTotal: 1,
data: files
}
} catch {
return {
rows: 0,
rowsTotal: 0,
page: 0,
pageTotal: 0,
data: []
}
}
}
public async update(_obj: Implementation<T>): Promise<Implementation<T> | null> {
throw new Error('not implemented')
}
public async patch(_id: string, _obj: Partial<Implementation<T>>): Promise<Implementation<T> | null> {
throw new Error('not implemented')
}
public async delete(_obj: Implementation<T>): Promise<boolean> {
throw new Error('not implemented')
}
private getFullPath(localPath?: string): string {
if (localPath && !localPath?.startsWith('/')) {
console.warn('Your path should start with a "/", adding it')
localPath = ('/' + localPath) as any
}
let realPath = this.basePath + (localPath ? localPath : '')
if (realPath.includes('\\')) {
realPath = realPath.replace(/\\/g, '/')
}
return realPath
}
private async readFile(localPath: string): Promise<Implementation<T>> {
const path = this.getFullPath(localPath)
console.log('reading file at', path)
const stats = await fs.stat(path)
const type = stats.isFile() ? 'file' : 'directory'
console.log('file is a', type)
const obj: Implementation<T> = {
path: localPath,
filename: localPath.slice(localPath.lastIndexOf('/') + 1),
data: type === 'file' ? await fs.readFile(path) : '',
type: type,
size: stats.size
} as any
objectLoop(this.schema.model, (item, key) => {
if (isSchemaItem(item) && item.database?.created) {
// @ts-expect-error things get validated anyway
obj[key] = stats.ctime
} else if (isSchemaItem(item) && item.database?.updated) {
// @ts-expect-error things get validated anyway
obj[key] = stats.mtime
}
})
return obj
}
}