Implement email verification lockout and missing phase 2 items
This commit is contained in:
+82
-15
@@ -12,6 +12,56 @@ const pgPool = new Pool({
|
||||
password: process.env.PGPASSWORD || 'postgres',
|
||||
database: process.env.PGDATABASE || 'ckqr',
|
||||
});
|
||||
// === BULK OPERATIONS ===
|
||||
|
||||
// POST /api/qr/bulk-delete
|
||||
router.post('/bulk-delete', verifyToken, async (req, res) => {
|
||||
const { ids } = req.body;
|
||||
if (!ids || !Array.isArray(ids) || ids.length === 0) {
|
||||
return res.status(400).json({ error: 'No IDs provided' });
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await pgPool.query(
|
||||
'DELETE FROM qr_codes WHERE id = ANY($1) AND user_id = $2 RETURNING id',
|
||||
[ids, req.user.id]
|
||||
);
|
||||
res.json({ success: true, deleted: result.rowCount });
|
||||
} catch (err) {
|
||||
console.error('Error in bulk delete:', err);
|
||||
res.status(500).json({ error: 'Server error during bulk delete' });
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/qr/bulk-move
|
||||
router.post('/bulk-move', verifyToken, async (req, res) => {
|
||||
const { ids, folderId } = req.body;
|
||||
if (!ids || !Array.isArray(ids) || ids.length === 0) {
|
||||
return res.status(400).json({ error: 'No IDs provided' });
|
||||
}
|
||||
|
||||
try {
|
||||
let targetFolder = folderId;
|
||||
if (targetFolder === 'default') targetFolder = null;
|
||||
|
||||
// Verify folder ownership if not default
|
||||
if (targetFolder) {
|
||||
const folderRes = await pgPool.query('SELECT id FROM folders WHERE id = $1 AND user_id = $2', [targetFolder, req.user.id]);
|
||||
if (folderRes.rows.length === 0) {
|
||||
return res.status(403).json({ error: 'Folder access denied' });
|
||||
}
|
||||
}
|
||||
|
||||
const result = await pgPool.query(
|
||||
'UPDATE qr_codes SET folder_id = $1 WHERE id = ANY($2) AND user_id = $3 RETURNING id',
|
||||
[targetFolder, ids, req.user.id]
|
||||
);
|
||||
res.json({ success: true, moved: result.rowCount });
|
||||
} catch (err) {
|
||||
console.error('Error in bulk move:', err);
|
||||
res.status(500).json({ error: 'Server error during bulk move' });
|
||||
}
|
||||
});
|
||||
|
||||
// Get all QRs for user
|
||||
router.get('/', verifyToken, async (req, res) => {
|
||||
@@ -57,7 +107,7 @@ router.get('/:id', verifyToken, async (req, res) => {
|
||||
|
||||
// Generate QR Code
|
||||
router.post('/generate', verifyToken, requireEmailVerification, async (req, res) => {
|
||||
const { name, type, dataType, destinationUrl, designData, customSlug, folderId } = req.body;
|
||||
const { name, type, dataType, destinationUrl, routingData, designData, customSlug, folderId, tags } = req.body;
|
||||
const userId = req.user.id;
|
||||
|
||||
if (!type || !dataType) {
|
||||
@@ -66,11 +116,11 @@ router.post('/generate', verifyToken, requireEmailVerification, async (req, res)
|
||||
|
||||
try {
|
||||
let shortUrlId = null;
|
||||
let finalQrContent = destinationUrl;
|
||||
let finalQrContent = destinationUrl; // Only valid if static and URL
|
||||
|
||||
// In static mode, the finalQrContent is whatever payload we send (handled mostly by frontend),
|
||||
// but if dynamic, we generate the shortUrlId.
|
||||
if (type === 'dynamic') {
|
||||
if (!destinationUrl) return res.status(400).json({ error: 'Destination URL is required for dynamic QR' });
|
||||
|
||||
// Allow custom slug or generate random
|
||||
shortUrlId = customSlug ? customSlug.trim() : shortid.generate();
|
||||
|
||||
@@ -84,16 +134,20 @@ router.post('/generate', verifyToken, requireEmailVerification, async (req, res)
|
||||
|
||||
// In production, this would use the real domain (e.g. https://ck-qr.com/l/)
|
||||
finalQrContent = `${process.env.FRONTEND_URL || 'http://localhost:4000'}/l/${shortUrlId}`;
|
||||
} else {
|
||||
// If static, destinationUrl is sent as the actual payload by the frontend for simple URLs,
|
||||
// but for complex types, frontend puts the raw payload in designData.data.
|
||||
// We just store destinationUrl for backward compatibility.
|
||||
}
|
||||
|
||||
const result = await pgPool.query(
|
||||
`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]
|
||||
`INSERT INTO qr_codes (user_id, name, type, data_type, destination_url, routing_data, short_url_id, design_data, folder_id, tags)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING *`,
|
||||
[userId, name || 'Untitled QR', type, dataType, destinationUrl || null, routingData || null, shortUrlId, designData || {}, folderId || null, tags || []]
|
||||
);
|
||||
|
||||
// Generate Image (Data URI for simple response, could be saved to S3 later)
|
||||
const qrImage = await QRCode.toDataURL(finalQrContent);
|
||||
const qrImage = await QRCode.toDataURL(finalQrContent || 'preview');
|
||||
|
||||
res.json({
|
||||
message: 'QR Code generated',
|
||||
@@ -108,7 +162,7 @@ router.post('/generate', verifyToken, requireEmailVerification, async (req, res)
|
||||
|
||||
// Update QR Code (Editable Link / Move Folder / Update Design)
|
||||
router.put('/:id', verifyToken, requireEmailVerification, async (req, res) => {
|
||||
const { name, destinationUrl, shortUrlId, designData, folderId } = req.body;
|
||||
const { name, dataType, destinationUrl, routingData, shortUrlId, designData, folderId, tags } = req.body;
|
||||
const qrId = req.params.id;
|
||||
const userId = req.user.id;
|
||||
|
||||
@@ -134,12 +188,25 @@ router.put('/:id', verifyToken, requireEmailVerification, async (req, res) => {
|
||||
const updated = await pgPool.query(
|
||||
`UPDATE qr_codes
|
||||
SET name = COALESCE($1, name),
|
||||
destination_url = COALESCE($2, destination_url),
|
||||
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]
|
||||
data_type = COALESCE($2, data_type),
|
||||
destination_url = $3,
|
||||
routing_data = COALESCE($4, routing_data),
|
||||
short_url_id = COALESCE($5, short_url_id),
|
||||
design_data = COALESCE($6, design_data),
|
||||
folder_id = $7,
|
||||
tags = $8
|
||||
WHERE id = $9 RETURNING *`,
|
||||
[
|
||||
name,
|
||||
dataType,
|
||||
destinationUrl !== undefined ? destinationUrl : qr.destination_url,
|
||||
routingData !== undefined ? routingData : qr.routing_data,
|
||||
newShortUrlId,
|
||||
designData || qr.design_data,
|
||||
folderId !== undefined ? (folderId === 'default' ? null : folderId) : qr.folder_id,
|
||||
tags !== undefined ? tags : qr.tags,
|
||||
qrId
|
||||
]
|
||||
);
|
||||
|
||||
res.json({ message: 'QR Code updated', qrRecord: updated.rows[0] });
|
||||
|
||||
Reference in New Issue
Block a user