const QRCode = require('qrcode'); const axios = require('axios'); const FormData = require('form-data'); const winston = require('winston'); // Configure logger const logger = winston.createLogger({ level: process.env.NODE_ENV === 'production' ? 'info' : 'debug', format: winston.format.combine( winston.format.timestamp(), winston.format.json() ), transports: [ new winston.transports.Console({ format: winston.format.simple() }) ] }); /** * Generate QR code as PNG buffer * @param {string} text - Text/URL to encode * @param {Object} options - QR code options * @returns {Promise} PNG buffer */ async function generateQRCode(text, options = {}) { const defaultOptions = { type: 'png', width: 256, margin: 1, color: { dark: '#000000', light: '#FFFFFF' }, errorCorrectionLevel: 'M' }; const qrOptions = { ...defaultOptions, ...options }; try { const buffer = await QRCode.toBuffer(text, qrOptions); return buffer; } catch (error) { logger.error('Failed to generate QR code:', error); throw new Error('Failed to generate QR code'); } } /** * Upload QR code to NocoDB storage * @param {Buffer} buffer - PNG buffer * @param {string} filename - Filename for the upload * @param {Object} config - NocoDB configuration * @returns {Promise} Upload response */ async function uploadQRCodeToNocoDB(buffer, filename, config) { const formData = new FormData(); formData.append('file', buffer, { filename: filename, contentType: 'image/png' }); try { // Use the base URL without /api/v1 for v2 endpoints const baseUrl = config.apiUrl.replace('/api/v1', ''); const uploadUrl = `${baseUrl}/api/v2/storage/upload`; logger.info(`Uploading QR code to: ${uploadUrl}`); const response = await axios({ url: uploadUrl, method: 'post', data: formData, headers: { ...formData.getHeaders(), 'xc-token': config.apiToken }, params: { path: 'qrcodes' } }); logger.info('QR code upload successful:', response.data); return response.data; } catch (error) { logger.error('Failed to upload QR code to NocoDB:', error.response?.data || error.message); throw new Error('Failed to upload QR code'); } } /** * Generate and upload QR code * @param {string} url - URL to encode * @param {string} label - Label for the QR code * @param {Object} config - NocoDB configuration * @returns {Promise} Upload result */ async function generateAndUploadQRCode(url, label, config) { if (!url) { return null; } try { // Generate QR code const buffer = await generateQRCode(url); // Create filename const timestamp = Date.now(); const safeLabel = label.replace(/[^a-z0-9]/gi, '_').toLowerCase(); const filename = `qr_${safeLabel}_${timestamp}.png`; // Upload to NocoDB const uploadResult = await uploadQRCodeToNocoDB(buffer, filename, config); return uploadResult; } catch (error) { logger.error('Failed to generate and upload QR code:', error); throw error; } } /** * Delete QR code from NocoDB storage * @param {string} fileUrl - File URL to delete * @param {Object} config - NocoDB configuration * @returns {Promise} Success status */ async function deleteQRCodeFromNocoDB(fileUrl, config) { if (!fileUrl) { return true; } try { // Extract file path from URL const urlParts = fileUrl.split('/'); const filePath = urlParts.slice(-2).join('/'); await axios({ url: `${config.apiUrl}/api/v2/storage/upload`, method: 'delete', headers: { 'xc-token': config.apiToken }, params: { path: filePath } }); return true; } catch (error) { logger.error('Failed to delete QR code from NocoDB:', error); // Don't throw error for deletion failures return false; } } module.exports = { generateQRCode, uploadQRCodeToNocoDB, generateAndUploadQRCode, deleteQRCodeFromNocoDB };