- Add database migration for sale fields (percentage, active, dates) - Update API to handle sale operations and bulk updates - Create SaleManager component for admin interface - Update FilamentTableV2 to display sale prices on frontend - Add sale column in admin dashboard - Implement sale price calculations with strikethrough styling
241 lines
7.6 KiB
JavaScript
241 lines
7.6 KiB
JavaScript
const express = require('express');
|
|
const { Pool } = require('pg');
|
|
const cors = require('cors');
|
|
const bcrypt = require('bcryptjs');
|
|
const jwt = require('jsonwebtoken');
|
|
require('dotenv').config();
|
|
|
|
const app = express();
|
|
const PORT = process.env.PORT || 80;
|
|
|
|
// PostgreSQL connection
|
|
const pool = new Pool({
|
|
connectionString: process.env.DATABASE_URL,
|
|
ssl: process.env.DATABASE_URL?.includes('amazonaws.com') ? { rejectUnauthorized: false } : false
|
|
});
|
|
|
|
// Middleware
|
|
app.use(cors({
|
|
origin: true, // Allow all origins in development
|
|
credentials: true,
|
|
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
|
allowedHeaders: ['Content-Type', 'Authorization'],
|
|
exposedHeaders: ['Content-Length', 'Content-Type'],
|
|
maxAge: 86400
|
|
}));
|
|
app.use(express.json());
|
|
|
|
// Handle preflight requests
|
|
app.options('*', cors());
|
|
|
|
// Health check route
|
|
app.get('/', (req, res) => {
|
|
res.json({ status: 'ok', service: 'Filamenteka API' });
|
|
});
|
|
|
|
// JWT middleware
|
|
const authenticateToken = (req, res, next) => {
|
|
const authHeader = req.headers['authorization'];
|
|
const token = authHeader && authHeader.split(' ')[1];
|
|
|
|
if (!token) {
|
|
return res.status(401).json({ error: 'Unauthorized' });
|
|
}
|
|
|
|
jwt.verify(token, process.env.JWT_SECRET || 'your-secret-key', (err, user) => {
|
|
if (err) {
|
|
console.error('JWT verification error:', err);
|
|
return res.status(403).json({ error: 'Invalid token' });
|
|
}
|
|
req.user = user;
|
|
next();
|
|
});
|
|
};
|
|
|
|
// Auth endpoints
|
|
app.post('/api/login', async (req, res) => {
|
|
const { username, password } = req.body;
|
|
|
|
// For now, simple hardcoded admin check
|
|
if (username === 'admin' && password === process.env.ADMIN_PASSWORD) {
|
|
const token = jwt.sign({ username }, process.env.JWT_SECRET || 'your-secret-key', { expiresIn: '24h' });
|
|
res.json({ token });
|
|
} else {
|
|
res.status(401).json({ error: 'Invalid credentials' });
|
|
}
|
|
});
|
|
|
|
// Colors endpoints
|
|
app.get('/api/colors', async (req, res) => {
|
|
try {
|
|
const result = await pool.query('SELECT * FROM colors ORDER BY name');
|
|
res.json(result.rows);
|
|
} catch (error) {
|
|
console.error('Error fetching colors:', error);
|
|
res.status(500).json({ error: 'Failed to fetch colors' });
|
|
}
|
|
});
|
|
|
|
app.post('/api/colors', authenticateToken, async (req, res) => {
|
|
const { name, hex, cena_refill, cena_spulna } = req.body;
|
|
|
|
try {
|
|
const result = await pool.query(
|
|
'INSERT INTO colors (name, hex, cena_refill, cena_spulna) VALUES ($1, $2, $3, $4) RETURNING *',
|
|
[name, hex, cena_refill || 3499, cena_spulna || 3999]
|
|
);
|
|
res.json(result.rows[0]);
|
|
} catch (error) {
|
|
console.error('Error creating color:', error);
|
|
res.status(500).json({ error: 'Failed to create color' });
|
|
}
|
|
});
|
|
|
|
app.put('/api/colors/:id', authenticateToken, async (req, res) => {
|
|
const { id } = req.params;
|
|
const { name, hex, cena_refill, cena_spulna } = req.body;
|
|
|
|
try {
|
|
const result = await pool.query(
|
|
'UPDATE colors SET name = $1, hex = $2, cena_refill = $3, cena_spulna = $4, updated_at = CURRENT_TIMESTAMP WHERE id = $5 RETURNING *',
|
|
[name, hex, cena_refill || 3499, cena_spulna || 3999, id]
|
|
);
|
|
res.json(result.rows[0]);
|
|
} catch (error) {
|
|
console.error('Error updating color:', error);
|
|
res.status(500).json({ error: 'Failed to update color' });
|
|
}
|
|
});
|
|
|
|
app.delete('/api/colors/:id', authenticateToken, async (req, res) => {
|
|
const { id } = req.params;
|
|
|
|
try {
|
|
await pool.query('DELETE FROM colors WHERE id = $1', [id]);
|
|
res.json({ success: true });
|
|
} catch (error) {
|
|
console.error('Error deleting color:', error);
|
|
res.status(500).json({ error: 'Failed to delete color' });
|
|
}
|
|
});
|
|
|
|
// Filaments endpoints (PUBLIC - no auth required)
|
|
app.get('/api/filaments', async (req, res) => {
|
|
try {
|
|
const result = await pool.query('SELECT * FROM filaments ORDER BY created_at DESC');
|
|
res.json(result.rows);
|
|
} catch (error) {
|
|
console.error('Error fetching filaments:', error);
|
|
res.status(500).json({ error: 'Failed to fetch filaments' });
|
|
}
|
|
});
|
|
|
|
app.post('/api/filaments', authenticateToken, async (req, res) => {
|
|
const { tip, finish, boja, boja_hex, refill, spulna, cena } = req.body;
|
|
|
|
try {
|
|
// Ensure refill and spulna are numbers
|
|
const refillNum = parseInt(refill) || 0;
|
|
const spulnaNum = parseInt(spulna) || 0;
|
|
const kolicina = refillNum + spulnaNum;
|
|
|
|
const result = await pool.query(
|
|
`INSERT INTO filaments (tip, finish, boja, boja_hex, refill, spulna, kolicina, cena)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *`,
|
|
[tip, finish, boja, boja_hex, refillNum, spulnaNum, kolicina, cena]
|
|
);
|
|
res.json(result.rows[0]);
|
|
} catch (error) {
|
|
console.error('Error creating filament:', error);
|
|
res.status(500).json({ error: 'Failed to create filament' });
|
|
}
|
|
});
|
|
|
|
app.put('/api/filaments/:id', authenticateToken, async (req, res) => {
|
|
const { id } = req.params;
|
|
const { tip, finish, boja, boja_hex, refill, spulna, cena, sale_percentage, sale_active, sale_start_date, sale_end_date } = req.body;
|
|
|
|
try {
|
|
// Ensure refill and spulna are numbers
|
|
const refillNum = parseInt(refill) || 0;
|
|
const spulnaNum = parseInt(spulna) || 0;
|
|
const kolicina = refillNum + spulnaNum;
|
|
|
|
const result = await pool.query(
|
|
`UPDATE filaments
|
|
SET tip = $1, finish = $2, boja = $3, boja_hex = $4,
|
|
refill = $5, spulna = $6, kolicina = $7, cena = $8,
|
|
sale_percentage = $9, sale_active = $10,
|
|
sale_start_date = $11, sale_end_date = $12,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = $13 RETURNING *`,
|
|
[tip, finish, boja, boja_hex, refillNum, spulnaNum, kolicina, cena,
|
|
sale_percentage || 0, sale_active || false, sale_start_date, sale_end_date, id]
|
|
);
|
|
res.json(result.rows[0]);
|
|
} catch (error) {
|
|
console.error('Error updating filament:', error);
|
|
res.status(500).json({ error: 'Failed to update filament' });
|
|
}
|
|
});
|
|
|
|
app.delete('/api/filaments/:id', authenticateToken, async (req, res) => {
|
|
const { id } = req.params;
|
|
|
|
try {
|
|
await pool.query('DELETE FROM filaments WHERE id = $1', [id]);
|
|
res.json({ success: true });
|
|
} catch (error) {
|
|
console.error('Error deleting filament:', error);
|
|
res.status(500).json({ error: 'Failed to delete filament' });
|
|
}
|
|
});
|
|
|
|
// Bulk sale update endpoint
|
|
app.post('/api/filaments/sale/bulk', authenticateToken, async (req, res) => {
|
|
const { filamentIds, salePercentage, saleStartDate, saleEndDate, enableSale } = req.body;
|
|
|
|
try {
|
|
let query;
|
|
let params;
|
|
|
|
if (filamentIds && filamentIds.length > 0) {
|
|
// Update specific filaments
|
|
query = `
|
|
UPDATE filaments
|
|
SET sale_percentage = $1,
|
|
sale_active = $2,
|
|
sale_start_date = $3,
|
|
sale_end_date = $4,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = ANY($5)
|
|
RETURNING *`;
|
|
params = [salePercentage || 0, enableSale || false, saleStartDate, saleEndDate, filamentIds];
|
|
} else {
|
|
// Update all filaments
|
|
query = `
|
|
UPDATE filaments
|
|
SET sale_percentage = $1,
|
|
sale_active = $2,
|
|
sale_start_date = $3,
|
|
sale_end_date = $4,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
RETURNING *`;
|
|
params = [salePercentage || 0, enableSale || false, saleStartDate, saleEndDate];
|
|
}
|
|
|
|
const result = await pool.query(query, params);
|
|
res.json({
|
|
success: true,
|
|
updatedCount: result.rowCount,
|
|
updatedFilaments: result.rows
|
|
});
|
|
} catch (error) {
|
|
console.error('Error updating sale:', error);
|
|
res.status(500).json({ error: 'Failed to update sale' });
|
|
}
|
|
});
|
|
|
|
app.listen(PORT, () => {
|
|
console.log(`Server running on port ${PORT}`);
|
|
}); |