feat: bruno.json validation for local collections

This commit is contained in:
Anoop M D 2022-10-18 02:34:22 +05:30
parent 8a96a0ce71
commit ff87586a1d
5 changed files with 93 additions and 24 deletions

View File

@ -21,9 +21,9 @@ const useLocalCollectionTreeSync = () => {
const { ipcRenderer } = window; const { ipcRenderer } = window;
const _openCollection = (pathname, uid) => { const _openCollection = (pathname, uid, name) => {
console.log(`collection uid: ${uid}, pathname: ${pathname}`); console.log(`collection uid: ${uid}, pathname: ${pathname}, name: ${name}`);
dispatch(openLocalCollectionEvent(uid, pathname)); dispatch(openLocalCollectionEvent(uid, pathname, name));
}; };
const _collectionTreeUpdated = (type, val) => { const _collectionTreeUpdated = (type, val) => {
@ -60,16 +60,22 @@ const useLocalCollectionTreeSync = () => {
toast.success('Collection is already opened under local collections'); toast.success('Collection is already opened under local collections');
}; };
const _displayError = (message) => {
toast.error(message || 'Something went wrong!');
};
ipcRenderer.invoke('renderer:ready'); ipcRenderer.invoke('renderer:ready');
const removeListener1 = ipcRenderer.on('main:collection-opened', _openCollection); const removeListener1 = ipcRenderer.on('main:collection-opened', _openCollection);
const removeListener2 = ipcRenderer.on('main:collection-tree-updated', _collectionTreeUpdated); const removeListener2 = ipcRenderer.on('main:collection-tree-updated', _collectionTreeUpdated);
const removeListener3 = ipcRenderer.on('main:collection-already-opened', _collectionAlreadyOpened); const removeListener3 = ipcRenderer.on('main:collection-already-opened', _collectionAlreadyOpened);
const removeListener4 = ipcRenderer.on('main:display-error', _displayError);
return () => { return () => {
removeListener1(); removeListener1();
removeListener2(); removeListener2();
removeListener3(); removeListener3();
removeListener4();
}; };
}, [isElectron]); }, [isElectron]);
}; };

View File

@ -57,11 +57,11 @@ export const loadCollectionsFromIdb = () => (dispatch) => {
.catch(() => toast.error("Error occured while loading collections from IndexedDB")); .catch(() => toast.error("Error occured while loading collections from IndexedDB"));
}; };
export const openLocalCollectionEvent = (uid, pathname) => (dispatch, getState) => { export const openLocalCollectionEvent = (uid, pathname, name) => (dispatch, getState) => {
const localCollection = { const localCollection = {
version: "1", version: "1",
uid: uid, uid: uid,
name: path.basename(pathname), name: name,
pathname: pathname, pathname: pathname,
items: [] items: []
}; };

View File

@ -22,7 +22,8 @@
"fs-extra": "^10.1.0", "fs-extra": "^10.1.0",
"is-valid-path": "^0.1.1", "is-valid-path": "^0.1.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"nanoid": "3.3.4" "nanoid": "3.3.4",
"yup": "^0.32.11"
}, },
"devDependencies": { "devDependencies": {
"electron": "^21.1.1", "electron": "^21.1.1",

View File

@ -1,8 +1,52 @@
const { uuid } = require('../utils/common'); const fs = require('fs');
const path = require('path');
const { dialog, ipcMain } = require('electron'); const { dialog, ipcMain } = require('electron');
const Yup = require('yup');
const { isDirectory, normalizeAndResolvePath } = require('../utils/filesystem'); const { isDirectory, normalizeAndResolvePath } = require('../utils/filesystem');
const openCollection = async (win, watcher) => { const uidSchema = Yup.string()
.length(21, 'uid must be 21 characters in length')
.matches(/^[a-zA-Z0-9]*$/, 'uid must be alphanumeric')
.required('uid is required')
.strict();
const configSchema = Yup.object({
uid: uidSchema,
name: Yup.string().nullable().max(256, 'name must be 256 characters or less'),
type: Yup.string().oneOf(['collection']).required('type is required'),
version: Yup.string().oneOf(['1']).required('type is required')
}).noUnknown(true).strict();
const readConfigFile = async (pathname) => {
try {
const jsonData = fs.readFileSync(pathname, 'utf8');
return JSON.parse(jsonData);
} catch(err) {
return Promise.reject(new Error("Unable to parse json in bruno.json"));
}
}
const validateSchema = async (config) => {
try {
await configSchema.validate(config);
} catch(err) {
return Promise.reject(new Error("bruno.json format is invalid"));
}
};
const getCollectionConfigFile = async (pathname) => {
const configFilePath = path.join(pathname, 'bruno.json');
if (!fs.existsSync(configFilePath)){
throw new Error(`The collection is not valid (bruno.json not found)`);
}
const config = await readConfigFile(configFilePath);
await validateSchema(config);
return config;
}
const openCollectionDialog = async (win, watcher) => {
const { filePaths } = await dialog.showOpenDialog(win, { const { filePaths } = await dialog.showOpenDialog(win, {
properties: ['openDirectory', 'createDirectory'] properties: ['openDirectory', 'createDirectory']
}); });
@ -10,19 +54,37 @@ const openCollection = async (win, watcher) => {
if (filePaths && filePaths[0]) { if (filePaths && filePaths[0]) {
const resolvedPath = normalizeAndResolvePath(filePaths[0]); const resolvedPath = normalizeAndResolvePath(filePaths[0]);
if (isDirectory(resolvedPath)) { if (isDirectory(resolvedPath)) {
if(!watcher.hasWatcher(resolvedPath)) { openCollection(win, watcher, resolvedPath);
const uid = uuid();
win.webContents.send('main:collection-opened', resolvedPath, uid);
ipcMain.emit('main:collection-opened', win, resolvedPath, uid);
} else {
win.webContents.send('main:collection-already-opened', resolvedPath);
}
} else { } else {
console.error(`[ERROR] Cannot open unknown folder: "${resolvedPath}"`); console.error(`[ERROR] Cannot open unknown folder: "${resolvedPath}"`);
} }
} }
}
const openCollection = async (win, watcher, collectionPath, options = {}) => {
if(!watcher.hasWatcher(collectionPath)) {
try {
const {
uid,
name
} = await getCollectionConfigFile(collectionPath);
console.log(uid);
console.log(name);
win.webContents.send('main:collection-opened', collectionPath, uid, name);
ipcMain.emit('main:collection-opened', win, collectionPath, uid);
} catch(err) {
if(!options.dontSendDisplayErrors) {
win.webContents.send('main:display-error', err.message || 'An error occured while opening the local collection');
}
}
} else {
win.webContents.send('main:collection-already-opened', collectionPath);
}
}; };
module.exports = { module.exports = {
openCollection openCollection,
openCollectionDialog
}; };

View File

@ -11,7 +11,7 @@ const {
createDirectory createDirectory
} = require('../utils/filesystem'); } = require('../utils/filesystem');
const { uuid, stringifyJson, parseJson } = require('../utils/common'); const { uuid, stringifyJson, parseJson } = require('../utils/common');
const { openCollection } = require('../app/collections'); const { openCollectionDialog, openCollection } = require('../app/collections');
const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollections) => { const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollections) => {
// browse directory // browse directory
@ -41,14 +41,14 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
const uid = uuid(); const uid = uuid();
const content = await stringifyJson({ const content = await stringifyJson({
version: '1.0', version: '1',
uid: uid, uid: uid,
name: collectionName, name: collectionName,
type: 'collection' type: 'collection'
}); });
await writeFile(path.join(dirPath, 'bruno.json'), content); await writeFile(path.join(dirPath, 'bruno.json'), content);
mainWindow.webContents.send('main:collection-opened', dirPath, uid); mainWindow.webContents.send('main:collection-opened', dirPath, uid, collectionName);
ipcMain.emit('main:collection-opened', mainWindow, dirPath, uid); ipcMain.emit('main:collection-opened', mainWindow, dirPath, uid);
return; return;
@ -149,7 +149,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
ipcMain.handle('renderer:open-collection', () => { ipcMain.handle('renderer:open-collection', () => {
if(watcher && mainWindow) { if(watcher && mainWindow) {
openCollection(mainWindow, watcher); openCollectionDialog(mainWindow, watcher);
} }
}); });
@ -168,9 +168,9 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
if(lastOpened && lastOpened.length) { if(lastOpened && lastOpened.length) {
for(let collectionPath of lastOpened) { for(let collectionPath of lastOpened) {
if(isDirectory(collectionPath)) { if(isDirectory(collectionPath)) {
const uid = uuid(); openCollection(mainWindow, watcher, collectionPath, {
mainWindow.webContents.send('main:collection-opened', collectionPath, uid); dontSendDisplayErrors: true
ipcMain.emit('main:collection-opened', mainWindow, collectionPath, uid); });
} }
} }
} }
@ -180,7 +180,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
const registerMainEventHandlers = (mainWindow, watcher, lastOpenedCollections) => { const registerMainEventHandlers = (mainWindow, watcher, lastOpenedCollections) => {
ipcMain.on('main:open-collection', () => { ipcMain.on('main:open-collection', () => {
if(watcher && mainWindow) { if(watcher && mainWindow) {
openCollection(mainWindow, watcher); openCollectionDialog(mainWindow, watcher);
} }
}); });