This commit is contained in:
2025-12-22 07:18:57 +01:00
parent 0d9f313dc0
commit cfcd08680f
46 changed files with 1338 additions and 98 deletions
@@ -0,0 +1,26 @@
import { APIPathsCrudParams, APIResponseObject } from "../../types";
import dsqlCrud from "../../utils/data-fetching/crud";
export default async function <
T extends { [k: string]: any } = { [k: string]: any }
>({
table,
body,
targetId,
}: APIPathsCrudParams<T>): Promise<APIResponseObject> {
if (!targetId && !body?.searchQuery) {
throw new Error(
`Target ID or \`searchQuery\` field is required to delete Data.`
);
}
const DELETE_RESLT = await dsqlCrud({
...body?.crudParams,
action: "delete",
table,
query: body?.searchQuery,
targetId,
});
return DELETE_RESLT;
}
@@ -0,0 +1,29 @@
import { APIPathsCrudParams, APIResponseObject } from "../../types";
import dsqlCrud from "../../utils/data-fetching/crud";
export default async function <
T extends { [k: string]: any } = { [k: string]: any }
>({
table,
query,
allowedTable,
url,
}: APIPathsCrudParams<T>): Promise<APIResponseObject> {
if (
(allowedTable?.allowedFields || allowedTable?.disallowedFields) &&
!query?.searchQuery?.selectFields?.[0]
) {
throw new Error(
`Please specify fields to select! Use the \`selectFields\` option in the query object`
);
}
const GET_RESULT = await dsqlCrud({
...query?.crudParams,
action: "get",
table,
query: query?.searchQuery,
});
return GET_RESULT;
}
@@ -0,0 +1,15 @@
import { APIPathsCrudParams, APIResponseObject } from "../../types";
import dsqlCrud from "../../utils/data-fetching/crud";
export default async function <
T extends { [k: string]: any } = { [k: string]: any }
>({ table, body }: APIPathsCrudParams<T>): Promise<APIResponseObject> {
const POST_RESULT = await dsqlCrud({
...body?.crudParams,
action: "insert",
table,
data: body?.data,
});
return POST_RESULT;
}
@@ -0,0 +1,27 @@
import { APIPathsCrudParams, APIResponseObject } from "../../types";
import dsqlCrud from "../../utils/data-fetching/crud";
export default async function <
T extends { [k: string]: any } = { [k: string]: any }
>({
table,
body,
targetId,
}: APIPathsCrudParams<T>): Promise<APIResponseObject> {
if (!targetId && !body?.searchQuery) {
throw new Error(
`Target ID or \`searchQuery\` field is required to update Data.`
);
}
const PUT_RESULT = await dsqlCrud({
...body?.crudParams,
action: "update",
table,
data: body?.data,
query: body?.searchQuery,
targetId,
});
return PUT_RESULT;
}
+74
View File
@@ -0,0 +1,74 @@
import _ from "lodash";
import {
APIPathsCrudParams,
APIPathsParams,
APIResponseObject,
} from "../types";
import getResult from "./functions/get-result";
import { grabPathData } from "./utils/grab-path-data";
import checks from "./utils/checks";
import postResult from "./functions/post-result";
import putResult from "./functions/put-result";
import deleteResult from "./functions/delete-result";
export default async function apiCrudHandler<
T extends { [k: string]: any } = { [k: string]: any }
>(params: APIPathsParams<T>): Promise<APIResponseObject> {
try {
const { auth, method } = params;
const isAuthorized = await auth?.();
const { table, targetId, url, query } = grabPathData<T>(params);
const crudParams: APIPathsCrudParams<T> = {
...params,
isAuthorized,
table,
targetId,
};
const checkedObj = await checks<T>({
...crudParams,
query: _.merge(params.query || {}, query),
});
crudParams.query = checkedObj.query;
crudParams.body = checkedObj.body;
crudParams.allowedTable = checkedObj.allowedTable;
crudParams.url = url;
if (targetId) {
if (crudParams.query) {
if (crudParams.query.crudParams) {
crudParams.query.crudParams.targetId = targetId;
} else {
crudParams.query.crudParams = {
targetId,
};
}
}
}
switch (method) {
case "GET":
return await getResult(crudParams);
case "POST":
return await postResult(crudParams);
case "PUT":
return await putResult(crudParams);
case "DELETE":
return await deleteResult(crudParams);
}
return {
success: false,
msg: `Unhandled`,
};
} catch (error: any) {
return {
success: false,
msg: error.message,
};
}
}
+166
View File
@@ -0,0 +1,166 @@
import _ from "lodash";
import { APIPathsCrudParams, APIPathsParamsAllowedTable } from "../../types";
export default async function checks<
T extends { [k: string]: any } = { [k: string]: any }
>({
table,
allowedTables,
query,
body,
method,
getMiddleware,
postMiddleware,
putMiddleware,
deleteMiddleware,
crudMiddleware,
}: APIPathsCrudParams<T>): Promise<
Pick<APIPathsCrudParams<T>, "query" | "body"> & {
allowedTable: APIPathsParamsAllowedTable;
}
> {
const allowedTable = allowedTables.find((tbl) => tbl.table == table);
if (!allowedTable) {
throw new Error(`Can't Access this table: \`${table}\``);
}
let newQuery = _.cloneDeep(query);
let newBody = _.cloneDeep(body);
const searchFields = Object.keys(newQuery?.searchQuery?.query || {});
const selectFields = (
newQuery?.searchQuery?.selectFields
? newQuery.searchQuery.selectFields.map((f) =>
typeof f == "string"
? f
: typeof f == "object"
? f.fieldName
: undefined
)
: undefined
)?.filter((f) => typeof f == "string");
const targetFields = [...(searchFields || []), ...(selectFields || [])];
if (method == "GET" && allowedTable.allowedFields) {
for (let i = 0; i < targetFields.length; i++) {
const fld = targetFields[i];
const allowedFld = allowedTable.allowedFields.find((f) =>
typeof f == "string" ? f == fld : fld.match(f)
);
if (!allowedFld) {
throw new Error(`\`${allowedFld}\` field not allowed`);
}
}
}
if (method == "GET" && allowedTable.disallowedFields) {
for (let i = 0; i < targetFields.length; i++) {
const fld = targetFields[i];
const disallowedFld = allowedTable.disallowedFields.find((f) =>
typeof f == "string" ? f == fld : fld.match(f)
);
if (disallowedFld) {
throw new Error(`\`${disallowedFld}\` field not allowed`);
}
}
}
if (method == "GET" && getMiddleware) {
newQuery = await getMiddleware({ query: newQuery || ({} as any) });
}
if (method !== "GET" && crudMiddleware) {
const middRes = await crudMiddleware({
body: newBody || ({} as any),
query: newQuery || {},
});
newBody = _.merge(newBody, middRes);
}
if (method == "POST" && postMiddleware) {
const middRes = await postMiddleware({
body: newBody || ({} as any),
query: newQuery || {},
});
newBody = _.merge(newBody, middRes);
}
if (method == "PUT" && putMiddleware) {
const middRes = await putMiddleware({
body: newBody || ({} as any),
query: newQuery || {},
});
newBody = _.merge(newBody, middRes);
}
if (method == "DELETE" && deleteMiddleware) {
const middRes = await deleteMiddleware({
body: newBody || ({} as any),
query: newQuery || {},
});
newBody = _.merge(newBody, middRes);
}
if (newQuery?.searchQuery?.join) {
for (let i = 0; i < newQuery.searchQuery.join.length; i++) {
const join = newQuery.searchQuery.join[i];
const joinTableName = join.tableName;
const selectFields = join.selectFields;
if (allowedTables?.[0]) {
const allowedJoinTable = allowedTables.find(
(t) => t.table == joinTableName
);
if (!allowedJoinTable?.table) {
throw new Error(`Can't joint \`${joinTableName}\` table`);
}
const allowedFields = allowedJoinTable.allowedFields;
const disallowedFields = allowedJoinTable.disallowedFields;
if (selectFields?.[0]) {
for (let j = 0; j < selectFields.length; j++) {
const selectField = selectFields[j];
const selectFieldName =
typeof selectField == "object"
? selectField.field
: String(selectField);
if (
allowedFields?.[0] &&
!allowedFields.find(
(f) => String(f) == selectFieldName
)
) {
throw new Error(`Can't Select this Field!`);
}
if (
disallowedFields?.[0] &&
disallowedFields.find(
(f) => String(f) == selectFieldName
)
) {
throw new Error(`Disallowed Field Selected!`);
}
}
}
}
}
}
return {
query: newQuery,
body: newBody,
allowedTable,
};
}
@@ -0,0 +1,34 @@
import { APIPathsData, APIPathsParams, APIPathsQuery } from "../../types";
import deserializeQuery from "../../utils/deserialize-query";
export function grabPathData<
T extends { [k: string]: any } = { [k: string]: any }
>({ href, basePath }: APIPathsParams<T>): APIPathsData<T> {
const urlObj = new URL(href);
const pathname = basePath
? urlObj.pathname.replace(basePath, "")
: urlObj.pathname;
const urlArray = pathname.split("/").filter((u) => Boolean(u.match(/./)));
const table = urlArray[0];
const targetId = urlArray[1];
let query = (
urlObj?.searchParams
? deserializeQuery(Object.fromEntries(urlObj.searchParams))
: undefined
) as APIPathsQuery<T> | undefined;
if (!table) {
throw new Error(`No Table Found`);
}
return {
table,
targetId,
query,
url: urlObj,
};
}
@@ -1,4 +1,10 @@
import { DSQL_TableSchemaType, PostInsertReturn } from "../../../types";
import { format } from "sql-formatter";
import {
APIResponseObject,
DSQL_TableSchemaType,
DsqlCrudParamWhereClause,
PostInsertReturn,
} from "../../../types";
import checkIfIsMaster from "../../../utils/check-if-is-master";
import connDbHandler from "../../../utils/db/conn-db-handler";
import { DbContextsArray } from "./runQuery";
@@ -8,9 +14,10 @@ type Param<T extends { [k: string]: any } = any, K extends string = string> = {
dbFullName?: string;
tableName: K;
tableSchema?: DSQL_TableSchemaType;
identifierColumnName: keyof T;
identifierValue: string | number;
identifierColumnName?: keyof T;
identifierValue?: string | number;
forceLocal?: boolean;
whereClauseObject?: DsqlCrudParamWhereClause;
};
/**
@@ -27,7 +34,8 @@ export default async function deleteDbEntry<
identifierColumnName,
identifierValue,
forceLocal,
}: Param<T, K>): Promise<PostInsertReturn | null> {
whereClauseObject,
}: Param<T, K>): Promise<APIResponseObject<PostInsertReturn>> {
try {
const isMaster = forceLocal
? true
@@ -38,21 +46,46 @@ export default async function deleteDbEntry<
*
* @description
*/
const query = `DELETE FROM ${
let query = `DELETE FROM ${
isMaster && !dbFullName ? "" : `\`${dbFullName}\`.`
}\`${tableName}\` WHERE \`${identifierColumnName.toString()}\`=?`;
}\`${tableName}\``;
const deletedEntry = await connDbHandler({
let values: any[] = [];
if (whereClauseObject) {
query += ` ${whereClauseObject.clause}`;
values.push(...(whereClauseObject.params || []));
} else if (identifierColumnName && identifierValue) {
query += ` WHERE \`${identifierColumnName.toString()}\`=?`;
values = [identifierValue];
} else {
throw new Error(
`Delete operation has no specified rows! Can't delete everything in this table!`
);
}
const deletedEntry = (await connDbHandler({
query,
values: [identifierValue],
});
values,
})) as PostInsertReturn;
/**
* Return statement
*/
return deletedEntry;
return {
success: Boolean(deletedEntry.affectedRows),
payload: deletedEntry,
queryObject: {
sql: format(query),
params: values,
},
};
} catch (error: any) {
console.log("Error Deleting Entry =>", error.message);
return null;
const errorMsg = `Error Deleting Entry =>, ${error.message}`;
console.log(errorMsg);
return {
success: false,
msg: errorMsg,
};
}
}
@@ -33,6 +33,7 @@ export default function grabParsedValue({
if (typeof newValue == "undefined") {
return;
}
if (
typeof newValue !== "string" &&
typeof newValue !== "number" &&
@@ -94,7 +95,7 @@ export default function grabParsedValue({
typeof newValue === "string" &&
(newValue.match(/^null$/i) || !newValue.match(/./i))
) {
newValue = undefined;
newValue = "";
}
if (
@@ -5,6 +5,7 @@ import { DbContextsArray } from "./runQuery";
import {
APIResponseObject,
DSQL_TableSchemaType,
DsqlCrudParamWhereClause,
PostInsertReturn,
} from "../../../types";
import _ from "lodash";
@@ -25,6 +26,7 @@ type Param<T extends { [k: string]: any } = any> = {
forceLocal?: boolean;
debug?: boolean;
dbConfig?: ConnectionConfig;
whereClauseObject?: DsqlCrudParamWhereClause;
};
/**
@@ -46,6 +48,7 @@ export default async function updateDbEntry<
forceLocal,
debug,
dbConfig,
whereClauseObject,
}: Param<T>): Promise<APIResponseObject<PostInsertReturn>> {
/**
* Check if data is valid
@@ -135,13 +138,17 @@ export default async function updateDbEntry<
////////////////////////////////////////
////////////////////////////////////////
const query = `UPDATE ${
let query = `UPDATE ${
isMaster && !dbFullName ? "" : `\`${dbFullName}\`.`
}\`${tableName}\` SET ${updateKeyValueArray.join(",")} WHERE \`${
identifierColumnName as string
}\`=?`;
}\`${tableName}\` SET ${updateKeyValueArray.join(",")}`;
updateValues.push(identifierValue);
if (whereClauseObject) {
query += ` ${whereClauseObject.clause}`;
updateValues.push(...(whereClauseObject.params || []));
} else {
query += ` WHERE \`${identifierColumnName as string}\`=?`;
updateValues.push(identifierValue);
}
const updatedEntry = await connDbHandler({
query,
+111
View File
@@ -1543,6 +1543,11 @@ export type DsqlCrudParam<
dbConfig?: ConnectionConfig;
};
export type DsqlCrudParamWhereClause = {
clause: string;
params?: string[];
};
export type ErrorCallback = (title: string, error: Error, data?: any) => void;
export interface MariaDBUser {
@@ -2840,3 +2845,109 @@ export type CrudQueryObject<
userKey?: string;
crudParams?: Omit<DsqlCrudParam<P>, "action" | "table">;
};
export type APIPathsParams<
T extends { [k: string]: any } = { [k: string]: any }
> = {
/**
* Full URL with http and query
* @example
* https://example.com/api/table?searchQuery={}
*/
href: string;
/**
* Base path before the dynamic path of the url.
* If no base path URL will be taken as the complete
* dynamic url
*/
basePath?: string;
auth?: () => Promise<boolean>;
getMiddleware?: (params: {
query: APIPathsQuery<T>;
}) => Promise<APIPathsQuery<T>>;
postMiddleware?: APIPathsParamsCrudMiddleware<T>;
putMiddleware?: APIPathsParamsCrudMiddleware<T>;
deleteMiddleware?: APIPathsParamsCrudMiddleware<T>;
/** Runs For `POST`, `PUT`, and `DELETE` */
crudMiddleware?: APIPathsParamsCrudMiddleware<T>;
method: "GET" | "POST" | "PUT" | "DELETE";
/**
* Request Query
*/
query?: APIPathsQuery<T>;
/**
* Request Body
*/
body?: APIPathsBody<T>;
allowedTables: APIPathsParamsAllowedTable[];
};
export type APIPathsBody<
T extends { [k: string]: any } = { [k: string]: any }
> = APIPathsQuery & {
data?: T;
};
export type APIPathsQuery<
T extends { [k: string]: any } = { [k: string]: any }
> = {
searchQuery?: DsqlCrudQueryObject<T>;
crudParams?: Pick<
DsqlCrudParam<T>,
| "count"
| "countOnly"
| "targetId"
| "targetField"
| "targetValue"
| "tableSchema"
>;
};
export type APIPathsParamsGetMiddleware<
T extends { [k: string]: any } = { [k: string]: any }
> = (params: { query: APIPathsQuery<T> }) => Promise<DsqlCrudQueryObject<T>>;
export type APIPathsParamsCrudMiddleware<
T extends { [k: string]: any } = { [k: string]: any }
> = (params: {
body: APIPathsBody<T>;
query: DsqlCrudQueryObject<T>;
}) => Promise<APIPathsBody<T>>;
export type APIPathsParamsAllowedTable = {
table: string;
allowedFields?: (string | RegExp)[];
disallowedFields?: (string | RegExp)[];
};
export type APIPathsCrudParams<
T extends { [k: string]: any } = { [k: string]: any }
> = APIPathsParams<T> & {
isAuthorized?: boolean;
table: string;
targetId?: string;
allowedTable?: APIPathsParamsAllowedTable;
url?: URL;
};
export type APIPathsData<
T extends { [k: string]: any } = { [k: string]: any }
> = {
table: string;
targetId?: string;
query?: APIPathsQuery<T>;
url: URL;
};
export type ClientCrudFetchParams<
T extends { [k: string]: any } = { [k: string]: any },
P = string
> = {
table: P;
method?: "GET" | "POST" | "PUT" | "DELETE";
query?: APIPathsQuery<T>;
body?: APIPathsBody<T>;
basePath?: string;
targetId?: string | number | null;
apiOrigin?: string;
};
+11 -5
View File
@@ -107,11 +107,17 @@ export default async function <
const parsedRes = checkArrayDepth(res, 2)
? parseDbResults({ unparsedResults: res[0], tableSchema })
: res[0];
const parsedBatchRes = checkArrayDepth(res, 3)
? res.map((_r: any[][]) => {
return parseDbResults({ unparsedResults: _r[0], tableSchema });
})
: res;
const parsedBatchRes =
connQueries.length > 1
? checkArrayDepth(res, 3)
? res.map((_r: any[][]) => {
return parseDbResults({
unparsedResults: _r[0],
tableSchema,
});
})
: res
: undefined;
const isSuccess = Array.isArray(res) && Array.isArray(res[0]);
+59 -24
View File
@@ -9,6 +9,8 @@ import dsqlCrudGet from "./crud-get";
import connDbHandler from "../db/conn-db-handler";
import addDbEntry from "../../functions/backend/db/addDbEntry";
import updateDbEntry from "../../functions/backend/db/updateDbEntry";
import sqlGenerator from "../../functions/dsql/sql/sql-generator";
import deleteDbEntry from "../../functions/backend/db/deleteDbEntry";
export default async function dsqlCrud<
T extends { [key: string]: any } = { [key: string]: any },
@@ -30,6 +32,7 @@ export default async function dsqlCrud<
tableSchema,
deleteKeyValuesOperator,
dbConfig,
query,
} = params;
const finalData = (sanitize ? sanitize({ data }) : data) as T;
@@ -37,6 +40,25 @@ export default async function dsqlCrud<
sanitize ? sanitize({ batchData }) : batchData
) as T[];
const queryObject = query
? sqlGenerator({
tableName: table,
genObject: query,
dbFullName,
})
: undefined;
const whereClause = queryObject?.string.replace(/^.*?( WHERE )/, "$1");
const whereClauseObject = whereClause
? {
clause: whereClause!,
params: queryObject?.values
.filter((v) => typeof v == "string" || typeof v == "number")
.map((v) => String(v)),
}
: undefined;
switch (action) {
case "get":
return await dsqlCrudGet<T, K>(params);
@@ -68,37 +90,50 @@ export default async function dsqlCrud<
debug,
tableSchema,
dbConfig,
whereClauseObject,
});
return UPDATE_RESULT;
case "delete":
const deleteQuery = sqlDeleteGenerator({
data: targetId
? { id: targetId }
: targetField && targetValue
? { [targetField]: targetValue }
: deleteData,
tableName: table,
dbFullName,
deleteKeyValues,
deleteKeyValuesOperator,
});
let res: PostInsertReturn;
const res = (await connDbHandler({
query: deleteQuery?.query,
values: deleteQuery?.values,
dsqlConnOpts: { config: dbConfig },
})) as PostInsertReturn;
if (whereClauseObject) {
const DELETE_RES = await deleteDbEntry({
whereClauseObject,
tableName: table,
dbFullName,
});
return {
success: Boolean(res.affectedRows),
payload: res,
queryObject: {
sql: format(deleteQuery?.query || ""),
params: deleteQuery?.values || [],
},
};
return DELETE_RES;
} else {
const deleteQuery = sqlDeleteGenerator({
data: targetId
? { id: targetId }
: targetField && targetValue
? { [targetField]: targetValue }
: deleteData,
tableName: table,
dbFullName,
deleteKeyValues,
deleteKeyValuesOperator,
});
res = (await connDbHandler({
query: deleteQuery?.query,
values: deleteQuery?.values,
dsqlConnOpts: { config: dbConfig },
})) as PostInsertReturn;
return {
success: Boolean(res.affectedRows),
payload: res,
queryObject: {
sql: format(deleteQuery?.query || ""),
params: deleteQuery?.values || [],
},
};
}
default:
return {