Skip to content

Commit

Permalink
feat: new plugin for SQL database and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
RebeccaSelvaggini committed May 16, 2024
1 parent 4d0b8f7 commit 65b658d
Show file tree
Hide file tree
Showing 8 changed files with 1,628 additions and 78 deletions.
35 changes: 35 additions & 0 deletions pkg/db/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "@slangroom/db",
"version": "1.28.2",
"dependencies": {
"@slangroom/core": "workspace:*",
"@slangroom/shared": "workspace:*",
"sequelize": "^6.16.0",
"sqlite3": "^5.0.0"
},
"repository": "https://github.com/dyne/slangroom",
"license": "AGPL-3.0-only",
"type": "module",
"main": "./build/esm/src/index.js",
"types": "./build/esm/src/index.d.ts",
"exports": {
".": {
"import": {
"types": "./build/esm/src/index.d.ts",
"default": "./build/esm/src/index.js"
}
},
"./*": {
"import": {
"types": "./build/esm/src/*.d.ts",
"default": "./build/esm/src/*.js"
}
}
},
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@types/supertest": "^6.0.2"
}
}
5 changes: 5 additions & 0 deletions pkg/db/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// SPDX-FileCopyrightText: 2024 Dyne.org foundation
//
// SPDX-License-Identifier: AGPL-3.0-or-later

export * from '@slangroom/db/plugin';
16 changes: 16 additions & 0 deletions pkg/db/src/interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export interface QueryGetRecord {
id: string;
table: string;
database: string;
}

export interface QuerySaveVar {
varName: string;
varObj: object;
database: string;
table: string;
}

export interface ObjectLiteral {
[key: string]: any;
}
214 changes: 214 additions & 0 deletions pkg/db/src/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
// SPDX-FileCopyrightText: 2024 Dyne.org foundation
//
// SPDX-License-Identifier: AGPL-3.0-or-later

import { Plugin } from '@slangroom/core';
import { Jsonable } from '@slangroom/shared';
import { BindOrReplacements, DataTypes, Model, Sequelize } from "sequelize";
import { QueryGetRecord, QuerySaveVar } from "./interfaces.js";

class Result extends Model {
public result!: string;
}

export class DbError extends Error {
constructor(e: string) {
super(e)
this.name = 'Slangroom @slangroom/db Error'
}
}

type DK = string | object | null | undefined;

function safeJSONParse(o: DK, errorMessage?: string): ({ ok: true, parsed: object } | { ok: false, error: string }) {
const notNull = o ?? {};
if (typeof notNull === "object") return { ok: true, parsed: notNull };
try {
return { ok: true, parsed: JSON.parse(notNull) };
} catch (err) {
return { ok: false, error: errorMessage ?? err };
}
}

const p = new Plugin();

/**
* @internal
*/

/**
*
* @param {string} statement name of the SQL statement
* @param {string} database keyName of the database
*/
export const execute = p.new('connect',
['statement'],
'execute sql statement',
async (ctx) => {
const statement = ctx.fetch('statement') as string;
const database = ctx.fetchConnect()[0] as string;
try {
const db = new Sequelize(database, {
// disable logging; default: console.log
logging: false
});
const t = await db.transaction();
const [o, m] = await db.query(statement, { transaction: t });
await t.commit();
const output = { output: o ? o : m } as Jsonable;
db.close();
return ctx.pass(output);
} catch (error) {
return ctx.fail(new DbError(error.message));
}
},
);

/**
* @internal
*/

export const executeParams = p.new('connect',
['statement', 'parameters'],
'execute sql statement with parameters',
async (ctx) => {
const statement = ctx.fetch('statement') as string;
const parameters = ctx.fetch('parameters') as BindOrReplacements;
const database = ctx.fetchConnect()[0] as string;
try {
const db = new Sequelize(database, { logging: false });
const t = await db.transaction();
const [o, m] = await db.query(statement, {
transaction: t,
replacements: parameters
});
await t.commit();
const output = { output: o ? o : m } as Jsonable;
db.close();
return ctx.pass(output);
} catch (error) {
return ctx.fail(new DbError(error.message));
}
},
);

/**
* @internal
*/
/**
* @param {string} record name of the field (row)
* @param {string} table keyName of the table
* @param {string} database keyName of the database
*/
export const getRecord = p.new('connect',
['record', 'table'],
'read the record of the table',
async (ctx) => {
const record = ctx.fetch('record') as string;
const table = ctx.fetch('table') as string;
const database = ctx.fetchConnect()[0] as string;

const parse = (o: string) => safeJSONParse(o, `[DATABASE] Error in JSON format "${o}"`)

// create object(s) with the FOUR values of each GET_RECORD
const dbQueries: QueryGetRecord[] = [];

dbQueries.push({
id: record,
table: table,
database: database
});
try {
var output = {}
for (const query of dbQueries) {
const db = new Sequelize(query.database, { logging: false });
Result.init(
{ result: DataTypes.TEXT },
{
tableName: query.table,
freezeTableName: true,
sequelize: db,
}
);
await Result.sync();
try {
let result = await Result.findByPk(query.id);
if (result) {
result = result.get({ plain: true });
// column name is result
const resultData = parse(result!.result);
if (!resultData.ok) return ctx.fail(new DbError(resultData.error));
output = resultData.parsed;
} else {
return ctx.fail(new DbError(`[DATABASE]
Returned null for id "${query.id}" in table "${query.table}" in db "${query.database}".`));
}
} catch (e) {
return ctx.fail(new DbError(`[DATABASE]
Something went wrong for id "${query.id}" in table "${query.table}" in db "${query.database}".`));
}
db.close();
}
} catch (e) {
return ctx.fail(new DbError(`[DATABASE] Database error: ${e}`));
}
return ctx.pass(output);
},
);

/**
* @internal
*/
export const saveVar = p.new('connect',
['variable', 'name', 'table'],
'save the variable in the database table',
async (ctx) => {
const elem = ctx.fetch('variable') as object;
const varName = ctx.fetch('name') as string;
const database = ctx.fetchConnect()[0] as string;
const table = ctx.fetch('table') as string;

const dbQueries: QuerySaveVar[] = [];

// create object(s) with the THREE values in each GET_RECORD
dbQueries.push({
varName: varName,
varObj: elem,
database: database,
table: table,
});

try {
for (const query of dbQueries) {

const db = new Sequelize(query.database, { logging: false });
Result.init(
{ result: DataTypes.TEXT },
{
tableName: query.table,
freezeTableName: true,
sequelize: db,
}
);
await Result.sync();
try {
// column name must be result
await Result.create({
result: JSON.stringify({
[query.varName]: query.varObj,
}),
});
} catch (e) {
return ctx.fail(new DbError(`[DATABASE]
Error in table "${query.table}" in db "${query.database}": ${e}`));
}
db.close();
}
} catch (e) {
return ctx.fail(new DbError(`[DATABASE] Database error: ${e}`));
}
return ctx.pass(null);
},
);

export const db = p;
Loading

0 comments on commit 65b658d

Please sign in to comment.