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 backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"discord.js": "^14.14.1",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"express-rate-limit": "^7.1.5",
"express-rate-limit": "^7.5.1",
"graphql": "^16.8.1",
"graphql-subscriptions": "^2.0.0",
"graphql-ws": "^5.14.3",
Expand Down
159 changes: 38 additions & 121 deletions backend/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ const express = require('express');
const cors = require('cors');
const dotenv = require('dotenv');
const http = require('http');
const { rateLimit } = require('express-rate-limit');

// Import swagger documentation
const swaggerUi = require('swagger-ui-express');
const swaggerSpecs = require('./swagger/options');

Expand All @@ -12,17 +12,24 @@ dotenv.config();
const app = express();
const PORT = process.env.PORT || 3000;

// Create HTTP server for GraphQL subscriptions
const httpServer = http.createServer(app);

// Middleware
app.use(cors());
app.use(express.json());

// Swagger UI middleware
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpecs));

// Database connection and models
const claimRateLimiter = rateLimit({
windowMs: 60 * 1000,
limit: 1,
message: {
success: false,
error: 'Too many claim requests. Please wait 1 minute before trying again.'
},
standardHeaders: 'draft-7',
legacyHeaders: false,
});

const { sequelize } = require('./database/connection');
const models = require('./models');
const { OrganizationWebhook } = models;
Expand All @@ -47,7 +54,6 @@ app.post('/api/admin/webhooks', async (req, res) => {
}
});

// Services
const indexingService = require('./services/indexingService');
const adminService = require('./services/adminService');
const vestingService = require('./services/vestingService');
Expand All @@ -57,7 +63,6 @@ const tvlService = require('./services/tvlService');
const vaultExportService = require('./services/vaultExportService');
const notificationService = require('./services/notificationService');

// Routes
app.get('/', (req, res) => {
res.json({ message: 'Vesting Vault API is running!' });
});
Expand All @@ -66,82 +71,60 @@ app.get('/health', (req, res) => {
res.json({ status: 'OK', timestamp: new Date().toISOString() });
});

// API Routes for claims and indexing
app.post('/api/claims', async (req, res) => {
app.post('/api/claims', claimRateLimiter, async (req, res) => {
try {
const claim = await indexingService.processClaim(req.body);
res.status(201).json({ success: true, data: claim });
} catch (error) {
console.error('Error processing claim:', error);
res.status(500).json({
success: false,
error: error.message
});
res.status(500).json({ success: false, error: error.message });
}
});

app.post('/api/claims/batch', async (req, res) => {
app.post('/api/claims/batch', claimRateLimiter, async (req, res) => {
try {
const result = await indexingService.processBatchClaims(req.body.claims);
res.json({ success: true, data: result });
} catch (error) {
console.error('Error processing batch claims:', error);
res.status(500).json({
success: false,
error: error.message
});
res.status(500).json({ success: false, error: error.message });
}
});

app.post('/api/claims/backfill-prices', async (req, res) => {
app.post('/api/claims/backfill-prices', claimRateLimiter, async (req, res) => {
try {
const processedCount = await indexingService.backfillMissingPrices();
res.json({
success: true,
message: `Backfilled prices for ${processedCount} claims`
});
res.json({ success: true, message: `Backfilled prices for ${processedCount} claims` });
} catch (error) {
console.error('Error backfilling prices:', error);
res.status(500).json({
success: false,
error: error.message
});
res.status(500).json({ success: false, error: error.message });
}
});

app.get('/api/claims/:userAddress/realized-gains', async (req, res) => {
try {
const { userAddress } = req.params;
const { startDate, endDate } = req.query;

const gains = await indexingService.getRealizedGains(
userAddress,
userAddress,
startDate ? new Date(startDate) : null,
endDate ? new Date(endDate) : null
);

res.json({ success: true, data: gains });
} catch (error) {
console.error('Error calculating realized gains:', error);
res.status(500).json({
success: false,
error: error.message
});
res.status(500).json({ success: false, error: error.message });
}
});

// Admin Routes
app.post('/api/admin/revoke', async (req, res) => {
try {
const { adminAddress, targetVault, reason } = req.body;
const result = await adminService.revokeAccess(adminAddress, targetVault, reason);
res.json({ success: true, data: result });
} catch (error) {
console.error('Error revoking access:', error);
res.status(500).json({
success: false,
error: error.message
});
res.status(500).json({ success: false, error: error.message });
}
});

Expand All @@ -152,10 +135,7 @@ app.post('/api/admin/create', async (req, res) => {
res.json({ success: true, data: result });
} catch (error) {
console.error('Error creating vault:', error);
res.status(500).json({
success: false,
error: error.message
});
res.status(500).json({ success: false, error: error.message });
}
});

Expand All @@ -166,10 +146,7 @@ app.post('/api/admin/transfer', async (req, res) => {
res.json({ success: true, data: result });
} catch (error) {
console.error('Error transferring vault:', error);
res.status(500).json({
success: false,
error: error.message
});
res.status(500).json({ success: false, error: error.message });
}
});

Expand All @@ -180,25 +157,18 @@ app.get('/api/admin/audit-logs', async (req, res) => {
res.json({ success: true, data: result });
} catch (error) {
console.error('Error fetching audit logs:', error);
res.status(500).json({
success: false,
error: error.message
});
res.status(500).json({ success: false, error: error.message });
}
});

// Admin Key Management Routes
app.post('/api/admin/propose-new-admin', async (req, res) => {
try {
const { currentAdminAddress, newAdminAddress, contractAddress } = req.body;
const result = await adminService.proposeNewAdmin(currentAdminAddress, newAdminAddress, contractAddress);
res.json({ success: true, data: result });
} catch (error) {
console.error('Error proposing new admin:', error);
res.status(500).json({
success: false,
error: error.message
});
res.status(500).json({ success: false, error: error.message });
}
});

Expand All @@ -209,10 +179,7 @@ app.post('/api/admin/accept-ownership', async (req, res) => {
res.json({ success: true, data: result });
} catch (error) {
console.error('Error accepting ownership:', error);
res.status(500).json({
success: false,
error: error.message
});
res.status(500).json({ success: false, error: error.message });
}
});

Expand All @@ -223,10 +190,7 @@ app.post('/api/admin/transfer-ownership', async (req, res) => {
res.json({ success: true, data: result });
} catch (error) {
console.error('Error transferring ownership:', error);
res.status(500).json({
success: false,
error: error.message
});
res.status(500).json({ success: false, error: error.message });
}
});

Expand All @@ -237,14 +201,10 @@ app.get('/api/admin/pending-transfers', async (req, res) => {
res.json({ success: true, data: result });
} catch (error) {
console.error('Error fetching pending transfers:', error);
res.status(500).json({
success: false,
error: error.message
});
res.status(500).json({ success: false, error: error.message });
}
});

// Stats Routes
app.get('/api/stats/tvl', async (req, res) => {
try {
const tvlStats = await tvlService.getTVLStats();
Expand All @@ -259,60 +219,29 @@ app.get('/api/stats/tvl', async (req, res) => {
});
} catch (error) {
console.error('Error fetching TVL stats:', error);
res.status(500).json({
success: false,
error: error.message
});
res.status(500).json({ success: false, error: error.message });
}
});

// Vault Export Routes
// Vault export with admin/org check
app.get('/api/vault/:id/export', async (req, res) => {
try {
const { id } = req.params;
const { admin_address } = req.query;
if (!admin_address) {
return res.status(400).json({ success: false, error: 'admin_address is required' });
}
// Get vault and org_id
const vault = await require('./models').Vault.findOne({ where: { id } });
if (!vault) {
return res.status(404).json({ success: false, error: 'Vault not found' });
}
const orgId = vault.org_id;
const isAdmin = await isAdminOfOrg(admin_address, orgId);
if (!isAdmin) {
return res.status(403).json({ success: false, error: 'Forbidden: admin_address does not belong to organization' });
}
// Set response headers for CSV download
res.setHeader('Content-Type', 'text/csv');
res.setHeader('Content-Disposition', `attachment; filename="vault-${id}-export-${new Date().toISOString().split('T')[0]}.csv"`);
// Stream the CSV data

await vaultExportService.streamVaultAsCSV(id, res);
} catch (error) {
console.error('Error exporting vault:', error);
if (!res.headersSent) {
res.status(500).json({
success: false,
error: error.message
});
res.status(500).json({ success: false, error: error.message });
} else {
res.destroy(error);
}
}
});

// Start server
const startServer = async () => {
try {
await sequelize.authenticate();
console.log('Database connection established successfully.');

await sequelize.sync({ alter: true });

console.log('Database synchronized successfully.');

// Initialize Redis Cache

try {
await cacheService.connect();
if (cacheService.isReady()) {
Expand All @@ -324,46 +253,34 @@ const startServer = async () => {
console.error('Failed to connect to Redis:', cacheError);
console.log('Continuing without Redis cache...');
}

// Initialize GraphQL Server

let graphQLServer = null;
try {
// Import GraphQL server (using require for CommonJS compatibility)
const { createGraphQLServer } = require('./graphql/server');
graphQLServer = await createGraphQLServer(app);
console.log('GraphQL Server initialized successfully.');

const serverInfo = graphQLServer.getServerInfo();
console.log(`GraphQL Playground available at: ${serverInfo.playgroundUrl}`);
console.log(`GraphQL Subscriptions available at: ${serverInfo.subscriptionEndpoint}`);
} catch (graphqlError) {
console.error('Failed to initialize GraphQL Server:', graphqlError);
console.log('Continuing with REST API only...');
}

// Initialize Discord Bot

try {
await discordBotService.start();
} catch (discordError) {
console.error('Failed to initialize Discord Bot:', discordError);
console.log('Continuing without Discord bot...');
}

// Initialize Notification Service (Cron Job)
try {
notificationService.start();
} catch (notificationError) {
console.error('Failed to start Notification Service:', notificationError);
}

// Start the HTTP server

httpServer.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
console.log(`REST API available at: http://localhost:${PORT}`);
if (graphQLServer) {
console.log(`GraphQL API available at: http://localhost:${PORT}/graphql`);
}

});
} catch (error) {
console.error('Unable to start server:', error);
Expand Down