This commit is contained in:
Sewmina 2025-07-14 23:36:22 +05:30
commit 6c644fd434
9 changed files with 3096 additions and 0 deletions

42
.gitignore vendored Normal file
View File

@ -0,0 +1,42 @@
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Build output
dist/
build/
# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# IDE files
.vscode/
.idea/
*.swp
*.swo
# OS files
.DS_Store
Thumbs.db
# Logs
logs/
*.log
# Runtime data
pids/
*.pid
*.seed
*.pid.lock
# Coverage directory used by tools like istanbul
coverage/
# TypeScript cache
*.tsbuildinfo

159
README.md Normal file
View File

@ -0,0 +1,159 @@
# DMRocket API
A Node.js/Express API for managing orders with MySQL database integration.
## Setup
### 1. Install Dependencies
```bash
npm install
```
### 2. Database Setup
Create your MySQL database and table:
```sql
CREATE DATABASE dmrocket;
USE dmrocket;
CREATE TABLE `Orders` (
`id` INT NOT NULL AUTO_INCREMENT,
`wallet` TEXT NOT NULL,
`mint` TEXT NOT NULL,
`amount` BIGINT NOT NULL DEFAULT '100',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`user_id` TEXT NOT NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB;
```
### 3. Environment Configuration
Create a `.env.local` file in the root directory with your database credentials:
```env
# Database Configuration
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=your_password_here
DB_NAME=dmrocket
DB_PORT=3306
# Server Configuration
PORT=7111
NODE_ENV=development
```
### 4. Run the Application
```bash
# Development mode
npm run dev
# Production build
npm run build
npm start
```
## API Endpoints
All endpoints use GET requests for simplicity and ease of use.
### Orders API
#### Create Order
- **GET** `/api/v1/create_order`
- **Query Parameters:**
- `wallet` (required) - Wallet address
- `mint` (required) - Mint address
- `amount` (optional) - Order amount (default: 100)
- `user_id` (required) - User identifier
**Example:**
```
GET /api/v1/create_order?wallet=wallet_address&mint=mint_address&amount=200&user_id=user123
```
#### Get All Orders
- **GET** `/api/v1/get_all_orders`
**Example:**
```
GET /api/v1/get_all_orders
```
#### Get Order by ID
- **GET** `/api/v1/get_order_by_id/:id`
**Example:**
```
GET /api/v1/get_order_by_id/1
```
#### Get Orders by User ID
- **GET** `/api/v1/get_orders_by_user/:userId`
**Example:**
```
GET /api/v1/get_orders_by_user/user123
```
#### Update Order
- **GET** `/api/v1/update_order/:id`
- **Query Parameters:** (any combination of fields)
- `wallet` (optional) - New wallet address
- `mint` (optional) - New mint address
- `amount` (optional) - New amount
- `user_id` (optional) - New user ID
**Example:**
```
GET /api/v1/update_order/1?amount=300&user_id=newuser456
```
#### Delete Order
- **GET** `/api/v1/delete_order/:id`
**Example:**
```
GET /api/v1/delete_order/1
```
### Health Check
- **GET** `/health` - Returns API status and database connection status
## Response Format
All API responses follow this format:
```json
{
"success": true,
"data": {...},
"count": 1
}
```
Error responses:
```json
{
"error": "Error message",
"message": "Detailed error description"
}
```
## Database Schema
| Field | Type | Description |
|-------|------|-------------|
| id | INT | Auto-increment primary key |
| wallet | TEXT | Wallet address |
| mint | TEXT | Mint address |
| amount | BIGINT | Order amount (default: 100) |
| created_at | DATETIME | Timestamp (auto-generated) |
| user_id | TEXT | User identifier |
## Development
- **TypeScript** for type safety
- **Express.js** for the web framework
- **MySQL2** for database operations
- **Dotenv** for environment variable management

2427
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

30
package.json Normal file
View File

@ -0,0 +1,30 @@
{
"name": "dmrocket_api",
"version": "1.0.0",
"main": "dist/app.js",
"scripts": {
"build": "tsc",
"start": "node dist/app.js",
"dev": "ts-node src/app.ts",
"dev:watch": "nodemon --exec ts-node src/app.ts",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"devDependencies": {
"@types/express": "^4.17.1",
"@types/node": "^20.0.0",
"eslint": "^9.31.0",
"nodemon": "^3.0.0",
"ts-node": "^10.9.0",
"typescript": "^5.8.3"
},
"dependencies": {
"@types/mysql": "^2.15.27",
"dotenv": "^17.2.0",
"express": "^4.17.1",
"mysql2": "^3.14.2"
}
}

61
src/app.ts Normal file
View File

@ -0,0 +1,61 @@
import express, { Request, Response } from 'express';
import dotenv from 'dotenv';
import { testConnection } from './config/database';
import orderRoutes from './routes/orderRoutes';
// Load environment variables
dotenv.config({ path: '.env.local' });
const app = express();
const port: number = 7111;
// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Test database connection on startup
testConnection().then((connected) => {
if (!connected) {
console.error('Failed to connect to database. Please check your .env.local configuration.');
}
});
// Routes
app.get('/', (req: Request, res: Response) => {
res.send('DMRocket API - Unauthorized');
});
// API Routes
app.use('/api/v1', orderRoutes);
// Health check endpoint
app.get('/health', (req: Request, res: Response) => {
res.json({
status: 'OK',
timestamp: new Date().toISOString(),
database: 'Connected'
});
});
// Error handling middleware
app.use((err: any, req: Request, res: Response, next: any) => {
console.error('Error:', err);
res.status(500).json({
error: 'Internal server error',
message: err.message || 'Something went wrong'
});
});
// 404 handler
app.use('*', (req: Request, res: Response) => {
res.status(404).json({
error: 'Not found',
message: 'The requested endpoint does not exist'
});
});
// Start server
app.listen(port, () => {
console.log(`DMRocket API is listening at http://localhost:${port}`);
console.log(`Health check available at http://localhost:${port}/health`);
});

35
src/config/database.ts Normal file
View File

@ -0,0 +1,35 @@
import mysql from 'mysql2/promise';
import dotenv from 'dotenv';
// Load environment variables
dotenv.config({ path: '.env.local' });
// Database configuration
const dbConfig = {
host: process.env.DB_HOST || 'localhost',
user: process.env.DB_USER || 'root',
password: process.env.DB_PASSWORD || '',
database: process.env.DB_NAME || 'dmrocket',
port: parseInt(process.env.DB_PORT || '3306'),
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
};
// Create connection pool
const pool = mysql.createPool(dbConfig);
// Test database connection
export const testConnection = async () => {
try {
const connection = await pool.getConnection();
console.log('Database connected successfully');
connection.release();
return true;
} catch (error) {
console.error('Database connection failed:', error);
return false;
}
};
export default pool;

196
src/routes/orderRoutes.ts Normal file
View File

@ -0,0 +1,196 @@
import { Router, Request, Response } from 'express';
import { OrderService, Order } from '../services/orderService';
const router = Router();
// Create a new order (GET with query parameters)
router.get('/create_order', async (req: Request, res: Response) => {
try {
const { wallet, mint, amount, user_id } = req.query;
// Validate required fields
if (!wallet || !mint || !user_id) {
return res.status(400).json({
error: 'Missing required fields: wallet, mint, and user_id are required'
});
}
const orderData: Order = {
wallet: wallet as string,
mint: mint as string,
amount: amount ? parseInt(amount as string) : 100, // Default to 100 if not provided
user_id: user_id as string
};
const newOrder = await OrderService.createOrder(orderData);
return res.status(201).json({
success: true,
data: newOrder
});
} catch (error) {
console.error('Error creating order:', error);
return res.status(500).json({
error: 'Internal server error',
message: 'Failed to create order'
});
}
});
// Get all orders
router.get('/get_all_orders', async (req: Request, res: Response) => {
try {
const orders = await OrderService.getAllOrders();
return res.json({
success: true,
data: orders,
count: orders.length
});
} catch (error) {
console.error('Error fetching orders:', error);
return res.status(500).json({
error: 'Internal server error',
message: 'Failed to fetch orders'
});
}
});
// Get order by ID
router.get('/get_order_by_id/:id', async (req: Request, res: Response) => {
try {
const id = parseInt(req.params.id as string);
if (isNaN(id)) {
return res.status(400).json({
error: 'Invalid order ID'
});
}
const order = await OrderService.getOrderById(id);
if (!order) {
return res.status(404).json({
error: 'Order not found'
});
}
return res.json({
success: true,
data: order
});
} catch (error) {
console.error('Error fetching order:', error);
return res.status(500).json({
error: 'Internal server error',
message: 'Failed to fetch order'
});
}
});
// Get orders by user ID
router.get('/get_orders_by_user/:userId', async (req: Request, res: Response) => {
try {
const { userId } = req.params;
if (!userId) {
return res.status(400).json({
error: 'User ID is required'
});
}
const orders = await OrderService.getOrdersByUserId(userId as string);
return res.json({
success: true,
data: orders,
count: orders.length
});
} catch (error) {
console.error('Error fetching user orders:', error);
return res.status(500).json({
error: 'Internal server error',
message: 'Failed to fetch user orders'
});
}
});
// Update order (GET with query parameters)
router.get('/update_order/:id', async (req: Request, res: Response) => {
try {
const id = parseInt(req.params.id as string);
if (isNaN(id)) {
return res.status(400).json({
error: 'Invalid order ID'
});
}
const { wallet, mint, amount, user_id } = req.query;
const updates: Partial<Order> = {};
if (wallet !== undefined) updates.wallet = wallet as string;
if (mint !== undefined) updates.mint = mint as string;
if (amount !== undefined) updates.amount = parseInt(amount as string);
if (user_id !== undefined) updates.user_id = user_id as string;
if (Object.keys(updates).length === 0) {
return res.status(400).json({
error: 'No valid fields to update'
});
}
const success = await OrderService.updateOrder(id, updates);
if (!success) {
return res.status(404).json({
error: 'Order not found'
});
}
return res.json({
success: true,
message: 'Order updated successfully'
});
} catch (error) {
console.error('Error updating order:', error);
return res.status(500).json({
error: 'Internal server error',
message: 'Failed to update order'
});
}
});
// Delete order
router.get('/delete_order/:id', async (req: Request, res: Response) => {
try {
const id = parseInt(req.params.id as string);
if (isNaN(id)) {
return res.status(400).json({
error: 'Invalid order ID'
});
}
const success = await OrderService.deleteOrder(id);
if (!success) {
return res.status(404).json({
error: 'Order not found'
});
}
return res.json({
success: true,
message: 'Order deleted successfully'
});
} catch (error) {
console.error('Error deleting order:', error);
return res.status(500).json({
error: 'Internal server error',
message: 'Failed to delete order'
});
}
});
export default router;

View File

@ -0,0 +1,115 @@
import pool from '../config/database';
export interface Order {
id?: number;
wallet: string;
mint: string;
amount: number;
created_at?: Date;
user_id: string;
}
export class OrderService {
// Create a new order
static async createOrder(order: Order): Promise<Order> {
try {
const [result] = await pool.execute(
'INSERT INTO Orders (wallet, mint, amount, user_id) VALUES (?, ?, ?, ?)',
[order.wallet, order.mint, order.amount, order.user_id]
);
const insertResult = result as any;
const newOrder: Order = {
id: insertResult.insertId,
wallet: order.wallet,
mint: order.mint,
amount: order.amount,
user_id: order.user_id,
created_at: new Date()
};
return newOrder;
} catch (error) {
console.error('Error creating order:', error);
throw error;
}
}
// Get all orders
static async getAllOrders(): Promise<Order[]> {
try {
const [rows] = await pool.execute('SELECT * FROM Orders ORDER BY created_at DESC');
return rows as Order[];
} catch (error) {
console.error('Error fetching orders:', error);
throw error;
}
}
// Get order by ID
static async getOrderById(id: number): Promise<Order | null> {
try {
const [rows] = await pool.execute('SELECT * FROM Orders WHERE id = ?', [id]);
const orders = rows as Order[];
return orders.length > 0 ? orders[0] || null : null;
} catch (error) {
console.error('Error fetching order by ID:', error);
throw error;
}
}
// Get orders by user_id
static async getOrdersByUserId(userId: string): Promise<Order[]> {
try {
const [rows] = await pool.execute('SELECT * FROM Orders WHERE user_id = ? ORDER BY created_at DESC', [userId]);
return rows as Order[];
} catch (error) {
console.error('Error fetching orders by user ID:', error);
throw error;
}
}
// Update order
static async updateOrder(id: number, updates: Partial<Order>): Promise<boolean> {
try {
const allowedFields = ['wallet', 'mint', 'amount', 'user_id'];
const updateFields: string[] = [];
const updateValues: any[] = [];
for (const [key, value] of Object.entries(updates)) {
if (allowedFields.includes(key) && value !== undefined) {
updateFields.push(`${key} = ?`);
updateValues.push(value);
}
}
if (updateFields.length === 0) {
return false;
}
updateValues.push(id);
const [result] = await pool.execute(
`UPDATE Orders SET ${updateFields.join(', ')} WHERE id = ?`,
updateValues
);
const updateResult = result as any;
return updateResult.affectedRows > 0;
} catch (error) {
console.error('Error updating order:', error);
throw error;
}
}
// Delete order
static async deleteOrder(id: number): Promise<boolean> {
try {
const [result] = await pool.execute('DELETE FROM Orders WHERE id = ?', [id]);
const deleteResult = result as any;
return deleteResult.affectedRows > 0;
} catch (error) {
console.error('Error deleting order:', error);
throw error;
}
}
}

31
tsconfig.json Normal file
View File

@ -0,0 +1,31 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"removeComments": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"dist"
]
}