last changes

This commit is contained in:
fahed
2026-02-16 10:59:55 +03:00
parent e92f11241d
commit 9bb4ecb0dd
808 changed files with 106431 additions and 0 deletions

271
server/index.js Normal file
View File

@@ -0,0 +1,271 @@
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const axios = require('axios');
const app = express();
app.use(cors());
app.use(express.json());
const PORT = process.env.SALLA_SERVER_PORT || 3001;
// Salla OAuth Config
const SALLA_CLIENT_ID = process.env.SALLA_CLIENT_ID;
const SALLA_CLIENT_SECRET = process.env.SALLA_CLIENT_SECRET;
const SALLA_REDIRECT_URI = process.env.SALLA_REDIRECT_URI || 'http://localhost:3001/auth/callback';
// Token storage (in production, use a database)
let accessToken = process.env.SALLA_ACCESS_TOKEN || null;
let refreshToken = process.env.SALLA_REFRESH_TOKEN || null;
// ============================================
// OAuth Endpoints
// ============================================
// State for CSRF protection
const crypto = require('crypto');
let oauthState = null;
// Step 1: Redirect to Salla authorization
app.get('/auth/login', (req, res) => {
oauthState = crypto.randomBytes(16).toString('hex');
const authUrl = `https://accounts.salla.sa/oauth2/auth?` +
`client_id=${SALLA_CLIENT_ID}` +
`&redirect_uri=${encodeURIComponent(SALLA_REDIRECT_URI)}` +
`&response_type=code` +
`&scope=offline_access` +
`&state=${oauthState}`;
res.redirect(authUrl);
});
// Step 2: Handle OAuth callback
app.get('/auth/callback', async (req, res) => {
const { code, error, state } = req.query;
if (error) {
return res.status(400).json({ error: 'Authorization denied', details: error });
}
if (!code) {
return res.status(400).json({ error: 'No authorization code received' });
}
// Verify state (optional check - some flows may not return state)
if (oauthState && state && state !== oauthState) {
return res.status(400).json({ error: 'Invalid state parameter' });
}
try {
// Exchange code for tokens
const response = await axios.post('https://accounts.salla.sa/oauth2/token', {
client_id: SALLA_CLIENT_ID,
client_secret: SALLA_CLIENT_SECRET,
grant_type: 'authorization_code',
code: code,
redirect_uri: SALLA_REDIRECT_URI
}, {
headers: { 'Content-Type': 'application/json' }
});
accessToken = response.data.access_token;
refreshToken = response.data.refresh_token;
// Log tokens (save these to .env for persistence)
console.log('\n========================================');
console.log('🎉 SALLA CONNECTED SUCCESSFULLY!');
console.log('========================================');
console.log('Add these to your .env file:');
console.log(`SALLA_ACCESS_TOKEN=${accessToken}`);
console.log(`SALLA_REFRESH_TOKEN=${refreshToken}`);
console.log('========================================\n');
res.send(`
<html>
<body style="font-family: Arial; text-align: center; padding: 50px;">
<h1>✅ Salla Connected!</h1>
<p>Authorization successful. You can close this window.</p>
<p>Tokens have been logged to the console.</p>
<script>setTimeout(() => window.close(), 3000);</script>
</body>
</html>
`);
} catch (err) {
console.error('Token exchange failed:', err.response?.data || err.message);
res.status(500).json({ error: 'Token exchange failed', details: err.response?.data });
}
});
// Check auth status
app.get('/auth/status', (req, res) => {
res.json({
connected: !!accessToken,
hasRefreshToken: !!refreshToken
});
});
// Refresh token
async function refreshAccessToken() {
if (!refreshToken) throw new Error('No refresh token available');
const response = await axios.post('https://accounts.salla.sa/oauth2/token', {
client_id: SALLA_CLIENT_ID,
client_secret: SALLA_CLIENT_SECRET,
grant_type: 'refresh_token',
refresh_token: refreshToken
});
accessToken = response.data.access_token;
if (response.data.refresh_token) {
refreshToken = response.data.refresh_token;
}
return accessToken;
}
// ============================================
// Salla API Proxy Endpoints
// ============================================
// Generic API caller with auto-refresh
async function callSallaAPI(endpoint, method = 'GET', data = null) {
if (!accessToken) throw new Error('Not authenticated. Visit /auth/login first.');
try {
const response = await axios({
method,
url: `https://api.salla.dev/admin/v2${endpoint}`,
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
data
});
return response.data;
} catch (err) {
if (err.response?.status === 401) {
// Try refresh
await refreshAccessToken();
return callSallaAPI(endpoint, method, data);
}
throw err;
}
}
// Get store info
app.get('/api/store', async (req, res) => {
try {
const data = await callSallaAPI('/store/info');
res.json(data);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// Get orders
app.get('/api/orders', async (req, res) => {
try {
const { page = 1, per_page = 50, status } = req.query;
let endpoint = `/orders?page=${page}&per_page=${per_page}`;
if (status) endpoint += `&status=${status}`;
const data = await callSallaAPI(endpoint);
res.json(data);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// Get order details
app.get('/api/orders/:id', async (req, res) => {
try {
const data = await callSallaAPI(`/orders/${req.params.id}`);
res.json(data);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// Get products
app.get('/api/products', async (req, res) => {
try {
const { page = 1, per_page = 50 } = req.query;
const data = await callSallaAPI(`/products?page=${page}&per_page=${per_page}`);
res.json(data);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// Get customers
app.get('/api/customers', async (req, res) => {
try {
const { page = 1, per_page = 50 } = req.query;
const data = await callSallaAPI(`/customers?page=${page}&per_page=${per_page}`);
res.json(data);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// Get analytics/reports
app.get('/api/analytics/summary', async (req, res) => {
try {
// Fetch multiple endpoints for a summary
const [orders, products] = await Promise.all([
callSallaAPI('/orders?per_page=100'),
callSallaAPI('/products?per_page=100')
]);
// Calculate summary
const ordersList = orders.data || [];
const totalRevenue = ordersList.reduce((sum, o) => sum + (o.amounts?.total?.amount || 0), 0);
const avgOrderValue = ordersList.length > 0 ? totalRevenue / ordersList.length : 0;
res.json({
orders: {
total: orders.pagination?.total || ordersList.length,
recent: ordersList.length
},
products: {
total: products.pagination?.total || (products.data?.length || 0)
},
revenue: {
total: totalRevenue,
average_order: avgOrderValue,
currency: ordersList[0]?.amounts?.total?.currency || 'SAR'
}
});
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// ============================================
// Start Server
// ============================================
app.listen(PORT, () => {
console.log(`\n🚀 Salla Integration Server running on http://localhost:${PORT}`);
console.log('\nEndpoints:');
console.log(' GET /auth/login - Start OAuth flow');
console.log(' GET /auth/callback - OAuth callback');
console.log(' GET /auth/status - Check connection status');
console.log(' GET /api/store - Store info');
console.log(' GET /api/orders - List orders');
console.log(' GET /api/products - List products');
console.log(' GET /api/customers - List customers');
console.log(' GET /api/analytics/summary - Dashboard summary');
if (!SALLA_CLIENT_ID || !SALLA_CLIENT_SECRET) {
console.log('\n⚠ WARNING: SALLA_CLIENT_ID and SALLA_CLIENT_SECRET not set!');
console.log(' Add them to server/.env file');
}
if (accessToken) {
console.log('\n✅ Access token loaded from environment');
} else {
console.log('\n📝 Visit http://localhost:' + PORT + '/auth/login to connect Salla');
}
});