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:
2025-11-10 23:52:12 +01:00
commit 3d9f7d2052
23 changed files with 4658 additions and 0 deletions

108
backend/index.js Normal file
View 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}`);
});

2353
backend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

18
backend/package.json Normal file
View File

@@ -0,0 +1,18 @@
{
"name": "server",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"cors": "^2.8.5",
"express": "^5.1.0",
"multer": "^2.0.2",
"sqlite3": "^5.1.7"
}
}