feat: Add folder management, visual thumbnails in dashboard, and full-page editor
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Pool } = require('pg');
|
||||
const { verifyToken } = require('../middleware/auth');
|
||||
|
||||
const pgPool = new Pool({
|
||||
host: process.env.PGHOST || 'postgres',
|
||||
port: process.env.PGPORT || 5432,
|
||||
user: process.env.PGUSER || 'postgres',
|
||||
password: process.env.PGPASSWORD || 'postgres',
|
||||
database: process.env.PGDATABASE || 'ckqr',
|
||||
});
|
||||
|
||||
// Get all folders for user
|
||||
router.get('/', verifyToken, async (req, res) => {
|
||||
try {
|
||||
const result = await pgPool.query(
|
||||
'SELECT * FROM folders WHERE user_id = $1 ORDER BY created_at DESC',
|
||||
[req.user.id]
|
||||
);
|
||||
res.json({ folders: result.rows });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Server error fetching folders' });
|
||||
}
|
||||
});
|
||||
|
||||
// Create a new folder
|
||||
router.post('/', verifyToken, async (req, res) => {
|
||||
const { name } = req.body;
|
||||
if (!name) return res.status(400).json({ error: 'Folder name is required' });
|
||||
|
||||
try {
|
||||
const result = await pgPool.query(
|
||||
'INSERT INTO folders (user_id, name) VALUES ($1, $2) RETURNING *',
|
||||
[req.user.id, name]
|
||||
);
|
||||
res.json({ message: 'Folder created', folder: result.rows[0] });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Server error creating folder' });
|
||||
}
|
||||
});
|
||||
|
||||
// Delete a folder
|
||||
router.delete('/:id', verifyToken, async (req, res) => {
|
||||
try {
|
||||
const result = await pgPool.query(
|
||||
'DELETE FROM folders WHERE id = $1 AND user_id = $2 RETURNING id',
|
||||
[req.params.id, req.user.id]
|
||||
);
|
||||
if (result.rows.length === 0) return res.status(404).json({ error: 'Folder not found or unauthorized' });
|
||||
res.json({ message: 'Folder deleted' });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Error deleting folder' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
+41
-15
@@ -16,14 +16,23 @@ const pgPool = new Pool({
|
||||
// Get all QRs for user
|
||||
router.get('/', verifyToken, async (req, res) => {
|
||||
try {
|
||||
const result = await pgPool.query(
|
||||
`SELECT q.*,
|
||||
const { folderId } = req.query;
|
||||
let queryStr = `SELECT q.*,
|
||||
COALESCE((SELECT COUNT(*) FROM qr_scans s WHERE s.qr_code_id = q.id), 0) as scans
|
||||
FROM qr_codes q
|
||||
WHERE q.user_id = $1
|
||||
ORDER BY q.created_at DESC`,
|
||||
[req.user.id]
|
||||
);
|
||||
WHERE q.user_id = $1`;
|
||||
let params = [req.user.id];
|
||||
|
||||
if (folderId === 'default') {
|
||||
queryStr += ` AND q.folder_id IS NULL`;
|
||||
} else if (folderId) {
|
||||
queryStr += ` AND q.folder_id = $2`;
|
||||
params.push(folderId);
|
||||
}
|
||||
|
||||
queryStr += ` ORDER BY q.created_at DESC`;
|
||||
|
||||
const result = await pgPool.query(queryStr, params);
|
||||
res.json({ qr_codes: result.rows });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@@ -31,9 +40,24 @@ router.get('/', verifyToken, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Get single QR code by ID
|
||||
router.get('/:id', verifyToken, async (req, res) => {
|
||||
try {
|
||||
const result = await pgPool.query(
|
||||
`SELECT * FROM qr_codes WHERE id = $1 AND user_id = $2`,
|
||||
[req.params.id, req.user.id]
|
||||
);
|
||||
if (result.rows.length === 0) return res.status(404).json({ error: 'QR Code not found' });
|
||||
res.json({ qr_code: result.rows[0] });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Generate QR Code
|
||||
router.post('/generate', verifyToken, async (req, res) => {
|
||||
const { name, type, dataType, destinationUrl, designData, customSlug } = req.body;
|
||||
const { name, type, dataType, destinationUrl, designData, customSlug, folderId } = req.body;
|
||||
const userId = req.user.id;
|
||||
|
||||
if (!type || !dataType) {
|
||||
@@ -63,9 +87,9 @@ router.post('/generate', verifyToken, async (req, res) => {
|
||||
}
|
||||
|
||||
const result = await pgPool.query(
|
||||
`INSERT INTO qr_codes (user_id, name, type, data_type, destination_url, short_url_id, design_data)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *`,
|
||||
[userId, name || 'Untitled QR', type, dataType, destinationUrl, shortUrlId, designData || {}]
|
||||
`INSERT INTO qr_codes (user_id, name, type, data_type, destination_url, short_url_id, design_data, folder_id)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *`,
|
||||
[userId, name || 'Untitled QR', type, dataType, destinationUrl, shortUrlId, designData || {}, folderId || null]
|
||||
);
|
||||
|
||||
// Generate Image (Data URI for simple response, could be saved to S3 later)
|
||||
@@ -82,9 +106,9 @@ router.post('/generate', verifyToken, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Update QR Code (Editable Link)
|
||||
// Update QR Code (Editable Link / Move Folder / Update Design)
|
||||
router.put('/:id', verifyToken, async (req, res) => {
|
||||
const { name, destinationUrl, shortUrlId } = req.body;
|
||||
const { name, destinationUrl, shortUrlId, designData, folderId } = req.body;
|
||||
const qrId = req.params.id;
|
||||
const userId = req.user.id;
|
||||
|
||||
@@ -111,9 +135,11 @@ router.put('/:id', verifyToken, async (req, res) => {
|
||||
`UPDATE qr_codes
|
||||
SET name = COALESCE($1, name),
|
||||
destination_url = COALESCE($2, destination_url),
|
||||
short_url_id = COALESCE($3, short_url_id)
|
||||
WHERE id = $4 RETURNING *`,
|
||||
[name, destinationUrl, newShortUrlId, qrId]
|
||||
short_url_id = COALESCE($3, short_url_id),
|
||||
design_data = COALESCE($4, design_data),
|
||||
folder_id = $5
|
||||
WHERE id = $6 RETURNING *`,
|
||||
[name, destinationUrl, newShortUrlId, designData || qr.design_data, folderId !== undefined ? (folderId === 'default' ? null : folderId) : qr.folder_id, qrId]
|
||||
);
|
||||
|
||||
res.json({ message: 'QR Code updated', qrRecord: updated.rows[0] });
|
||||
|
||||
Reference in New Issue
Block a user