From e886b9a8bc5e5ec1b08551ea4f70d80f3390ce12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Boixader=20G=C3=BCell?= Date: Fri, 24 Nov 2023 15:00:50 +0100 Subject: [PATCH] feat: add logs and refactor to organize logic --- electron-app/.gitignore | 1 - electron-app/src/fileSystemFn.ts | 35 +++--- electron-app/src/fileSystemServerFn.ts | 11 +- electron-app/src/logic/errors.ts | 8 ++ .../src/logic/logs/domain/log.datasource.ts | 7 ++ .../src/logic/logs/domain/log.entity.ts | 39 +++++++ .../src/logic/logs/domain/log.repository.ts | 7 ++ .../domain/use-cases/delete-logs.use-case.ts | 13 +++ .../domain/use-cases/get-logs.use-case.ts | 14 +++ .../domain/use-cases/save-logs.use-case.ts | 14 +++ .../file-system.log.datasource.ts | 63 +++++++++++ .../logs/infrastructure/log.repository.ts | 19 ++++ .../src/logic/logs/presentation/routes.ts | 55 +++++++++ .../src/logic/sync/domain/sync.datasource.ts | 9 ++ .../src/logic/sync/domain/sync.entity.ts | 60 ++++++++++ .../src/logic/sync/domain/sync.repository.ts | 9 ++ .../domain/use-cases/create-sync.use-case.ts | 21 ++++ .../domain/use-cases/delete-sync.use-case.ts | 18 +++ .../domain/use-cases/get-all-sync.use-case.ts | 14 +++ .../domain/use-cases/get-sync.use-case.ts | 19 ++++ .../domain/use-cases/update-sync.use-case.ts | 23 ++++ .../file-system.sync.datasource.ts | 60 ++++++++++ .../sync/infrastructure/sync.repository.ts | 27 +++++ .../src/logic/sync/presentation/routes.ts | 102 +++++++++++++++++ electron-app/src/presentation/routes.ts | 5 +- electron-app/src/presentation/sync/routes.ts | 105 ------------------ electron-app/src/types/server.d.ts | 34 +++++- electron-app/tests/sever.spec.js | 25 ++++- 28 files changed, 680 insertions(+), 137 deletions(-) create mode 100644 electron-app/src/logic/errors.ts create mode 100644 electron-app/src/logic/logs/domain/log.datasource.ts create mode 100644 electron-app/src/logic/logs/domain/log.entity.ts create mode 100644 electron-app/src/logic/logs/domain/log.repository.ts create mode 100644 electron-app/src/logic/logs/domain/use-cases/delete-logs.use-case.ts create mode 100644 electron-app/src/logic/logs/domain/use-cases/get-logs.use-case.ts create mode 100644 electron-app/src/logic/logs/domain/use-cases/save-logs.use-case.ts create mode 100644 electron-app/src/logic/logs/infrastructure/file-system.log.datasource.ts create mode 100644 electron-app/src/logic/logs/infrastructure/log.repository.ts create mode 100644 electron-app/src/logic/logs/presentation/routes.ts create mode 100644 electron-app/src/logic/sync/domain/sync.datasource.ts create mode 100644 electron-app/src/logic/sync/domain/sync.entity.ts create mode 100644 electron-app/src/logic/sync/domain/sync.repository.ts create mode 100644 electron-app/src/logic/sync/domain/use-cases/create-sync.use-case.ts create mode 100644 electron-app/src/logic/sync/domain/use-cases/delete-sync.use-case.ts create mode 100644 electron-app/src/logic/sync/domain/use-cases/get-all-sync.use-case.ts create mode 100644 electron-app/src/logic/sync/domain/use-cases/get-sync.use-case.ts create mode 100644 electron-app/src/logic/sync/domain/use-cases/update-sync.use-case.ts create mode 100644 electron-app/src/logic/sync/infrastructure/file-system.sync.datasource.ts create mode 100644 electron-app/src/logic/sync/infrastructure/sync.repository.ts create mode 100644 electron-app/src/logic/sync/presentation/routes.ts delete mode 100644 electron-app/src/presentation/sync/routes.ts diff --git a/electron-app/.gitignore b/electron-app/.gitignore index d4d1937..927b58b 100644 --- a/electron-app/.gitignore +++ b/electron-app/.gitignore @@ -1,5 +1,4 @@ # Logs -logs *.log npm-debug.log* yarn-debug.log* diff --git a/electron-app/src/fileSystemFn.ts b/electron-app/src/fileSystemFn.ts index 003ccb1..78cbc26 100644 --- a/electron-app/src/fileSystemFn.ts +++ b/electron-app/src/fileSystemFn.ts @@ -2,31 +2,20 @@ import { constants } from 'fs'; import fs from 'fs/promises'; import path from 'path'; -export async function createFile(path: string, content: string | Uint8Array): Promise { - try { - await fs.writeFile(path, content); - console.log(`File created at ${path}`); - } catch (error) { - console.error('Error creating file:', error); - } +export async function writeFile(path: string, content: string | Uint8Array): Promise { + await fs.writeFile(path, content); +} + +export async function appendFile(path: string, content: string | Uint8Array): Promise { + await fs.appendFile(path, content); } export async function createDirectory(path: string): Promise { - try { - await fs.mkdir(path, { recursive: true }); - console.log(`Directory deleted at ${path}`); - } catch (error) { - console.error('Error delete directory:', error); - } + await fs.mkdir(path, { recursive: true }); } export async function deleteDirectory(path: string): Promise { - try { - await fs.rmdir(path, { recursive: true }); - console.log(`Directory created at ${path}`); - } catch (error) { - console.error('Error creating directory:', error); - } + await fs.rmdir(path, { recursive: true }); } export async function pathExists(path: string): Promise { @@ -38,6 +27,14 @@ export async function pathExists(path: string): Promise { } } +export async function readFile(path: string) { + return await fs.readFile(path, 'utf-8'); +} + +export async function deleteFile(path: string) { + await fs.rm(path); +} + export async function findFilesInDirectory(directory: string, extensions: string[]): Promise { const result: string[] = []; diff --git a/electron-app/src/fileSystemServerFn.ts b/electron-app/src/fileSystemServerFn.ts index 99ed3cc..31b4f14 100644 --- a/electron-app/src/fileSystemServerFn.ts +++ b/electron-app/src/fileSystemServerFn.ts @@ -1,4 +1,4 @@ -import { createDirectory, createFile, pathExists } from './fileSystemFn'; +import { createDirectory, pathExists, writeFile } from './fileSystemFn'; const defaultConfig = { syncPeriod: 3600, // In seconds @@ -13,12 +13,17 @@ export async function beforeStartServer(basePath: string) { await createDirectory(`${basePath}/sync`); } + if (!(await pathExists(`${basePath}/logs`))) { + await createDirectory(`${basePath}/logs`); + } + const configPath = `${basePath}/config.json`; if (!(await pathExists(configPath))) { - await createFile(configPath, JSON.stringify(defaultConfig, null, 2)); + await writeFile(configPath, JSON.stringify(defaultConfig, null, 2)); } + const syncPath = `${basePath}/sync.json`; if (!(await pathExists(syncPath))) { - await createFile(syncPath, JSON.stringify({}, null, 2)); + await writeFile(syncPath, JSON.stringify({}, null, 2)); } } diff --git a/electron-app/src/logic/errors.ts b/electron-app/src/logic/errors.ts new file mode 100644 index 0000000..956afe9 --- /dev/null +++ b/electron-app/src/logic/errors.ts @@ -0,0 +1,8 @@ +export class CustomError extends Error { + constructor( + public readonly message: string, + public readonly statusCode: number = 400, + ) { + super(message); + } +} diff --git a/electron-app/src/logic/logs/domain/log.datasource.ts b/electron-app/src/logic/logs/domain/log.datasource.ts new file mode 100644 index 0000000..9430bfb --- /dev/null +++ b/electron-app/src/logic/logs/domain/log.datasource.ts @@ -0,0 +1,7 @@ +import { LogEntity } from './log.entity'; + +export abstract class ILogDatasource { + abstract saveLog(log: LogEntity): Promise; + abstract getLogs(): Promise; + abstract deleteLogs(): Promise; +} diff --git a/electron-app/src/logic/logs/domain/log.entity.ts b/electron-app/src/logic/logs/domain/log.entity.ts new file mode 100644 index 0000000..82e35ff --- /dev/null +++ b/electron-app/src/logic/logs/domain/log.entity.ts @@ -0,0 +1,39 @@ +export enum LogSeverityLevel { + low = 'low', + medium = 'medium', + high = 'high', +} + +export interface LogEntityOptions { + level: LogSeverityLevel; + message: string; + origin: string; + createdAt?: Date; +} + +export class LogEntity { + public level: LogSeverityLevel; // Enum + public message: string; + public createdAt: Date; + public origin: string; + + constructor(options: LogEntityOptions) { + const { message, level, origin, createdAt = new Date() } = options; + this.message = message; + this.level = level; + this.createdAt = createdAt; + this.origin = origin; + } + + static fromJson = (json: string): LogEntity => { + json = json === '' ? '{}' : json; + const { message, level, createdAt, origin } = JSON.parse(json); + const log = new LogEntity({ + message, + level, + createdAt: new Date(createdAt), + origin, + }); + return log; + }; +} diff --git a/electron-app/src/logic/logs/domain/log.repository.ts b/electron-app/src/logic/logs/domain/log.repository.ts new file mode 100644 index 0000000..1eb998e --- /dev/null +++ b/electron-app/src/logic/logs/domain/log.repository.ts @@ -0,0 +1,7 @@ +import { LogEntity } from './log.entity'; + +export abstract class ILogRepository { + abstract saveLog(log: LogEntity): Promise; + abstract getLogs(): Promise; + abstract deleteLogs(): Promise; +} diff --git a/electron-app/src/logic/logs/domain/use-cases/delete-logs.use-case.ts b/electron-app/src/logic/logs/domain/use-cases/delete-logs.use-case.ts new file mode 100644 index 0000000..a157070 --- /dev/null +++ b/electron-app/src/logic/logs/domain/use-cases/delete-logs.use-case.ts @@ -0,0 +1,13 @@ +import { ILogRepository } from '../log.repository'; + +export interface DeleteLogsUseCase { + execute(): Promise; +} + +export class DeleteLogs implements DeleteLogsUseCase { + constructor(private readonly repository: ILogRepository) {} + + execute(): Promise { + return this.repository.deleteLogs(); + } +} diff --git a/electron-app/src/logic/logs/domain/use-cases/get-logs.use-case.ts b/electron-app/src/logic/logs/domain/use-cases/get-logs.use-case.ts new file mode 100644 index 0000000..62d5bc8 --- /dev/null +++ b/electron-app/src/logic/logs/domain/use-cases/get-logs.use-case.ts @@ -0,0 +1,14 @@ +import { LogEntity } from '../log.entity'; +import { ILogRepository } from '../log.repository'; + +export interface GetLogUseCase { + execute(): Promise; +} + +export class GetLogs implements GetLogUseCase { + constructor(private readonly repository: ILogRepository) {} + + execute(): Promise { + return this.repository.getLogs(); + } +} diff --git a/electron-app/src/logic/logs/domain/use-cases/save-logs.use-case.ts b/electron-app/src/logic/logs/domain/use-cases/save-logs.use-case.ts new file mode 100644 index 0000000..a518b16 --- /dev/null +++ b/electron-app/src/logic/logs/domain/use-cases/save-logs.use-case.ts @@ -0,0 +1,14 @@ +import { LogEntity } from '../log.entity'; +import { ILogRepository } from '../log.repository'; + +export interface SaveLogsUseCase { + execute(log: LogEntity): Promise; +} + +export class SaveLogs implements SaveLogsUseCase { + constructor(private readonly repository: ILogRepository) {} + + execute(log: LogEntity): Promise { + return this.repository.saveLog(log); + } +} diff --git a/electron-app/src/logic/logs/infrastructure/file-system.log.datasource.ts b/electron-app/src/logic/logs/infrastructure/file-system.log.datasource.ts new file mode 100644 index 0000000..d8e10c9 --- /dev/null +++ b/electron-app/src/logic/logs/infrastructure/file-system.log.datasource.ts @@ -0,0 +1,63 @@ +import { + appendFile, + createDirectory, + deleteFile, + findFilesInDirectory, + pathExists, + readFile, + writeFile, +} from '../../../fileSystemFn'; +import { ILogDatasource } from '../domain/log.datasource'; +import { LogEntity } from '../domain/log.entity'; + +export class FileSystemLogDatasource implements ILogDatasource { + private basePath: string; + + constructor(basePath: string) { + this.basePath = `${basePath}/logs`; + } + + private createLogsFiles = async () => { + if (await pathExists(this.basePath)) { + await createDirectory(this.basePath); + } + + const dateText = new Intl.DateTimeFormat('en-ca', { dateStyle: 'short' }).format(new Date()); + const fileName = `${this.basePath}/${dateText}.log`; + if (!(await pathExists(`${this.basePath}/${dateText}.log`))) { + await writeFile(`${this.basePath}/${dateText}.log`, ''); + } + return fileName; + }; + + async saveLog(newLog: LogEntity): Promise { + const logAsJson = `${JSON.stringify(newLog)}\n`; + const fileName = await this.createLogsFiles(); + await appendFile(fileName, logAsJson); + } + + private getLogsFromFile = async (path: string): Promise => { + const content = await readFile(path); + if (content === '') return []; + + const logs = content.split('\n').map(LogEntity.fromJson); + return logs; + }; + + async getLogs(): Promise { + const files = await findFilesInDirectory(this.basePath, ['log']); + const result = []; + for (const file of files) { + const logs = await this.getLogsFromFile(file); + result.push(...logs); + } + return result; + } + + async deleteLogs(): Promise { + const files = await findFilesInDirectory(this.basePath, ['log']); + for (const file of files) { + await deleteFile(file); + } + } +} diff --git a/electron-app/src/logic/logs/infrastructure/log.repository.ts b/electron-app/src/logic/logs/infrastructure/log.repository.ts new file mode 100644 index 0000000..31c00bc --- /dev/null +++ b/electron-app/src/logic/logs/infrastructure/log.repository.ts @@ -0,0 +1,19 @@ +import { ILogDatasource } from '../domain/log.datasource'; +import { LogEntity } from '../domain/log.entity'; +import { ILogRepository } from '../domain/log.repository'; + +export class LogRepository implements ILogRepository { + constructor(private readonly logDatasource: ILogDatasource) {} + + async saveLog(log: LogEntity): Promise { + return this.logDatasource.saveLog(log); + } + + async getLogs(): Promise { + return this.logDatasource.getLogs(); + } + + async deleteLogs(): Promise { + return this.logDatasource.deleteLogs(); + } +} diff --git a/electron-app/src/logic/logs/presentation/routes.ts b/electron-app/src/logic/logs/presentation/routes.ts new file mode 100644 index 0000000..3d0f16f --- /dev/null +++ b/electron-app/src/logic/logs/presentation/routes.ts @@ -0,0 +1,55 @@ +import { Response, Router } from 'express'; +import { CustomError } from '../../errors'; +import { DeleteLogs } from '../domain/use-cases/delete-logs.use-case'; +import { GetLogs } from '../domain/use-cases/get-logs.use-case'; +import { FileSystemLogDatasource } from '../infrastructure/file-system.log.datasource'; +import { LogRepository } from '../infrastructure/log.repository'; + +export class LogsFileSystemRoutes { + private readonly basePath: string; + + constructor(basePath: string) { + this.basePath = basePath; + } + + private handleError = (res: Response, error: unknown) => { + console.error(error); + if (error instanceof CustomError) { + res.status(error.statusCode).json({ error: error.message }); + return; + } + res.status(500).json({ error: 'Internal server error' }); + }; + + getRoutes(): Router { + const router = Router(); + const datasource = new FileSystemLogDatasource(this.basePath); + const logRepository = new LogRepository(datasource); + + router.get('/', async (_req, res) => { + try { + const data = await new GetLogs(logRepository).execute(); + res.status(200).send(data); + } catch (error) { + this.handleError(res, error); + } + }); + + router.get('/:since', async (_req, res) => { + res.status(200).send([]); + }); + + router.delete('/', async (_req, res) => { + try { + await new DeleteLogs(logRepository).execute(); + res.status(200).send({ + message: 'Logs deleted', + }); + } catch (error) { + this.handleError(res, error); + } + }); + + return router; + } +} diff --git a/electron-app/src/logic/sync/domain/sync.datasource.ts b/electron-app/src/logic/sync/domain/sync.datasource.ts new file mode 100644 index 0000000..1ce3684 --- /dev/null +++ b/electron-app/src/logic/sync/domain/sync.datasource.ts @@ -0,0 +1,9 @@ +import { SyncEntity } from './sync.entity'; + +export abstract class ISyncDatasource { + abstract createSync(sync: SyncEntity): Promise; + abstract getAllSync(): Promise<{ [id: string]: SyncEntity }>; + abstract getSync(id: string): Promise; + abstract deleteSync(id: string): Promise; + abstract updateSync(id: string, sync: SyncEntity): Promise; +} diff --git a/electron-app/src/logic/sync/domain/sync.entity.ts b/electron-app/src/logic/sync/domain/sync.entity.ts new file mode 100644 index 0000000..11573a1 --- /dev/null +++ b/electron-app/src/logic/sync/domain/sync.entity.ts @@ -0,0 +1,60 @@ +import { Classification, Connector, NucliaOptions } from '../../../types/server'; + +export enum LogSeverityLevel { + low = 'low', + medium = 'medium', + high = 'high', +} + +export interface LogEntityOptions { + connector: Connector; + kb: NucliaOptions; + folders: string[]; + labels?: Classification[]; + title: string; + id: string; +} + +export class SyncEntity { + public connector: Connector; + public kb: NucliaOptions; + public folders: string[]; + public labels: Classification[]; + public title: string; + public id: string; + + constructor(options: LogEntityOptions) { + const { connector, kb, folders, labels, title, id } = options; + this.connector = connector; + this.kb = kb; + this.folders = folders; + this.labels = labels || []; + this.title = title; + this.id = id; + } + + static fromJson = (json: string): SyncEntity => { + json = json === '' ? '{}' : json; + const { connector, kb, folders, labels, title, id } = JSON.parse(json); + const log = new SyncEntity({ + connector, + kb, + folders, + labels, + title, + id, + }); + return log; + }; + + serializeToJson() { + return { + id: this.id, + connector: this.connector, + kb: this.kb, + folders: this.folders, + labels: this.labels, + title: this.title, + }; + } +} diff --git a/electron-app/src/logic/sync/domain/sync.repository.ts b/electron-app/src/logic/sync/domain/sync.repository.ts new file mode 100644 index 0000000..c237fb4 --- /dev/null +++ b/electron-app/src/logic/sync/domain/sync.repository.ts @@ -0,0 +1,9 @@ +import { SyncEntity } from './sync.entity'; + +export abstract class ISyncRepository { + abstract createSync(sync: SyncEntity): Promise; + abstract getAllSync(): Promise<{ [id: string]: SyncEntity }>; + abstract getSync(id: string): Promise; + abstract deleteSync(id: string): Promise; + abstract updateSync(id: string, sync: SyncEntity): Promise; +} diff --git a/electron-app/src/logic/sync/domain/use-cases/create-sync.use-case.ts b/electron-app/src/logic/sync/domain/use-cases/create-sync.use-case.ts new file mode 100644 index 0000000..03f03a7 --- /dev/null +++ b/electron-app/src/logic/sync/domain/use-cases/create-sync.use-case.ts @@ -0,0 +1,21 @@ +import { CustomError } from '../../../errors'; +import { SyncEntity } from '../sync.entity'; +import { ISyncRepository } from '../sync.repository'; + +export interface CreateSyncUseCase { + execute(sync: SyncEntity): Promise; +} + +export class CreateSync implements CreateSyncUseCase { + constructor(private readonly repository: ISyncRepository) {} + + async execute(sync: SyncEntity): Promise { + const data = await this.repository.getSync(sync.id); + if (data !== null) { + throw new CustomError(`Sync with id ${sync.id} already exists`, 409); + } + await this.repository.createSync(sync); + // publish event create sync + return Promise.resolve(); + } +} diff --git a/electron-app/src/logic/sync/domain/use-cases/delete-sync.use-case.ts b/electron-app/src/logic/sync/domain/use-cases/delete-sync.use-case.ts new file mode 100644 index 0000000..4f8df85 --- /dev/null +++ b/electron-app/src/logic/sync/domain/use-cases/delete-sync.use-case.ts @@ -0,0 +1,18 @@ +import { CustomError } from '../../../errors'; +import { ISyncRepository } from '../sync.repository'; + +export interface DeleteSyncUseCase { + execute(id: string): Promise; +} + +export class DeleteSync implements DeleteSyncUseCase { + constructor(private readonly repository: ISyncRepository) {} + + async execute(id: string) { + const data = await this.repository.getSync(id); + if (data === null) { + throw new CustomError(`Sync with id ${id} not found`, 404); + } + await this.repository.deleteSync(id); + } +} diff --git a/electron-app/src/logic/sync/domain/use-cases/get-all-sync.use-case.ts b/electron-app/src/logic/sync/domain/use-cases/get-all-sync.use-case.ts new file mode 100644 index 0000000..16f4fe9 --- /dev/null +++ b/electron-app/src/logic/sync/domain/use-cases/get-all-sync.use-case.ts @@ -0,0 +1,14 @@ +import { SyncEntity } from '../sync.entity'; +import { ISyncRepository } from '../sync.repository'; + +export interface GetAllSyncUseCase { + execute(): Promise<{ [id: string]: SyncEntity }>; +} + +export class GetAllSync implements GetAllSyncUseCase { + constructor(private readonly repository: ISyncRepository) {} + + async execute() { + return await this.repository.getAllSync(); + } +} diff --git a/electron-app/src/logic/sync/domain/use-cases/get-sync.use-case.ts b/electron-app/src/logic/sync/domain/use-cases/get-sync.use-case.ts new file mode 100644 index 0000000..402cd1a --- /dev/null +++ b/electron-app/src/logic/sync/domain/use-cases/get-sync.use-case.ts @@ -0,0 +1,19 @@ +import { CustomError } from '../../../errors'; +import { SyncEntity } from '../sync.entity'; +import { ISyncRepository } from '../sync.repository'; + +export interface GetSyncUseCase { + execute(id: string): Promise; +} + +export class GetSync implements GetSyncUseCase { + constructor(private readonly repository: ISyncRepository) {} + + async execute(id: string) { + const data = await this.repository.getSync(id); + if (data === null) { + throw new CustomError(`Sync with id ${id} not found`, 404); + } + return data; + } +} diff --git a/electron-app/src/logic/sync/domain/use-cases/update-sync.use-case.ts b/electron-app/src/logic/sync/domain/use-cases/update-sync.use-case.ts new file mode 100644 index 0000000..d8fe4cc --- /dev/null +++ b/electron-app/src/logic/sync/domain/use-cases/update-sync.use-case.ts @@ -0,0 +1,23 @@ +import { CustomError } from '../../../errors'; +import { LogEntityOptions, SyncEntity } from '../sync.entity'; +import { ISyncRepository } from '../sync.repository'; + +export interface UpdateSyncUseCase { + execute(id: string, newSyncOpt: LogEntityOptions): Promise; +} + +export class UpdateSync implements UpdateSyncUseCase { + constructor(private readonly repository: ISyncRepository) {} + + async execute(id: string, newSyncOpt: LogEntityOptions) { + const data = await this.repository.getSync(id); + if (data === null) { + throw new CustomError(`Sync with id ${id} not found`, 404); + } + // const dataSeralized = data.serializeToJson(); + console.log('Before Update newSync', data, newSyncOpt); + const newSync = new SyncEntity({ ...data.serializeToJson(), ...newSyncOpt }); + console.log('Update newSync', newSync); + await this.repository.updateSync(id, newSync); + } +} diff --git a/electron-app/src/logic/sync/infrastructure/file-system.sync.datasource.ts b/electron-app/src/logic/sync/infrastructure/file-system.sync.datasource.ts new file mode 100644 index 0000000..df4bd76 --- /dev/null +++ b/electron-app/src/logic/sync/infrastructure/file-system.sync.datasource.ts @@ -0,0 +1,60 @@ +import { pathExists, readFile, writeFile } from '../../../fileSystemFn'; +import { ISyncDatasource } from '../domain/sync.datasource'; +import { SyncEntity } from '../domain/sync.entity'; + +export class FileSystemSyncDatasource implements ISyncDatasource { + private basePath: string; + private allSyncData: { [id: string]: SyncEntity }; + + constructor(basePath: string) { + this.basePath = `${basePath}/sync.json`; + this.allSyncData = {}; + } + private loadSyncData = async () => { + this.allSyncData = JSON.parse(await readFile(this.basePath)); + }; + + private createSyncFile = async () => { + if (await pathExists(this.basePath)) { + await writeFile(this.basePath, JSON.stringify({}, null, 2)); + } + }; + + async createSync(newSync: SyncEntity): Promise { + await this.loadSyncData(); + console.log('newSync', newSync); + this.allSyncData[newSync.id] = newSync; + + await this.createSyncFile(); + await writeFile(this.basePath, JSON.stringify(this.allSyncData, null, 2)); + } + + async getAllSync(): Promise<{ [id: string]: SyncEntity }> { + await this.loadSyncData(); + const result: { [id: string]: SyncEntity } = {}; + for (const key in this.allSyncData) { + result[key] = new SyncEntity(this.allSyncData[key]); + } + return result; + } + + async getSync(id: string): Promise { + await this.loadSyncData(); + if (id in this.allSyncData) { + return new SyncEntity(this.allSyncData[id]); + } + return null; + } + + async deleteSync(id: string): Promise { + await this.loadSyncData(); + delete this.allSyncData[id]; + await writeFile(this.basePath, JSON.stringify(this.allSyncData, null, 2)); + } + + async updateSync(id: string, sync: SyncEntity): Promise { + await this.loadSyncData(); + this.allSyncData[id] = sync; + await writeFile(this.basePath, JSON.stringify(this.allSyncData, null, 2)); + } +} diff --git a/electron-app/src/logic/sync/infrastructure/sync.repository.ts b/electron-app/src/logic/sync/infrastructure/sync.repository.ts new file mode 100644 index 0000000..4b4c1cc --- /dev/null +++ b/electron-app/src/logic/sync/infrastructure/sync.repository.ts @@ -0,0 +1,27 @@ +import { ISyncDatasource } from '../domain/sync.datasource'; +import { SyncEntity } from '../domain/sync.entity'; +import { ISyncRepository } from '../domain/sync.repository'; + +export class SyncRepository implements ISyncRepository { + constructor(private readonly syncDatasource: ISyncDatasource) {} + + async getAllSync() { + return this.syncDatasource.getAllSync(); + } + + async getSync(id: string) { + return this.syncDatasource.getSync(id); + } + + async createSync(sync: SyncEntity) { + return this.syncDatasource.createSync(sync); + } + + async deleteSync(id: string) { + return this.syncDatasource.deleteSync(id); + } + + async updateSync(id: string, sync: SyncEntity) { + return this.syncDatasource.updateSync(id, sync); + } +} diff --git a/electron-app/src/logic/sync/presentation/routes.ts b/electron-app/src/logic/sync/presentation/routes.ts new file mode 100644 index 0000000..9c9ae22 --- /dev/null +++ b/electron-app/src/logic/sync/presentation/routes.ts @@ -0,0 +1,102 @@ +import { Response, Router } from 'express'; +import { v4 as uuidv4 } from 'uuid'; +import { pathExists } from '../../../fileSystemFn'; +import { CustomError } from '../../errors'; +import { SyncEntity } from '../domain/sync.entity'; +import { CreateSync } from '../domain/use-cases/create-sync.use-case'; +import { DeleteSync } from '../domain/use-cases/delete-sync.use-case'; +import { GetAllSync } from '../domain/use-cases/get-all-sync.use-case'; +import { GetSync } from '../domain/use-cases/get-sync.use-case'; +import { UpdateSync } from '../domain/use-cases/update-sync.use-case'; +import { FileSystemSyncDatasource } from '../infrastructure/file-system.sync.datasource'; +import { SyncRepository } from '../infrastructure/sync.repository'; + +export class SyncFileSystemRoutes { + private readonly basePath: string; + + constructor(basePath: string) { + this.basePath = basePath; + } + + private handleError = (res: Response, error: unknown) => { + if (error instanceof CustomError) { + res.status(error.statusCode).json({ error: error.message }); + return; + } + // grabar log + res.status(500).json({ error: 'Internal server error' }); + }; + + getRoutes(): Router { + const router = Router(); + const datasource = new FileSystemSyncDatasource(this.basePath); + const syncRepository = new SyncRepository(datasource); + + router.use('/', async (_req, res, next) => { + if (!(await pathExists(`${this.basePath}/sync.json`))) { + res.status(404).send({ error: 'Nuclia folder not found' }); + return; + } + next(); + }); + + router.get('/', async (_req, res) => { + try { + const data = await new GetAllSync(syncRepository).execute(); + res.status(200).send(data); + } catch (error) { + this.handleError(res, error); + } + }); + + router.post('/', async (req, res) => { + const dataNewSync = req.body; + const id = uuidv4(); + const newSync = new SyncEntity({ + ...dataNewSync, + id, + }); + try { + await new CreateSync(syncRepository).execute(newSync); + res.status(201).send({ + id: newSync.id, + }); + } catch (error) { + this.handleError(res, error); + } + }); + + router.get('/:id', async (req, res) => { + const { id } = req.params; + try { + const data = await new GetSync(syncRepository).execute(id); + res.status(200).send(data); + } catch (error) { + this.handleError(res, error); + } + }); + + router.patch('/:id', async (req, res) => { + const { id } = req.params; + const dataNewSync = req.body; + try { + await new UpdateSync(syncRepository).execute(id, dataNewSync); + res.status(204).send(null); + } catch (error) { + this.handleError(res, error); + } + }); + + router.delete('/:id', async (req, res) => { + const { id } = req.params; + try { + await new DeleteSync(syncRepository).execute(id); + res.status(200).send(null); + } catch (error) { + this.handleError(res, error); + } + }); + + return router; + } +} diff --git a/electron-app/src/presentation/routes.ts b/electron-app/src/presentation/routes.ts index 8b6327e..c21afcc 100644 --- a/electron-app/src/presentation/routes.ts +++ b/electron-app/src/presentation/routes.ts @@ -1,6 +1,7 @@ import { Router } from 'express'; -import { SyncFileSystemRoutes } from './sync/routes'; +import { LogsFileSystemRoutes } from '../logic/logs/presentation/routes'; +import { SyncFileSystemRoutes } from '../logic/sync/presentation/routes'; export class AppFileSystemRoutes { private readonly basePath: string; @@ -12,7 +13,9 @@ export class AppFileSystemRoutes { getRoutes(): Router { const router = Router(); const syncFileSystemRoutes = new SyncFileSystemRoutes(this.basePath); + const logsFileSystemRoutes = new LogsFileSystemRoutes(this.basePath); router.use('/sync', syncFileSystemRoutes.getRoutes()); + router.use('/logs', logsFileSystemRoutes.getRoutes()); return router; } } diff --git a/electron-app/src/presentation/sync/routes.ts b/electron-app/src/presentation/sync/routes.ts deleted file mode 100644 index 1c9445a..0000000 --- a/electron-app/src/presentation/sync/routes.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { Router } from 'express'; -import fs from 'fs/promises'; -import { v4 as uuidv4 } from 'uuid'; -import { pathExists } from '../../fileSystemFn'; - -export class SyncFileSystemRoutes { - private readonly basePath: string; - - constructor(basePath: string) { - this.basePath = basePath; - } - - getRoutes(): Router { - const router = Router(); - - router.use('/', async (_req, res, next) => { - if (!(await pathExists(`${this.basePath}/sync.json`))) { - res.status(404).send({ error: 'Nuclia folder not found' }); - return; - } - next(); - }); - - router.get('/', async (_req, res) => { - const data = await fs.readFile(`${this.basePath}/sync.json`, 'utf8'); - res.status(200).send(JSON.parse(data)); - }); - - router.post('/', async (req, res) => { - const dataNewSync = req.body; - const uuid = uuidv4(); - - const currentSync = JSON.parse(await fs.readFile(`${this.basePath}/sync.json`, 'utf8')); - const syncAlreadyExists = uuid in currentSync; - if (syncAlreadyExists) { - res.status(409).send({ - reason: `Sync with id ${dataNewSync.id} already exists`, - }); - return; - } - currentSync[uuid] = dataNewSync; - await fs.writeFile(`${this.basePath}/sync.json`, JSON.stringify(currentSync, null, 2)); - - res.status(201).send({ - id: dataNewSync.id, - }); - }); - - router.get('/:id', async (req, res) => { - const { id } = req.params; - try { - const currentSync = JSON.parse(await fs.readFile(`${this.basePath}/sync.json`, 'utf8')); - if (!(id in currentSync)) { - res.status(404).send(null); - } else { - res.status(200).send(currentSync[id]); - } - } catch (error) { - console.error(error); - res.status(404).send(null); - } - }); - - router.patch('/:id', async (req, res) => { - const { id } = req.params; - const dataNewSync = req.body; - try { - const currentSync = JSON.parse(await fs.readFile(`${this.basePath}/sync.json`, 'utf8')); - if (!(id in currentSync)) { - res.status(404).send(null); - } else { - currentSync[id] = { - ...currentSync[id], - ...dataNewSync, - }; - - await fs.writeFile(`${this.basePath}/sync.json`, JSON.stringify(currentSync, null, 2)); - res.status(204).send(null); - } - } catch (error) { - console.error(error); - res.status(404).send(null); - } - }); - - router.delete('/:id', async (req, res) => { - const { id } = req.params; - try { - const currentSync = JSON.parse(await fs.readFile(`${this.basePath}/sync.json`, 'utf8')); - if (!(id in currentSync)) { - res.status(404).send(null); - } else { - delete currentSync[id]; - await fs.writeFile(`${this.basePath}/sync.json`, JSON.stringify(currentSync, null, 2)); - res.status(200).send(null); - } - } catch (error) { - console.error(error); - res.status(404).send(null); - } - }); - - return router; - } -} diff --git a/electron-app/src/types/server.d.ts b/electron-app/src/types/server.d.ts index cb4b839..2cd8b74 100644 --- a/electron-app/src/types/server.d.ts +++ b/electron-app/src/types/server.d.ts @@ -1,17 +1,39 @@ export type Connector = { - type: string; + name: string; + logo: string; // eslint-disable-next-line @typescript-eslint/no-explicit-any parameters: { [key: string]: any }; }; -export type NucliaOptions = {}; -export type Classification = {}; +export interface NucliaOptions { + /** + * The Nuclia backend to use. + * + * Example: `https://nuclia.cloud/api` */ + backend: string; + /** + * The geographical zone for the regional API calls. + * + * Example: `europe-1` */ + zone: string; + /** + * The Nuclia Knowledge Box unique id. + * + * Example: `17815eb2-06a5-40ee-a5aa-b2f9dbc5da70` */ + knowledgeBox: string; + /** + * Allows you to make calls to a private Knowledge Box. + * + * It can be used in a server-side app, but never in a web app. + */ + apiKey: string; +} +export type Classification = {}; export type Sync = { connector: Connector; - destination: NucliaOptions; + kb: NucliaOptions; folders: string[]; - permanentSync?: boolean; labels?: Classification[]; - id: string; + title: string; }; diff --git a/electron-app/tests/sever.spec.js b/electron-app/tests/sever.spec.js index 2129cd2..3b7a5b6 100644 --- a/electron-app/tests/sever.spec.js +++ b/electron-app/tests/sever.spec.js @@ -71,7 +71,7 @@ describe('Server width folder', () => { const id = Object.keys(response.body)[0]; const responsePatch = await request(testServer.app).patch(`/sync/${id}`).send({ - name: 'Sync1', + title: 'Sync1', }); expect(responsePatch.status).toBe(204); }); @@ -84,7 +84,7 @@ describe('Server width folder', () => { const id = Object.keys(response.body)[0]; const responseGet = await request(testServer.app).get(`/sync/${id}`); expect(responseGet.status).toBe(200); - expect(responseGet.body['name']).toBe('Sync1'); + expect(responseGet.body['title']).toBe('Sync1'); }); test('Delete a sync', async () => { @@ -107,3 +107,24 @@ describe('Server width folder', () => { expect(Object.keys(response.body).length).toEqual(0); }); }); + +describe('Server Logs', () => { + beforeAll(async () => { + await beforeStartServer('.nuclia'); + await testServer.start(); + }); + afterAll(async () => { + await deleteDirectory('.nuclia'); + await testServer.close(); + }); + + test('Init server', async () => { + const response = await request(testServer.app).get('/'); + expect(response.status).toBe(200); + }); + + test('Get logs', async () => { + const response = await request(testServer.app).get('/logs'); + expect(response.status).toBe(200); + }); +});