feat: initialize Svelte frontend with Vite and TypeScript
- Added package.json for project configuration and dependencies. - Included images for the application (teh-jokur.png and vite.svg). - Created main application structure with App.svelte, CounterCard.svelte, and AddCounterCard.svelte components. - Implemented functionality for adding, editing, incrementing, and decrementing counters. - Added clickOutside utility for handling outside clicks in editing mode. - Configured TypeScript with appropriate tsconfig files for app and node. - Set up Vite configuration for building the application. - Added global styles in app.css for consistent UI design.
This commit is contained in:
108
backend/index.js
Normal file
108
backend/index.js
Normal file
@@ -0,0 +1,108 @@
|
||||
const express = require('express');
|
||||
const sqlite3 = require('sqlite3').verbose();
|
||||
const cors = require('cors');
|
||||
const path = require('path');
|
||||
const multer = require('multer');
|
||||
const fs = require('fs');
|
||||
|
||||
const app = express();
|
||||
const dbPath = path.join(__dirname, 'db.sqlite');
|
||||
const db = new sqlite3.Database(dbPath);
|
||||
const upload = multer({ dest: path.join(__dirname, 'uploads/') });
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
|
||||
|
||||
// Initialize database and ensure counters table exists
|
||||
db.serialize(() => {
|
||||
db.run(`CREATE TABLE IF NOT EXISTS counters (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
value INTEGER NOT NULL DEFAULT 0,
|
||||
name TEXT NOT NULL DEFAULT 'Counter',
|
||||
image TEXT
|
||||
)`);
|
||||
});
|
||||
|
||||
// Get all counters
|
||||
app.get('/api/counters', (req, res) => {
|
||||
db.all('SELECT * FROM counters', (err, rows) => {
|
||||
if (err) return res.status(500).json({ error: err.message });
|
||||
// Ensure id and value are numbers
|
||||
const counters = rows.map(row => ({
|
||||
...row,
|
||||
id: Number(row.id),
|
||||
value: Number(row.value)
|
||||
}));
|
||||
res.json(counters);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Create a new counter with image
|
||||
app.post('/api/counters', upload.single('image'), (req, res) => {
|
||||
const { name = 'Counter', value = 0 } = req.body;
|
||||
const image = req.file ? `/uploads/${req.file.filename}` : null;
|
||||
db.run('INSERT INTO counters (name, value, image) VALUES (?, ?, ?)', [name, value, image], function (err) {
|
||||
if (err) return res.status(500).json({ error: err.message });
|
||||
res.json({ id: Number(this.lastID), name, value: Number(value), image });
|
||||
});
|
||||
});
|
||||
|
||||
// Update a counter (now supports image upload)
|
||||
app.put('/api/counters/:id', upload.single('image'), (req, res) => {
|
||||
const { name, value } = req.body;
|
||||
const id = req.params.id;
|
||||
let image = null;
|
||||
|
||||
// If a new image is uploaded, get its path
|
||||
if (req.file) {
|
||||
image = `/uploads/${req.file.filename}`;
|
||||
// Optionally, delete the old image file
|
||||
db.get('SELECT image FROM counters WHERE id = ?', [id], (err, row) => {
|
||||
if (row && row.image) {
|
||||
const oldImagePath = path.join(__dirname, '..', row.image);
|
||||
fs.unlink(oldImagePath, () => { }); // Ignore errors
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Build dynamic SQL and params
|
||||
let fields = [];
|
||||
let params = [];
|
||||
if (name !== undefined) { fields.push('name = ?'); params.push(name); }
|
||||
if (value !== undefined) { fields.push('value = ?'); params.push(value); }
|
||||
if (image !== null) { fields.push('image = ?'); params.push(image); }
|
||||
if (fields.length === 0) return res.status(400).json({ error: 'No valid fields to update.' });
|
||||
|
||||
params.push(id);
|
||||
const sql = `UPDATE counters SET ${fields.join(', ')} WHERE id = ?`;
|
||||
|
||||
db.run(sql, params, function (err) {
|
||||
if (err) return res.status(500).json({ error: err.message });
|
||||
res.json({ updated: this.changes });
|
||||
});
|
||||
});
|
||||
|
||||
// Delete a counter
|
||||
app.delete('/api/counters/:id', (req, res) => {
|
||||
db.run('DELETE FROM counters WHERE id = ?', [req.params.id], function (err) {
|
||||
if (err) return res.status(500).json({ error: err.message });
|
||||
res.json({ deleted: this.changes });
|
||||
});
|
||||
});
|
||||
|
||||
// Serve static frontend files
|
||||
const clientBuildPath = path.join(__dirname, '..', 'frontend', 'dist');
|
||||
app.use(express.static(clientBuildPath));
|
||||
|
||||
// For SPA: serve index.html for any unknown route (after API and uploads)
|
||||
// app.get('*', (req, res) => {
|
||||
// if (req.path.startsWith('/api') || req.path.startsWith('/uploads')) return res.status(404).end();
|
||||
// res.sendFile(path.join(clientBuildPath, 'index.html'));
|
||||
// });
|
||||
|
||||
const PORT = 3000;
|
||||
app.listen(PORT, () => {
|
||||
console.log(`API server running on http://localhost:${PORT}`);
|
||||
});
|
||||
Reference in New Issue
Block a user