Skip to content

Commit

Permalink
fixed graph realted function. updated all versions of system schema t…
Browse files Browse the repository at this point in the history
…o 1.0
  • Loading branch information
shubhvjain committed Feb 9, 2025
1 parent 5f45420 commit 7eb7f54
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 46 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "beanbagdb",
"version": "0.6.4",
"version": "0.6.5",
"description": "A JS library to introduce a schema layer to a No-SQL local database",
"main": "src/index.js",
"module": "src/index.js",
Expand Down
49 changes: 33 additions & 16 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ export class BeanBagDB {
throw new DocCreationError(`${v_errors.join(",")}.`)
}

if(input.schema=="setting_edge"){throw new DocCreationError("This type of record can only be created through the create_edge api")}
// if(input.schema=="system_edge"){throw new DocCreationError("This type of record can only be created through the create_edge api")}
if(Object.keys(input.data).length==0){throw new DocCreationError(`No data provided`)}
try {
let doc_obj = await this._insert_pre_checks(input.schema, input.data,input?.meta||{}, input?.settings||{});
Expand Down Expand Up @@ -564,6 +564,10 @@ export class BeanBagDB {
// todo : what if additionalField are allowed ??
let updated_data = { ...full_doc.data, ...allowed_updates };

if(full_doc.schema=="system_edge"){
// extra checks required. if everything is correct
updated_data = await this._create_edge(updated_data)
}
updated_data = this.util_validate_data({schema:schema.schema,data: updated_data});

// primary key check if multiple records can be created
Expand Down Expand Up @@ -622,8 +626,6 @@ export class BeanBagDB {
}




/**
* Deletes a document from the database by its ID.
*
Expand Down Expand Up @@ -821,21 +823,31 @@ export class BeanBagDB {
* @param {object} node1
* @param {object} node2
* @param {string} edge_name
* @param {*} edge_label
* @param {string} note
* @returns {Object}
*/
async create_edge(input){
const {node1,node2,edge_name,edge_label=""} = input
async _create_edge(input){
console.log(input)
let {node1,node2,edge_name,note=""} = input
this._check_ready_to_use();
if(!edge_name){throw new ValidationError("edge_name required")}
if(Object.keys(node1)==0){throw new ValidationError("node1 required")}
if(Object.keys(node2)==0){throw new ValidationError("node2 required")}

if(!node1|| Object.keys(node1).length==0){throw new ValidationError("node1 required")}
if(!node2|| Object.keys(node2).length==0){throw new ValidationError("node2 required")}
// if nodes are of type string, they are assumed to be ids since they are stored in the DB as ids
if(typeof(node1)=="string"){node1 = {_id:node1}}
if(typeof(node2)=="string"){node2 = {_id:node2}}
let n1 = await this.read(node1)
let n2 = await this.read(node2)
if(n1.doc._id==n2.doc._id){
throw new ValidationError("Both nodes cannot be the same")
}
if(n1.doc.schema=="system_edge"|| n2.doc.schema=="system_edge"){
throw new ValidationError("A node cannot be an existing edge document")
}
let edges_constraint

try {
// check if edge_name has a related edge_constraint
let d = await this.read({schema:"system_edge_constraint",data:{name:edge_name}})
edges_constraint = d["doc"]["data"]
let errors = []
Expand All @@ -850,7 +862,7 @@ async create_edge(input){
node2id = n1.doc._id
}
}else{
this.errors.push("Invalid nodes.This config of nodes not allowed")
errors.push("Invalid nodes.This config of nodes not allowed")
}

let records = await this.search({selector:{schema:"system_edge","data.edge_name":edge_name}})
Expand All @@ -870,18 +882,17 @@ async create_edge(input){
}

if(errors.length==0){
let edge = await this.create({schema:"system_edge",data:{node1: node1id , node2: node1id ,edge_name:edge_name }})
return edge
// let edge = await this.create({schema:"system_edge",data:})
return {node1: node1id , node2: node2id ,edge_name:edge_name ,note:note}
}else{
throw new RelationError(errors)
}

} catch (error) {
if(error instanceof DocNotFoundError){
let doc = {node1:"*",node2:"*",name:edge_name,label:edge_label}
let doc = {node1:"*",node2:"*",name:edge_name,note:note}
let new_doc = await this.create({schema:"system_edge_constraint",data:doc})
let edge = await this.create({schema:"system_edge",data:{node1: n1.doc._id,node2: n2.doc._id,edge_name:edge_name }})
return edge
return {node1: n1.doc._id,node2: n2.doc._id,edge_name:edge_name ,note:note}
}else{
throw error
}
Expand Down Expand Up @@ -1142,7 +1153,10 @@ async _upgrade_schema_in_bulk(schemas,log_upgrade=false,log_message="Schema Upgr
// validate data
if(!schemaDoc.active){throw new DocCreationError(`The schema "${schema}" is not active`)}

let new_data = this.util_validate_data({schema:schemaDoc.schema, data});
let new_data
if(schema!="system_edge"){
new_data = this.util_validate_data({schema:schemaDoc.schema, data});
}

// validate meta
if(Object.keys(meta).length>0){
Expand All @@ -1161,6 +1175,9 @@ async _upgrade_schema_in_bulk(schemas,log_upgrade=false,log_message="Schema Upgr
if (schema == "schema") {
//more checks are required
this.util_validate_schema_object(new_data);
}else if(schema == "system_edge"){
let create_edge_after_checks = await this._create_edge(data)
new_data = create_edge_after_checks
}
// @TODO : check if single record setting is set to true
//console.log(schemaDoc)
Expand Down
24 changes: 14 additions & 10 deletions src/system_schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const default_app = {
active: true,
description: "Meta-schema or the schema for defining other schemas",
system_generated: true,
version: 0.88,
version: 1,
title: "Schema document",
schema: {
type: "object",
Expand Down Expand Up @@ -146,7 +146,7 @@ export const default_app = {
},
{
system_generated: true,
version: 0.63,
version: 1,
description:
"To store user defined key. this can include anything like API tokens etc. There is a special method to fetch this. The values are encrypted",
name: "system_key",
Expand Down Expand Up @@ -184,7 +184,7 @@ export const default_app = {
},
},
{
version: 0.67,
version: 1,
system_generated: true,
description:
"The system relies on these settings for proper functioning or enabling optional features.",
Expand Down Expand Up @@ -218,7 +218,7 @@ export const default_app = {
title: "Edge constraint",
system_generated: true,
active: true,
version: 0.60,
version: 1,
description:
"To define edge constraints for simple directed graph of records.",
schema: {
Expand Down Expand Up @@ -275,12 +275,12 @@ export const default_app = {
title: "Edge in the system graph",
active: true,
system_generated: true,
version: 0.52,
version: 1,
description: "To define edges in the simple directed graph of records.",
schema: {
type: "object",
additionalProperties: true,
required: ["node1", "node2", "edge_type"],
required: ["node1", "node2", "edge_name"],
properties: {
node1: {
type: "string",
Expand All @@ -291,10 +291,14 @@ export const default_app = {
edge_name: {
type: "string",
},
note:{
type:"string",
default:" "
}
},
},
settings: {
primary_keys: ["node1", "node2", "edge_type"],
primary_keys: ["node1", "node2", "edge_name"],
non_editable_fields: ["edge_type"],
encrypted_fields: [],
},
Expand All @@ -304,7 +308,7 @@ export const default_app = {
title: "Media content",
active: true,
system_generated: true,
version: 0.62,
version: 1,
description: "To store images as Base64",
schema: {
type: "object",
Expand Down Expand Up @@ -336,7 +340,7 @@ export const default_app = {
system_generated: true,
title: "System log",
active: true,
version: 0.52,
version: 1,
description: "To define edges in the simple directed graph of records.",
schema: {
type: "object",
Expand Down Expand Up @@ -369,7 +373,7 @@ export const default_app = {
system_generated: true,
title: "Executable script",
active: true,
version: 0.2,
version: 1,
description: "To create scripts that implement some logic. Can run both on browser and client.",
schema: {
type: "object",
Expand Down
95 changes: 76 additions & 19 deletions test/couchdb.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// const crypto = require('crypto');
import {BeanBagDB} from '../src/index.js';
import crypto from 'crypto'
// const SDB = require("beanbagdb")

import Ajv from 'ajv';
import nano from "nano";
import BeanBagDB from '../src/index.js';

export class BeanBagDB_CouchDB extends BeanBagDB {
constructor(db_url,db_name,encryption_key){
Expand Down Expand Up @@ -41,30 +41,87 @@ export class BeanBagDB_CouchDB extends BeanBagDB {
}
},
utils:{
encrypt: async (text,encryptionKey)=>{
const key = crypto.scryptSync(encryptionKey, 'salt', 32); // Derive a 256-bit key
const iv = crypto.randomBytes(16); // Initialization vector
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
return iv.toString('hex') + ':' + encrypted; // Prepend the IV for later use
encrypt: async (text, encryptionKey) => {
const encoder = new TextEncoder();
const data = encoder.encode(text); // Encode the text into bytes

// Ensure the encryption key is of valid length (16, 24, or 32 bytes for AES-GCM)
const keyBytes = encoder.encode(encryptionKey);
if (
keyBytes.length !== 16 &&
keyBytes.length !== 24 &&
keyBytes.length !== 32
) {
throw new Error("Encryption key must be 16, 24, or 32 bytes long.");
}

// Convert encryptionKey to CryptoKey
const key = await crypto.subtle.importKey(
"raw",
keyBytes,
{ name: "AES-GCM" },
false,
["encrypt"]
);

// Create a random initialization vector (IV)
const iv = crypto.getRandomValues(new Uint8Array(12)); // 12 bytes for AES-GCM

// Encrypt the data
const encrypted = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv: iv },
key,
data
);

// Convert encrypted data and IV to base64 for storage
const encryptedArray = new Uint8Array(encrypted);
const encryptedText = btoa(String.fromCharCode(...encryptedArray));
const ivText = btoa(String.fromCharCode(...iv));

return ivText + ":" + encryptedText; // Store IV and encrypted text together
},
decrypt : async (encryptedText, encryptionKey)=>{
const key = crypto.scryptSync(encryptionKey, 'salt', 32); // Derive a 256-bit key
const [iv, encrypted] = encryptedText.split(':').map(part => Buffer.from(part, 'hex'));
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
decrypt: async (encryptedText, encryptionKey) => {
const [ivText, encryptedData] = encryptedText.split(":");

// Convert IV and encrypted data from base64 to byte arrays
const iv = Uint8Array.from(atob(ivText), (c) => c.charCodeAt(0));
const encryptedArray = Uint8Array.from(atob(encryptedData), (c) =>
c.charCodeAt(0)
);

const encoder = new TextEncoder();

// Convert encryptionKey to CryptoKey
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(encryptionKey),
{ name: "AES-GCM" },
false,
["decrypt"]
);

// Decrypt the data
const decrypted = await crypto.subtle.decrypt(
{ name: "AES-GCM", iv: iv },
key,
encryptedArray
);

// Convert decrypted data back to a string
const decoder = new TextDecoder();
return decoder.decode(decrypted);
},
ping : ()=>{
// @TODO ping the database to check connectivity when class is ready to use
},
validate_schema: (schema_obj, data_obj)=>{
const ajv = new Ajv({code: {esm: true}}) // options can be passed, e.g. {allErrors: true}
const ajv = new Ajv({code: {esm: true},strict:false,useDefaults:true}) // options can be passed, e.g. {allErrors: true}
const data_copy = {...data_obj}
const validate = ajv.compile(schema_obj);
const valid = validate(data_obj);
return {valid,validate}
const valid = validate(data_copy);

return {valid,validate,data:data_copy}
}
}
}
Expand Down

0 comments on commit 7eb7f54

Please sign in to comment.