Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
"*.{js,cjs,mjs,json,md,yml,yaml}": "prettier --write"
},
"keywords": [],
"author": "",
"author": "weare",
"license": "MIT",
"dependencies": {
"@apollo/client": "^4.2.1",
Expand Down
7 changes: 7 additions & 0 deletions src/queue/rabbitmq.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const ROUTING_KEYS = {

export const QUEUES = {
TRANSACTION_PROCESSING: "transaction-processing-queue",
TRANSACTION_REPROCESSING: "transaction-reprocessing-queue",
};

class RabbitMQManager {
Expand All @@ -37,6 +38,12 @@ class RabbitMQManager {
EXCHANGES.TRANSACTIONS,
ROUTING_KEYS.TRANSACTION_PROCESS
),
channel.assertQueue(QUEUES.TRANSACTION_REPROCESSING, { durable: true }),
channel.bindQueue(
QUEUES.TRANSACTION_REPROCESSING,
EXCHANGES.TRANSACTIONS,
ROUTING_KEYS.TRANSACTION_PROCESS
),
]);
},
});
Expand Down
44 changes: 44 additions & 0 deletions src/queue/reprocessingQueue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { rabbitMQManager, EXCHANGES, ROUTING_KEYS, QUEUES } from "./rabbitmq";
import { reprocessingService, ReprocessingJob } from "../services/reprocessingService";
import logger from "../utils/logger";

export const REPROCESSING_QUEUE_NAME = "transaction-reprocessing-queue";

export async function startReprocessingWorker(): Promise<void> {
await rabbitMQManager.consume<ReprocessingJob>(
REPROCESSING_QUEUE_NAME,
async (job) => {
try {
logger.info({ jobId: job.id, transactionId: job.transactionId }, "[reprocessing] Processing job");
const result = await reprocessingService.processJob(job);
logger.info({ jobId: job.id, success: result.success }, "[reprocessing] Job processed");
} catch (error) {
logger.error({ error, jobId: job.id }, "[reprocessing] Worker failed to process job");
}
},
3,
);
}

export async function scheduleReprocessingPoller(intervalMs = 30000): Promise<void> {
const poll = async () => {
try {
const pendingJobs = await reprocessingService.getPendingJobs(50);
for (const job of pendingJobs) {
await rabbitMQManager.publish(EXCHANGES.TRANSACTIONS, ROUTING_KEYS.TRANSACTION_PROCESS, {
type: "reprocessing",
jobId: job.id,
transactionId: job.transactionId,
provider: job.provider,
attemptNumber: job.attemptNumber,
scheduledAt: job.scheduledAt,
});
}
} catch (error) {
logger.error({ error }, "[reprocessing] Poller failed");
}
};

await poll();
setInterval(poll, intervalMs);
}
193 changes: 193 additions & 0 deletions src/routes/admin/assets.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { Router } from "express";
import { AssetWizardController } from "../../controllers/admin/assetWizardController";
import { assetWorkflowService } from "../../services/assetWorkflowService";
import { requireAdmin, logAdminAction } from "../admin";
import { createError, ERROR_CODES } from "../../middleware/errorHandler";

const router = Router();
const controller = new AssetWizardController();

router.use(requireAdmin);
router.use(logAdminAction("ASSET_ADMIN"));

/**
* @openapi
* /api/admin/assets:
Expand Down Expand Up @@ -40,4 +46,191 @@ router.get("/", controller.listAssets);
*/
router.post("/issue", controller.issueAsset);

/**
* @openapi
* /api/admin/assets/workflow/requests:
* post:
* summary: Create asset issuance request
* tags: [Admin, Assets]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required: [assetCode, name, limit, requestedBy]
* properties:
* assetCode: { type: string }
* name: { type: string }
* description: { type: string }
* limit: { type: string }
* requestedBy: { type: string }
* responses:
* 201:
* description: Request created
*/
router.post("/workflow/requests", async (req, res) => {
try {
const { assetCode, name, description, limit, requestedBy, trustlineConfig } = req.body;
const request = await assetWorkflowService.createRequest({ assetCode, name, description, limit, requestedBy, trustlineConfig });
res.status(201).json({ success: true, data: request });
} catch (error) {
throw createError(ERROR_CODES.INVALID_INPUT, error instanceof Error ? error.message : "Failed to create request");
}
});

/**
* @openapi
* /api/admin/assets/workflow/requests:
* get:
* summary: List asset issuance requests
* tags: [Admin, Assets]
* responses:
* 200:
* description: List of requests
*/
router.get("/workflow/requests", async (req, res) => {
try {
const { status } = req.query;
const requests = await assetWorkflowService["requestModel"].findAll(status as any);
res.json({ success: true, data: requests });
} catch (error) {
throw createError(ERROR_CODES.INTERNAL_ERROR, "Failed to fetch requests");
}
});

/**
* @openapi
* /api/admin/assets/workflow/requests/{id}/submit:
* post:
* summary: Submit request for approval
* tags: [Admin, Assets]
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Request submitted
*/
router.post("/workflow/requests/:id/submit", async (req, res) => {
try {
const request = await assetWorkflowService.submitForApproval(req.params.id);
res.json({ success: true, data: request });
} catch (error) {
throw createError(ERROR_CODES.INVALID_INPUT, error instanceof Error ? error.message : "Failed to submit request");
}
});

/**
* @openapi
* /api/admin/assets/workflow/requests/{id}/approve:
* post:
* summary: Approve or reject request
* tags: [Admin, Assets]
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required: [action, approverId]
* properties:
* action:
* type: string
* enum: [approve, reject, request_changes]
* approverId:
* type: string
* notes:
* type: string
* responses:
* 200:
* description: Request updated
*/
router.post("/workflow/requests/:id/approve", async (req, res) => {
try {
const { action, approverId, notes } = req.body;
const request = await assetWorkflowService.approveRequest(req.params.id, approverId, action, notes);
res.json({ success: true, data: request });
} catch (error) {
throw createError(ERROR_CODES.INVALID_INPUT, error instanceof Error ? error.message : "Failed to process approval");
}
});

/**
* @openapi
* /api/admin/assets/workflow/requests/{id}/trustline:
* post:
* summary: Configure trustline for asset
* tags: [Admin, Assets]
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required: [destinationAccount, limit]
* properties:
* destinationAccount:
* type: string
* limit:
* type: string
* autoSetup:
* type: boolean
* responses:
* 200:
* description: Trustline configured
*/
router.post("/workflow/requests/:id/trustline", async (req, res) => {
try {
const { destinationAccount, limit, autoSetup } = req.body;
const request = await assetWorkflowService.configureTrustline(req.params.id, { destinationAccount, limit, autoSetup });
res.json({ success: true, data: request });
} catch (error) {
throw createError(ERROR_CODES.INVALID_INPUT, error instanceof Error ? error.message : "Failed to configure trustline");
}
});

/**
* @openapi
* /api/admin/assets/workflow/validate:
* post:
* summary: Validate asset configuration
* tags: [Admin, Assets]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required: [assetCode, name, limit]
* responses:
* 200:
* description: Validation result
*/
router.post("/workflow/validate", async (req, res) => {
try {
const { assetCode, name, limit } = req.body;
const validation = assetWorkflowService.validateConfiguration({ assetCode, name, limit });
res.json({ success: true, data: validation });
} catch (error) {
throw createError(ERROR_CODES.INVALID_INPUT, "Validation failed");
}
});

export default router;

Loading
Loading