Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/config/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ const sequelize = new Sequelize(
},
);

import '../models/relations';
export { sequelize };
4 changes: 2 additions & 2 deletions src/controllers/ddl-controller.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Request, Response } from 'express';
import { sequelize } from '../config/database';
import { successResponse, errorResponse } from '../utils/response';
import { Operations } from '../types/ddl';
import { DDLOperations } from '../types/ddl';
import { DDLExecutor } from '../operations/migrate';

export const migrate = async (req: Request, res: Response) => {
const { operations }: { operations: Operations[] } = req.body;
const { operations }: { operations: DDLOperations[] } = req.body;
if (!operations || !Array.isArray(operations))
return errorResponse(res, 'Invalid payload structure', 400);

Expand Down
26 changes: 26 additions & 0 deletions src/controllers/dml-controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Request, Response } from 'express';
import { sequelize } from '../config/database';
import { successResponse, errorResponse } from '../utils/response';
import { DMLOperations } from '../types/dml';
import { DMLExecutor } from '../operations/execute';

export const execute = async (req: Request, res: Response) => {
const { operations }: { operations: DMLOperations[] } = req.body;
if (!operations || !Array.isArray(operations))
return errorResponse(res, 'Invalid payload structure', 400);

const transaction = await sequelize.transaction();

try {
const result = await DMLExecutor.execute(operations, transaction);
await transaction.commit();
return successResponse(
res,
result,
'DML operations completed successfully',
);
} catch (error: any) {
await transaction.rollback();
return errorResponse(res, error.message, 500);
}
};
14 changes: 14 additions & 0 deletions src/controllers/schema-controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Request, Response } from 'express';
import SchemaRepository from '../repositories/schema-repository';

export const schema = async (req: Request, res: Response) => {
try {
const tables = await SchemaRepository.getSchemas();

return res.json({ success: true, tables });
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : 'Unknown error';
res.status(500).json({ error: errorMessage });
}
};
6 changes: 5 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import userRoutes from './routes/user-routes';
import authRoutes from './routes/auth-routes';
import ddlRoutes from './routes/ddl-routes';
import { sequelize } from './config/database';
import schemaRoutes from './routes/schema-routes';
import dmlRoutes from './routes/dml-routes';

sequelize
.sync({ force: true })
.sync({ alter: true })
.then(async () => {
console.log('Database synchronized successfully.');
})
Expand All @@ -26,6 +28,8 @@ const apiRouter = express.Router();
apiRouter.use('/auth', authRoutes);
apiRouter.use('/users', userRoutes);
apiRouter.use('/migrate', ddlRoutes);
apiRouter.use('/schemas', schemaRoutes);
apiRouter.use('/execute', dmlRoutes);

app.use('/api', apiRouter);

Expand Down
5 changes: 0 additions & 5 deletions src/models/metadata-column.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,4 @@ MetadataColumn.init(
},
);

MetadataColumn.belongsTo(MetadataTable, {
foreignKey: 'table_id',
onDelete: 'CASCADE',
});
Comment thread
rifasania marked this conversation as resolved.

export default MetadataColumn;
1 change: 0 additions & 1 deletion src/models/metadata-table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ class MetadataTable extends Model {
public table_name!: string;
public readonly createdAt!: Date;
public readonly updatedAt!: Date;
primaryKey: any;
}

MetadataTable.init(
Expand Down
14 changes: 14 additions & 0 deletions src/models/relations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import MetadataTable from './metadata-table';
import MetadataColumn from './metadata-column';

MetadataTable.hasMany(MetadataColumn, {
foreignKey: 'table_id',
as: 'columns',
onDelete: 'CASCADE',
});

MetadataColumn.belongsTo(MetadataTable, {
foreignKey: 'table_id',
as: 'table',
onDelete: 'CASCADE',
});
1 change: 1 addition & 0 deletions src/operations/ddl/create-column.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export class CreateColumn {
ALTER TABLE "${table}" ADD COLUMN "${columnName}" ${colType}
${columnDefinition.nullable ? '' : 'NOT NULL'}
${columnDefinition.unique ? 'UNIQUE' : ''}
${colType === 'timestamp' ? 'DEFAULT NOW()' : ''}
`;

await sequelize.query(addColumnQuery, { transaction });
Expand Down
48 changes: 48 additions & 0 deletions src/operations/dml/delete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Transaction } from 'sequelize';
import { DeleteInstruction } from '../../types/dml';
import { DMLRepository } from '../../repositories/dml-repository';
import MetadataTableRepository from '../../repositories/metadata-table-repository';
import MetadataColumnRepository from '../../repositories/metadata-column-repository';
import {
parseAndValidateCondition,
validIdentifier,
} from '../../utils/validation';

export class DeleteOperation {
static async execute(
instruction: DeleteInstruction,
transaction: Transaction,
) {
const { table, condition, params } = instruction;

if (!validIdentifier(table))
throw new Error(`Invalid table name: ${table}`);

const metadataTable = await MetadataTableRepository.findOne(
{ table_name: table },
transaction,
);
if (!metadataTable) throw new Error(`Table ${table} does not exist`);

const metadataColumns = await MetadataColumnRepository.findAll(
{ table_id: metadataTable.id },
transaction,
);

const parsedCondition = condition
? parseAndValidateCondition(condition, metadataColumns)
: {};
const result = await DMLRepository.delete(
table,
parsedCondition,
params,
transaction,
);

await transaction.afterCommit(() => {
console.log(`Data deleted from ${table} successfully`);
});

return result;
}
}
4 changes: 4 additions & 0 deletions src/operations/dml/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export { SelectOperation } from './select';
export { InsertOperation } from './insert';
export { UpdateOperation } from './update';
export { DeleteOperation } from './delete';
30 changes: 30 additions & 0 deletions src/operations/dml/insert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Transaction } from 'sequelize';
import { InsertInstruction } from '../../types/dml';
import { DMLRepository } from '../../repositories/dml-repository';
import { parseAndValidateData, validIdentifier } from '../../utils/validation';

export class InsertOperation {
static async execute(
instruction: InsertInstruction,
transaction: Transaction,
) {
const { table, data } = instruction;

if (!validIdentifier(table)) {
throw new Error(`Invalid table name: ${table}`);
}

if (!data || Object.keys(data).length === 0) {
throw new Error('Insert data cannot be empty');
}

const parsedData = await parseAndValidateData(table, data, transaction);
const result = await DMLRepository.insert(table, parsedData, transaction);

await transaction.afterCommit(() => {
console.log(`Data inserted into ${table} successfully`);
});

return result[0][0].id;
}
}
43 changes: 43 additions & 0 deletions src/operations/dml/select.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Transaction } from 'sequelize';
import { SelectInstruction } from '../../types/dml';
import { DMLRepository } from '../../repositories/dml-repository';
import MetadataTableRepository from '../../repositories/metadata-table-repository';
import {
parseAndValidateCondition,
validIdentifier,
} from '../../utils/validation';

export class SelectOperation {
static async execute(
instruction: SelectInstruction,
transaction: Transaction,
) {
const { table, condition, orderBy, limit, offset, params } = instruction;

if (!validIdentifier(table))
throw new Error(`Invalid table name: ${table}`);

const metadataTable = await MetadataTableRepository.findOne(
{ table_name: table },
transaction,
);
if (!metadataTable) {
throw new Error(`Table ${table} does not exist`);
}

const parsedCondition = condition
? parseAndValidateCondition(condition)
: {};
const result = await DMLRepository.select(
table,
parsedCondition,
orderBy,
limit,
offset,
params,
transaction,
);

return result;
}
}
42 changes: 42 additions & 0 deletions src/operations/dml/update.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Transaction } from 'sequelize';
import { UpdateInstruction } from '../../types/dml';
import { DMLRepository } from '../../repositories/dml-repository';
import {
parseAndValidateCondition,
parseAndValidateData,
validIdentifier,
} from '../../utils/validation';

export class UpdateOperation {
static async execute(
instruction: UpdateInstruction,
transaction: Transaction,
) {
const { table, condition, set, params } = instruction;

if (!validIdentifier(table))
throw new Error(`Invalid table name: ${table}`);

if (!set || Object.keys(set).length === 0)
throw new Error('Update set cannot be empty');

const parsedSet = await parseAndValidateData(table, set, transaction);
const parsedCondition = condition
? parseAndValidateCondition(condition)
: {};

const result = await DMLRepository.update(
table,
parsedSet,
parsedCondition,
params,
transaction,
);

await transaction.afterCommit(() => {
console.log(`Data updated in ${table} successfully`);
});

return result;
}
}
58 changes: 58 additions & 0 deletions src/operations/execute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Transaction } from 'sequelize';
import { DMLOperations } from '../types/dml';
import {
SelectOperation,
InsertOperation,
UpdateOperation,
DeleteOperation,
} from '../operations/dml';

export class DMLExecutor {
static async execute(operations: DMLOperations[], transaction: Transaction) {
const results: Record<string, any>[] = [];

for (const { operation, instruction } of operations) {
switch (operation) {
case 'Select': {
const selectResult = await SelectOperation.execute(
instruction,
transaction,
);
results.push(selectResult);
break;
}
case 'Insert': {
const insertResult = await InsertOperation.execute(
instruction,
transaction,
);
results.push(insertResult);
break;
}
case 'Update': {
const updateResult = await UpdateOperation.execute(
instruction,
transaction,
);
if (updateResult) {
results.push(updateResult);
}
break;
}
case 'Delete': {
const deleteResult = await DeleteOperation.execute(
instruction,
transaction,
);
results.push(deleteResult);
break;
}
default: {
throw new Error(`Unsupported operation: ${operation}`);
}
}
}

return results;
}
}
8 changes: 2 additions & 6 deletions src/operations/migrate.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Transaction } from 'sequelize';
import { ColumnObject, Operations } from '../types/ddl';
import { ColumnObject, DDLOperations } from '../types/ddl';
import {
CreateTable,
CreateColumn,
Expand All @@ -10,16 +10,12 @@ import {
} from '../operations/ddl';

export class DDLExecutor {
static async execute(operations: Operations[], transaction: Transaction) {
static async execute(operations: DDLOperations[], transaction: Transaction) {
for (const { operation, resource, migration } of operations) {
const { name, table, column, from, to } = migration;

let columnName: string;
let finalColumnDefinition = {};
// let columnName = typeof column === 'string' ? column : undefined;
// if (column && typeof column === 'object' && 'definition' in column) {
// finalColumnDefinition = (column as any).definition;
// }

if (typeof column === 'string') {
columnName = column;
Expand Down
Loading