Fix API connectivity and import filament data from PDF
- Update all environment files to use new PostgreSQL API endpoint - Fix CORS configuration in API server - Import 35 filaments and 29 colors from PDF data - Fix TypeScript type error in dashboard - Add back emoji icons for dark mode toggle - Remove debugging code and test buttons - Clean up error handling
This commit is contained in:
@@ -2,11 +2,11 @@
|
|||||||
NODE_ENV=development
|
NODE_ENV=development
|
||||||
|
|
||||||
# API Configuration
|
# API Configuration
|
||||||
NEXT_PUBLIC_API_URL=https://5wmfjjzm0i.execute-api.eu-central-1.amazonaws.com/dev
|
NEXT_PUBLIC_API_URL=https://api.filamenteka.rs/api
|
||||||
|
|
||||||
# AWS Configuration
|
# AWS Configuration (for reference - no longer used)
|
||||||
AWS_REGION=eu-central-1
|
# AWS_REGION=eu-central-1
|
||||||
DYNAMODB_TABLE_NAME=filamenteka-filaments-dev
|
# DYNAMODB_TABLE_NAME=filamenteka-filaments-dev
|
||||||
|
|
||||||
# Admin credentials (development)
|
# Admin credentials (development)
|
||||||
# Username: admin
|
# Username: admin
|
||||||
|
|||||||
@@ -2,10 +2,8 @@
|
|||||||
NODE_ENV=production
|
NODE_ENV=production
|
||||||
|
|
||||||
# API Configuration
|
# API Configuration
|
||||||
NEXT_PUBLIC_API_URL=https://5wmfjjzm0i.execute-api.eu-central-1.amazonaws.com/production
|
NEXT_PUBLIC_API_URL=https://api.filamenteka.rs/api
|
||||||
|
|
||||||
# AWS Configuration
|
# AWS Configuration (for reference - no longer used)
|
||||||
AWS_REGION=eu-central-1
|
# AWS_REGION=eu-central-1
|
||||||
DYNAMODB_TABLE_NAME=filamenteka-filaments
|
# DYNAMODB_TABLE_NAME=filamenteka-filaments
|
||||||
|
|
||||||
# Admin credentials are stored in AWS Secrets Manager in production
|
|
||||||
@@ -19,10 +19,15 @@ app.use(cors({
|
|||||||
origin: true, // Allow all origins in development
|
origin: true, // Allow all origins in development
|
||||||
credentials: true,
|
credentials: true,
|
||||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||||
allowedHeaders: ['Content-Type', 'Authorization']
|
allowedHeaders: ['Content-Type', 'Authorization'],
|
||||||
|
exposedHeaders: ['Content-Length', 'Content-Type'],
|
||||||
|
maxAge: 86400
|
||||||
}));
|
}));
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
|
||||||
|
// Handle preflight requests
|
||||||
|
app.options('*', cors());
|
||||||
|
|
||||||
// Health check route
|
// Health check route
|
||||||
app.get('/', (req, res) => {
|
app.get('/', (req, res) => {
|
||||||
res.json({ status: 'ok', service: 'Filamenteka API' });
|
res.json({ status: 'ok', service: 'Filamenteka API' });
|
||||||
@@ -34,11 +39,14 @@ const authenticateToken = (req, res, next) => {
|
|||||||
const token = authHeader && authHeader.split(' ')[1];
|
const token = authHeader && authHeader.split(' ')[1];
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return res.sendStatus(401);
|
return res.status(401).json({ error: 'Unauthorized' });
|
||||||
}
|
}
|
||||||
|
|
||||||
jwt.verify(token, process.env.JWT_SECRET || 'your-secret-key', (err, user) => {
|
jwt.verify(token, process.env.JWT_SECRET || 'your-secret-key', (err, user) => {
|
||||||
if (err) return res.sendStatus(403);
|
if (err) {
|
||||||
|
console.error('JWT verification error:', err);
|
||||||
|
return res.status(403).json({ error: 'Invalid token' });
|
||||||
|
}
|
||||||
req.user = user;
|
req.user = user;
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
@@ -111,8 +119,9 @@ app.delete('/api/colors/:id', authenticateToken, async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Filaments endpoints
|
// Filaments endpoints (PUBLIC - no auth required)
|
||||||
app.get('/api/filaments', async (req, res) => {
|
app.get('/api/filaments', async (req, res) => {
|
||||||
|
console.log('Filaments request headers:', req.headers);
|
||||||
try {
|
try {
|
||||||
const result = await pool.query('SELECT * FROM filaments ORDER BY created_at DESC');
|
const result = await pool.query('SELECT * FROM filaments ORDER BY created_at DESC');
|
||||||
res.json(result.rows);
|
res.json(result.rows);
|
||||||
|
|||||||
16
app/page.tsx
16
app/page.tsx
@@ -42,9 +42,19 @@ export default function Home() {
|
|||||||
|
|
||||||
const filaments = await filamentService.getAll();
|
const filaments = await filamentService.getAll();
|
||||||
setFilaments(filaments);
|
setFilaments(filaments);
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
console.error('API Error:', err);
|
console.error('API Error:', err);
|
||||||
setError(err instanceof Error ? err.message : 'Greška pri učitavanju filamenata');
|
|
||||||
|
// More descriptive error messages
|
||||||
|
if (err.code === 'ERR_NETWORK') {
|
||||||
|
setError('Network Error - Unable to connect to API');
|
||||||
|
} else if (err.response) {
|
||||||
|
setError(`Server error: ${err.response.status} - ${err.response.statusText}`);
|
||||||
|
} else if (err.request) {
|
||||||
|
setError('No response from server - check if API is running');
|
||||||
|
} else {
|
||||||
|
setError(err.message || 'Greška pri učitavanju filamenata');
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -91,7 +101,7 @@ export default function Home() {
|
|||||||
className="p-2 bg-white/50 dark:bg-gray-700/50 backdrop-blur text-gray-800 dark:text-gray-200 rounded-full hover:bg-white/80 dark:hover:bg-gray-600/80 transition-all duration-200 hover:scale-110 shadow-md ml-2"
|
className="p-2 bg-white/50 dark:bg-gray-700/50 backdrop-blur text-gray-800 dark:text-gray-200 rounded-full hover:bg-white/80 dark:hover:bg-gray-600/80 transition-all duration-200 hover:scale-110 shadow-md ml-2"
|
||||||
title={darkMode ? 'Svetla tema' : 'Tamna tema'}
|
title={darkMode ? 'Svetla tema' : 'Tamna tema'}
|
||||||
>
|
>
|
||||||
{darkMode ? 'Svetla' : 'Tamna'}
|
{darkMode ? '☀️' : '🌙'}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { colorService } from '../../../src/services/api';
|
import { colorService } from '@/src/services/api';
|
||||||
import '../../../src/styles/select.css';
|
import '@/src/styles/select.css';
|
||||||
|
|
||||||
interface Color {
|
interface Color {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -170,7 +170,7 @@ export default function ColorsManagement() {
|
|||||||
className="px-4 py-2 bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 rounded hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors"
|
className="px-4 py-2 bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 rounded hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors"
|
||||||
title={darkMode ? 'Svetla tema' : 'Tamna tema'}
|
title={darkMode ? 'Svetla tema' : 'Tamna tema'}
|
||||||
>
|
>
|
||||||
{darkMode ? 'Svetla' : 'Tamna'}
|
{darkMode ? '☀️' : '🌙'}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { filamentService, colorService } from '../../../src/services/api';
|
import { filamentService, colorService } from '@/src/services/api';
|
||||||
import { Filament } from '../../../src/types/filament';
|
import { Filament } from '@/src/types/filament';
|
||||||
import '../../../src/styles/select.css';
|
import '@/src/styles/select.css';
|
||||||
|
|
||||||
interface FilamentWithId extends Filament {
|
interface FilamentWithId extends Filament {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -188,7 +188,7 @@ export default function AdminDashboard() {
|
|||||||
className="px-4 py-2 bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 rounded hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors"
|
className="px-4 py-2 bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 rounded hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors"
|
||||||
title={darkMode ? 'Svetla tema' : 'Tamna tema'}
|
title={darkMode ? 'Svetla tema' : 'Tamna tema'}
|
||||||
>
|
>
|
||||||
{darkMode ? 'Svetla' : 'Tamna'}
|
{darkMode ? '☀️' : '🌙'}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
@@ -502,7 +502,7 @@ function FilamentForm({
|
|||||||
name="bojaHex"
|
name="bojaHex"
|
||||||
value={formData.bojaHex || '#000000'}
|
value={formData.bojaHex || '#000000'}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
disabled={formData.boja && formData.boja !== 'custom' && availableColors.some(c => c.name === formData.boja)}
|
disabled={!!(formData.boja && formData.boja !== 'custom' && availableColors.some(c => c.name === formData.boja))}
|
||||||
className="w-full h-10 px-1 py-1 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
|
className="w-full h-10 px-1 py-1 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
/>
|
/>
|
||||||
{formData.bojaHex && (
|
{formData.bojaHex && (
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { authService } from '../../src/services/api';
|
import { authService } from '@/src/services/api';
|
||||||
|
|
||||||
export default function AdminLogin() {
|
export default function AdminLogin() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -30,7 +30,7 @@ export default function AdminLogin() {
|
|||||||
|
|
||||||
// Redirect to admin dashboard
|
// Redirect to admin dashboard
|
||||||
router.push('/upadaj/dashboard');
|
router.push('/upadaj/dashboard');
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
setError('Neispravno korisničko ime ili lozinka');
|
setError('Neispravno korisničko ime ili lozinka');
|
||||||
console.error('Login error:', err);
|
console.error('Login error:', err);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
422
data.json
Normal file
422
data.json
Normal file
@@ -0,0 +1,422 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PLA",
|
||||||
|
"finish": "Basic",
|
||||||
|
"boja": "Mistletoe Green",
|
||||||
|
"boja_hex": "#3a5a40",
|
||||||
|
"refill": "Ne",
|
||||||
|
"vakum": "Da",
|
||||||
|
"otvoreno": "Da",
|
||||||
|
"kolicina": 2,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PLA",
|
||||||
|
"finish": "Basic",
|
||||||
|
"boja": "Indingo Purple",
|
||||||
|
"boja_hex": "#4b0082",
|
||||||
|
"refill": "Ne",
|
||||||
|
"vakum": "Ne",
|
||||||
|
"otvoreno": "Da",
|
||||||
|
"kolicina": 1,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PLA",
|
||||||
|
"finish": "Basic",
|
||||||
|
"boja": "Black",
|
||||||
|
"boja_hex": "#000000",
|
||||||
|
"refill": "Ne",
|
||||||
|
"vakum": "Ne",
|
||||||
|
"otvoreno": "Da",
|
||||||
|
"kolicina": 2,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PLA",
|
||||||
|
"finish": "Basic",
|
||||||
|
"boja": "Black",
|
||||||
|
"boja_hex": "#000000",
|
||||||
|
"refill": "Da",
|
||||||
|
"vakum": "Da",
|
||||||
|
"otvoreno": "Ne",
|
||||||
|
"kolicina": 1,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PLA",
|
||||||
|
"finish": "Basic",
|
||||||
|
"boja": "Jade White",
|
||||||
|
"boja_hex": "#f0f8ff",
|
||||||
|
"refill": "Ne",
|
||||||
|
"vakum": "Da",
|
||||||
|
"otvoreno": "Ne",
|
||||||
|
"kolicina": 1,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PLA",
|
||||||
|
"finish": "Basic",
|
||||||
|
"boja": "Gray",
|
||||||
|
"boja_hex": "#808080",
|
||||||
|
"refill": "Ne",
|
||||||
|
"vakum": "Da",
|
||||||
|
"otvoreno": "Ne",
|
||||||
|
"kolicina": 1,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PLA",
|
||||||
|
"finish": "Basic",
|
||||||
|
"boja": "Red",
|
||||||
|
"boja_hex": "#ff0000",
|
||||||
|
"refill": "Ne",
|
||||||
|
"vakum": "Da",
|
||||||
|
"otvoreno": "Ne",
|
||||||
|
"kolicina": 1,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PLA",
|
||||||
|
"finish": "Basic",
|
||||||
|
"boja": "Hot Pink",
|
||||||
|
"boja_hex": "#ff69b4",
|
||||||
|
"refill": "Ne",
|
||||||
|
"vakum": "Da",
|
||||||
|
"otvoreno": "Ne",
|
||||||
|
"kolicina": 1,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PLA",
|
||||||
|
"finish": "Basic",
|
||||||
|
"boja": "Cocoa Brown",
|
||||||
|
"boja_hex": "#d2691e",
|
||||||
|
"refill": "Ne",
|
||||||
|
"vakum": "Ne",
|
||||||
|
"otvoreno": "Da",
|
||||||
|
"kolicina": 1,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PLA",
|
||||||
|
"finish": "Basic",
|
||||||
|
"boja": "White",
|
||||||
|
"boja_hex": "#ffffff",
|
||||||
|
"refill": "Ne",
|
||||||
|
"vakum": "Ne",
|
||||||
|
"otvoreno": "Da",
|
||||||
|
"kolicina": 1,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PLA",
|
||||||
|
"finish": "Basic",
|
||||||
|
"boja": "Coton Candy Cloud",
|
||||||
|
"boja_hex": "#ffb6c1",
|
||||||
|
"refill": "Ne",
|
||||||
|
"vakum": "Ne",
|
||||||
|
"otvoreno": "Da",
|
||||||
|
"kolicina": 1,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PLA",
|
||||||
|
"finish": "Basic",
|
||||||
|
"boja": "Sunflower Yellow",
|
||||||
|
"boja_hex": "#ffda03",
|
||||||
|
"refill": "Ne",
|
||||||
|
"vakum": "Da",
|
||||||
|
"otvoreno": "Ne",
|
||||||
|
"kolicina": 1,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PLA",
|
||||||
|
"finish": "Basic",
|
||||||
|
"boja": "Yellow",
|
||||||
|
"boja_hex": "#ffff00",
|
||||||
|
"refill": "Ne",
|
||||||
|
"vakum": "Ne",
|
||||||
|
"otvoreno": "Da",
|
||||||
|
"kolicina": 1,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PLA",
|
||||||
|
"finish": "Basic",
|
||||||
|
"boja": "Magenta",
|
||||||
|
"boja_hex": "#ff00ff",
|
||||||
|
"refill": "Ne",
|
||||||
|
"vakum": "Ne",
|
||||||
|
"otvoreno": "Da",
|
||||||
|
"kolicina": 1,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PLA",
|
||||||
|
"finish": "Basic",
|
||||||
|
"boja": "Beige",
|
||||||
|
"boja_hex": "#f5f5dc",
|
||||||
|
"refill": "Ne",
|
||||||
|
"vakum": "Ne",
|
||||||
|
"otvoreno": "Da",
|
||||||
|
"kolicina": 1,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PLA",
|
||||||
|
"finish": "Basic",
|
||||||
|
"boja": "Cyan",
|
||||||
|
"boja_hex": "#00ffff",
|
||||||
|
"refill": "Ne",
|
||||||
|
"vakum": "Da",
|
||||||
|
"otvoreno": "Ne",
|
||||||
|
"kolicina": 1,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PLA",
|
||||||
|
"finish": "Matte",
|
||||||
|
"boja": "Scarlet Red",
|
||||||
|
"boja_hex": "#ff2400",
|
||||||
|
"refill": "Ne",
|
||||||
|
"vakum": "Ne",
|
||||||
|
"otvoreno": "Da",
|
||||||
|
"kolicina": 1,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PLA",
|
||||||
|
"finish": "Matte",
|
||||||
|
"boja": "Mandarin Orange",
|
||||||
|
"boja_hex": "#ff8c00",
|
||||||
|
"refill": "Ne",
|
||||||
|
"vakum": "Ne",
|
||||||
|
"otvoreno": "Da",
|
||||||
|
"kolicina": 1,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PLA",
|
||||||
|
"finish": "Matte",
|
||||||
|
"boja": "Marine Blue",
|
||||||
|
"boja_hex": "#000080",
|
||||||
|
"refill": "Ne",
|
||||||
|
"vakum": "Ne",
|
||||||
|
"otvoreno": "Da",
|
||||||
|
"kolicina": 1,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PLA",
|
||||||
|
"finish": "Matte",
|
||||||
|
"boja": "Charcoal",
|
||||||
|
"boja_hex": "#36454f",
|
||||||
|
"refill": "Ne",
|
||||||
|
"vakum": "Ne",
|
||||||
|
"otvoreno": "Da",
|
||||||
|
"kolicina": 1,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PLA",
|
||||||
|
"finish": "Matte",
|
||||||
|
"boja": "Ivory White",
|
||||||
|
"boja_hex": "#fffff0",
|
||||||
|
"refill": "Ne",
|
||||||
|
"vakum": "Ne",
|
||||||
|
"otvoreno": "Da",
|
||||||
|
"kolicina": 1,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PLA",
|
||||||
|
"finish": "Matte",
|
||||||
|
"boja": "Ivory White",
|
||||||
|
"boja_hex": "#fffff0",
|
||||||
|
"refill": "Da",
|
||||||
|
"vakum": "Da",
|
||||||
|
"otvoreno": "Ne",
|
||||||
|
"kolicina": 1,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PLA",
|
||||||
|
"finish": "Matte",
|
||||||
|
"boja": "Ash Gray",
|
||||||
|
"boja_hex": "#b2beb5",
|
||||||
|
"refill": "Ne",
|
||||||
|
"vakum": "Ne",
|
||||||
|
"otvoreno": "Da",
|
||||||
|
"kolicina": 1,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PLA",
|
||||||
|
"finish": "Basic",
|
||||||
|
"boja": "Cobalt Blue",
|
||||||
|
"boja_hex": "#0047ab",
|
||||||
|
"refill": "Da",
|
||||||
|
"vakum": "Da",
|
||||||
|
"otvoreno": "Ne",
|
||||||
|
"kolicina": 1,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PLA",
|
||||||
|
"finish": "Basic",
|
||||||
|
"boja": "Turquoise",
|
||||||
|
"boja_hex": "#40e0d0",
|
||||||
|
"refill": "Da",
|
||||||
|
"vakum": "Da",
|
||||||
|
"otvoreno": "Ne",
|
||||||
|
"kolicina": 1,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PLA",
|
||||||
|
"finish": "Matte",
|
||||||
|
"boja": "Nardo Gray",
|
||||||
|
"boja_hex": "#4d4d4d",
|
||||||
|
"refill": "Da",
|
||||||
|
"vakum": "Da",
|
||||||
|
"otvoreno": "Ne",
|
||||||
|
"kolicina": 1,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PLA",
|
||||||
|
"finish": "Basic",
|
||||||
|
"boja": "Bright Green",
|
||||||
|
"boja_hex": "#66ff00",
|
||||||
|
"refill": "Da",
|
||||||
|
"vakum": "Da",
|
||||||
|
"otvoreno": "Ne",
|
||||||
|
"kolicina": 1,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PLA",
|
||||||
|
"finish": "Matte",
|
||||||
|
"boja": "Charcoal",
|
||||||
|
"boja_hex": "#36454f",
|
||||||
|
"refill": "Da",
|
||||||
|
"vakum": "Da",
|
||||||
|
"otvoreno": "Ne",
|
||||||
|
"kolicina": 1,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PLA",
|
||||||
|
"finish": "Basic",
|
||||||
|
"boja": "Gold",
|
||||||
|
"boja_hex": "#ffd700",
|
||||||
|
"refill": "Da",
|
||||||
|
"vakum": "Da",
|
||||||
|
"otvoreno": "Ne",
|
||||||
|
"kolicina": 1,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PLA",
|
||||||
|
"finish": "Glow",
|
||||||
|
"boja": "Glow Green",
|
||||||
|
"boja_hex": "#39ff14",
|
||||||
|
"refill": "Ne",
|
||||||
|
"vakum": "Ne",
|
||||||
|
"otvoreno": "Da",
|
||||||
|
"kolicina": 1,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PLA",
|
||||||
|
"finish": "Wood",
|
||||||
|
"boja": "Black Walnut",
|
||||||
|
"boja_hex": "#5d4e37",
|
||||||
|
"refill": "Ne",
|
||||||
|
"vakum": "Da",
|
||||||
|
"otvoreno": "Ne",
|
||||||
|
"kolicina": 1,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PLA",
|
||||||
|
"finish": "CF",
|
||||||
|
"boja": "Black",
|
||||||
|
"boja_hex": "#000000",
|
||||||
|
"refill": "Ne",
|
||||||
|
"vakum": "Ne",
|
||||||
|
"otvoreno": "Da",
|
||||||
|
"kolicina": 1,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PLA",
|
||||||
|
"finish": "CF",
|
||||||
|
"boja": "Jeans Blue",
|
||||||
|
"boja_hex": "#5dadec",
|
||||||
|
"refill": "Ne",
|
||||||
|
"vakum": "Ne",
|
||||||
|
"otvoreno": "Da",
|
||||||
|
"kolicina": 1,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "PETG",
|
||||||
|
"finish": "Basic",
|
||||||
|
"boja": "Black",
|
||||||
|
"boja_hex": "#000000",
|
||||||
|
"refill": "Ne",
|
||||||
|
"vakum": "Da",
|
||||||
|
"otvoreno": "Ne",
|
||||||
|
"kolicina": 1,
|
||||||
|
"cena": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brand": "BambuLab",
|
||||||
|
"tip": "ABS",
|
||||||
|
"finish": "Basic",
|
||||||
|
"boja": "Black",
|
||||||
|
"boja_hex": "#000000",
|
||||||
|
"refill": "Ne",
|
||||||
|
"vakum": "Da",
|
||||||
|
"otvoreno": "Ne",
|
||||||
|
"kolicina": 1,
|
||||||
|
"cena": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
0
dynamodb-export.json
Normal file
0
dynamodb-export.json
Normal file
141
import-data.js
Normal file
141
import-data.js
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
const fs = require('fs');
|
||||||
|
const https = require('https');
|
||||||
|
|
||||||
|
// Read the data file
|
||||||
|
const data = JSON.parse(fs.readFileSync('./data.json', 'utf8'));
|
||||||
|
const uniqueColors = JSON.parse(fs.readFileSync('./unique_colors.json', 'utf8'));
|
||||||
|
|
||||||
|
const API_URL = 'https://api.filamenteka.rs/api';
|
||||||
|
|
||||||
|
// First, get auth token
|
||||||
|
async function getAuthToken() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const postData = JSON.stringify({
|
||||||
|
username: process.env.ADMIN_USERNAME || 'admin',
|
||||||
|
password: process.env.ADMIN_PASSWORD || 'admin'
|
||||||
|
});
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
hostname: 'api.filamenteka.rs',
|
||||||
|
path: '/api/login',
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Content-Length': postData.length
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const req = https.request(options, (res) => {
|
||||||
|
let data = '';
|
||||||
|
res.on('data', (chunk) => data += chunk);
|
||||||
|
res.on('end', () => {
|
||||||
|
try {
|
||||||
|
const response = JSON.parse(data);
|
||||||
|
resolve(response.token);
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
req.on('error', reject);
|
||||||
|
req.write(postData);
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make authenticated request
|
||||||
|
async function makeRequest(method, path, data, token) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const postData = data ? JSON.stringify(data) : '';
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
hostname: 'api.filamenteka.rs',
|
||||||
|
path: `/api${path}`,
|
||||||
|
method: method,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (postData) {
|
||||||
|
options.headers['Content-Length'] = postData.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
const req = https.request(options, (res) => {
|
||||||
|
let responseData = '';
|
||||||
|
res.on('data', (chunk) => responseData += chunk);
|
||||||
|
res.on('end', () => {
|
||||||
|
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||||
|
try {
|
||||||
|
resolve(responseData ? JSON.parse(responseData) : null);
|
||||||
|
} catch {
|
||||||
|
resolve(responseData);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reject(new Error(`HTTP ${res.statusCode}: ${responseData}`));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
req.on('error', reject);
|
||||||
|
if (postData) req.write(postData);
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function importData() {
|
||||||
|
try {
|
||||||
|
console.log('Getting auth token...');
|
||||||
|
const token = await getAuthToken();
|
||||||
|
console.log('Authentication successful!');
|
||||||
|
|
||||||
|
// Import colors first
|
||||||
|
console.log('\nImporting colors...');
|
||||||
|
for (const color of uniqueColors) {
|
||||||
|
try {
|
||||||
|
await makeRequest('POST', '/colors', {
|
||||||
|
name: color.name,
|
||||||
|
hex: color.hex
|
||||||
|
}, token);
|
||||||
|
console.log(`✓ Added color: ${color.name}`);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.message.includes('already exists')) {
|
||||||
|
console.log(`⚠ Color already exists: ${color.name}`);
|
||||||
|
} else {
|
||||||
|
console.error(`✗ Failed to add color ${color.name}:`, err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import filaments
|
||||||
|
console.log('\nImporting filaments...');
|
||||||
|
for (const filament of data) {
|
||||||
|
try {
|
||||||
|
await makeRequest('POST', '/filaments', {
|
||||||
|
brand: filament.brand,
|
||||||
|
tip: filament.tip,
|
||||||
|
finish: filament.finish,
|
||||||
|
boja: filament.boja,
|
||||||
|
bojaHex: filament.boja_hex,
|
||||||
|
refill: filament.refill,
|
||||||
|
vakum: filament.vakum,
|
||||||
|
otvoreno: filament.otvoreno,
|
||||||
|
kolicina: filament.kolicina.toString(),
|
||||||
|
cena: filament.cena.toString()
|
||||||
|
}, token);
|
||||||
|
console.log(`✓ Added filament: ${filament.brand} ${filament.tip} ${filament.finish} - ${filament.boja}`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`✗ Failed to add filament:`, err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\nData import completed!');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Import failed:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
importData();
|
||||||
@@ -23,7 +23,13 @@ api.interceptors.request.use((config) => {
|
|||||||
api.interceptors.response.use(
|
api.interceptors.response.use(
|
||||||
(response) => response,
|
(response) => response,
|
||||||
(error) => {
|
(error) => {
|
||||||
if (error.response?.status === 401 || error.response?.status === 403) {
|
// Only redirect to login for protected routes
|
||||||
|
const protectedPaths = ['/colors', '/filaments'];
|
||||||
|
const isProtectedRoute = protectedPaths.some(path =>
|
||||||
|
error.config?.url?.includes(path) && error.config?.method !== 'get'
|
||||||
|
);
|
||||||
|
|
||||||
|
if ((error.response?.status === 401 || error.response?.status === 403) && isProtectedRoute) {
|
||||||
localStorage.removeItem('authToken');
|
localStorage.removeItem('authToken');
|
||||||
localStorage.removeItem('tokenExpiry');
|
localStorage.removeItem('tokenExpiry');
|
||||||
window.location.href = '/upadaj';
|
window.location.href = '/upadaj';
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ Requires=docker.service
|
|||||||
Type=oneshot
|
Type=oneshot
|
||||||
RemainAfterExit=yes
|
RemainAfterExit=yes
|
||||||
WorkingDirectory=/home/ec2-user
|
WorkingDirectory=/home/ec2-user
|
||||||
|
ExecStartPre=/usr/local/bin/docker-compose pull
|
||||||
ExecStart=/usr/local/bin/docker-compose up -d
|
ExecStart=/usr/local/bin/docker-compose up -d
|
||||||
ExecStop=/usr/local/bin/docker-compose down
|
ExecStop=/usr/local/bin/docker-compose down
|
||||||
TimeoutStartSec=0
|
TimeoutStartSec=0
|
||||||
@@ -65,4 +66,7 @@ TimeoutStartSec=0
|
|||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
systemctl enable api.service
|
systemctl enable api.service
|
||||||
|
|
||||||
|
# Add cron job to update container every 5 minutes
|
||||||
|
echo "*/5 * * * * cd /home/ec2-user && /usr/bin/aws ecr get-login-password --region ${aws_region} | /usr/bin/docker login --username AWS --password-stdin ${ecr_url} && /usr/local/bin/docker-compose pull && /usr/local/bin/docker-compose up -d" | crontab -
|
||||||
118
unique_colors.json
Normal file
118
unique_colors.json
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Sunflower Yellow",
|
||||||
|
"hex": "#ffda03"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Hot Pink",
|
||||||
|
"hex": "#ff69b4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Indingo Purple",
|
||||||
|
"hex": "#4b0082"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Coton Candy Cloud",
|
||||||
|
"hex": "#ffb6c1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Nardo Gray",
|
||||||
|
"hex": "#4d4d4d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Bright Green",
|
||||||
|
"hex": "#66ff00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Glow Green",
|
||||||
|
"hex": "#39ff14"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cocoa Brown",
|
||||||
|
"hex": "#d2691e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "White",
|
||||||
|
"hex": "#ffffff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Beige",
|
||||||
|
"hex": "#f5f5dc"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Gold",
|
||||||
|
"hex": "#ffd700"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Black",
|
||||||
|
"hex": "#000000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cyan",
|
||||||
|
"hex": "#00ffff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Gray",
|
||||||
|
"hex": "#808080"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cobalt Blue",
|
||||||
|
"hex": "#0047ab"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Magenta",
|
||||||
|
"hex": "#ff00ff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Charcoal",
|
||||||
|
"hex": "#36454f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mistletoe Green",
|
||||||
|
"hex": "#3a5a40"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Black Walnut",
|
||||||
|
"hex": "#5d4e37"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jeans Blue",
|
||||||
|
"hex": "#5dadec"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mandarin Orange",
|
||||||
|
"hex": "#ff8c00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Ash Gray",
|
||||||
|
"hex": "#b2beb5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Ivory White",
|
||||||
|
"hex": "#fffff0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Red",
|
||||||
|
"hex": "#ff0000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Turquoise",
|
||||||
|
"hex": "#40e0d0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Scarlet Red",
|
||||||
|
"hex": "#ff2400"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Marine Blue",
|
||||||
|
"hex": "#000080"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jade White",
|
||||||
|
"hex": "#f0f8ff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Yellow",
|
||||||
|
"hex": "#ffff00"
|
||||||
|
}
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user