Remove decorative icons and update CORS configuration

This commit is contained in:
DaX
2025-06-20 13:05:36 +02:00
parent 18110ab159
commit 62a4891112
51 changed files with 4284 additions and 2385 deletions

7
api/.dockerignore Normal file
View File

@@ -0,0 +1,7 @@
node_modules
npm-debug.log
.env
.env.example
README.md
.git
.gitignore

14
api/.env.example Normal file
View File

@@ -0,0 +1,14 @@
# Database connection
DATABASE_URL=postgresql://username:password@localhost:5432/filamenteka
# JWT Secret
JWT_SECRET=your-secret-key-here
# Admin password
ADMIN_PASSWORD=your-admin-password
# Server port
PORT=4000
# Environment
NODE_ENV=development

18
api/Dockerfile Normal file
View File

@@ -0,0 +1,18 @@
FROM node:18-alpine
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy application files
COPY . .
# Expose port
EXPOSE 80
# Start the application
CMD ["node", "server.js"]

75
api/migrate.js Normal file
View File

@@ -0,0 +1,75 @@
const { Pool } = require('pg');
const fs = require('fs');
const path = require('path');
require('dotenv').config();
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: process.env.DATABASE_URL?.includes('amazonaws.com') ? { rejectUnauthorized: false } : false
});
async function migrate() {
try {
// Read schema file
const schemaPath = path.join(__dirname, '..', 'database', 'schema.sql');
const schema = fs.readFileSync(schemaPath, 'utf8');
// Execute schema
await pool.query(schema);
console.log('Database migration completed successfully');
// Import legacy data if available
try {
const dataPath = path.join(__dirname, '..', 'data.json');
if (fs.existsSync(dataPath)) {
const legacyData = JSON.parse(fs.readFileSync(dataPath, 'utf8'));
// Import colors
const colors = new Set();
legacyData.forEach(item => {
if (item.boja) colors.add(item.boja);
});
for (const color of colors) {
const hex = legacyData.find(item => item.boja === color)?.bojaHex || '#000000';
await pool.query(
'INSERT INTO colors (name, hex) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET hex = $2',
[color, hex]
);
}
// Import filaments
for (const item of legacyData) {
await pool.query(
`INSERT INTO filaments (brand, tip, finish, boja, boja_hex, refill, vakum, otvoreno, kolicina, cena)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,
[
item.brand,
item.tip,
item.finish,
item.boja,
item.bojaHex,
item.refill,
item.vakum,
item.otvoreno,
item.kolicina || 1,
item.cena
]
);
}
console.log('Legacy data imported successfully');
}
} catch (error) {
console.error('Error importing legacy data:', error);
}
process.exit(0);
} catch (error) {
console.error('Migration failed:', error);
process.exit(1);
}
}
migrate();

1504
api/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

22
api/package.json Normal file
View File

@@ -0,0 +1,22 @@
{
"name": "filamenteka-api",
"version": "1.0.0",
"description": "API backend for Filamenteka",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"migrate": "node migrate.js"
},
"dependencies": {
"express": "^4.18.2",
"pg": "^8.11.3",
"cors": "^2.8.5",
"bcryptjs": "^2.4.3",
"jsonwebtoken": "^9.0.2",
"dotenv": "^16.3.1"
},
"devDependencies": {
"nodemon": "^3.0.2"
}
}

175
api/server.js Normal file
View File

@@ -0,0 +1,175 @@
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']
}));
app.use(express.json());
// 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.sendStatus(401);
}
jwt.verify(token, process.env.JWT_SECRET || 'your-secret-key', (err, user) => {
if (err) return res.sendStatus(403);
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 } = req.body;
try {
const result = await pool.query(
'INSERT INTO colors (name, hex) VALUES ($1, $2) RETURNING *',
[name, hex]
);
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 } = req.body;
try {
const result = await pool.query(
'UPDATE colors SET name = $1, hex = $2, updated_at = CURRENT_TIMESTAMP WHERE id = $3 RETURNING *',
[name, hex, 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
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 { brand, tip, finish, boja, boja_hex, refill, vakum, otvoreno, kolicina, cena } = req.body;
try {
const result = await pool.query(
`INSERT INTO filaments (brand, tip, finish, boja, boja_hex, refill, vakum, otvoreno, kolicina, cena)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING *`,
[brand, tip, finish, boja, boja_hex, refill, vakum, otvoreno, kolicina || 1, 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 { brand, tip, finish, boja, boja_hex, refill, vakum, otvoreno, kolicina, cena } = req.body;
try {
const result = await pool.query(
`UPDATE filaments
SET brand = $1, tip = $2, finish = $3, boja = $4, boja_hex = $5,
refill = $6, vakum = $7, otvoreno = $8, kolicina = $9, cena = $10,
updated_at = CURRENT_TIMESTAMP
WHERE id = $11 RETURNING *`,
[brand, tip, finish, boja, boja_hex, refill, vakum, otvoreno, kolicina || 1, cena, 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' });
}
});
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});

9
api/terraform.tfstate Normal file
View File

@@ -0,0 +1,9 @@
{
"version": 4,
"terraform_version": "1.5.7",
"serial": 1,
"lineage": "3a82dae6-d28d-d8bd-893b-3217b2dfad11",
"outputs": {},
"resources": [],
"check_results": null
}

18
api/vercel.json Normal file
View File

@@ -0,0 +1,18 @@
{
"version": 2,
"builds": [
{
"src": "server.js",
"use": "@vercel/node"
}
],
"routes": [
{
"src": "/(.*)",
"dest": "server.js"
}
],
"env": {
"NODE_TLS_REJECT_UNAUTHORIZED": "0"
}
}