Spaces:
Running
Running
Ig0tU Claude Sonnet 4.6 commited on
Commit ·
4517ae6
1
Parent(s): 0fdb99c
feat: base64 photo upload to temp storage + fix phone validation
Browse files- Read file inputs as base64 via FileReader before JSON submit
- Server saves vetPhoto to public/uploads/, stores URL in Honor sheet
- Increased body-parser limit to 10mb for image payloads
- Added submitterPhone to validation identifier check
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- public/live-site-integration.js +17 -0
- server.js +24 -5
public/live-site-integration.js
CHANGED
|
@@ -65,11 +65,28 @@
|
|
| 65 |
formData[fieldName] = el.checked ? 'Yes' : 'No';
|
| 66 |
} else if (el.type === 'radio') {
|
| 67 |
if (el.checked) formData[fieldName] = value;
|
|
|
|
|
|
|
| 68 |
} else {
|
| 69 |
formData[fieldName] = value;
|
| 70 |
}
|
| 71 |
});
|
| 72 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
// Set fallbacks for strict checking on the backend if this was just a generic form
|
| 74 |
if (!formData.name && !formData.Name && !formData.contactName && !formData.vetFirstName && !formData.submitterName) {
|
| 75 |
formData._generic_name = "User";
|
|
|
|
| 65 |
formData[fieldName] = el.checked ? 'Yes' : 'No';
|
| 66 |
} else if (el.type === 'radio') {
|
| 67 |
if (el.checked) formData[fieldName] = value;
|
| 68 |
+
} else if (el.type === 'file') {
|
| 69 |
+
// handled below via FileReader
|
| 70 |
} else {
|
| 71 |
formData[fieldName] = value;
|
| 72 |
}
|
| 73 |
});
|
| 74 |
|
| 75 |
+
// Read file inputs as base64 DataURLs
|
| 76 |
+
const filePromises = [];
|
| 77 |
+
Array.from(form.elements).forEach(el => {
|
| 78 |
+
if (el.type !== 'file' || !el.files || !el.files.length) return;
|
| 79 |
+
const key = el.name || el.id;
|
| 80 |
+
if (!key) return;
|
| 81 |
+
filePromises.push(new Promise(resolve => {
|
| 82 |
+
const reader = new FileReader();
|
| 83 |
+
reader.onload = e => { formData[key] = e.target.result; resolve(); };
|
| 84 |
+
reader.onerror = () => resolve();
|
| 85 |
+
reader.readAsDataURL(el.files[0]);
|
| 86 |
+
}));
|
| 87 |
+
});
|
| 88 |
+
await Promise.all(filePromises);
|
| 89 |
+
|
| 90 |
// Set fallbacks for strict checking on the backend if this was just a generic form
|
| 91 |
if (!formData.name && !formData.Name && !formData.contactName && !formData.vetFirstName && !formData.submitterName) {
|
| 92 |
formData._generic_name = "User";
|
server.js
CHANGED
|
@@ -5,6 +5,7 @@ const cors = require('cors');
|
|
| 5 |
const bodyParser = require('body-parser');
|
| 6 |
const { google } = require('googleapis');
|
| 7 |
const fs = require('fs');
|
|
|
|
| 8 |
const { Server } = require("socket.io");
|
| 9 |
const nodemailer = require('nodemailer');
|
| 10 |
|
|
@@ -21,8 +22,8 @@ const port = process.env.PORT || 3000;
|
|
| 21 |
|
| 22 |
// Middleware
|
| 23 |
app.use(cors());
|
| 24 |
-
app.use(bodyParser.json());
|
| 25 |
-
app.use(bodyParser.urlencoded({ extended: true }));
|
| 26 |
|
| 27 |
// Basic Auth for UI Protection
|
| 28 |
app.use((req, res, next) => {
|
|
@@ -87,8 +88,8 @@ app.post('/api/submit', async (req, res) => {
|
|
| 87 |
const { name, email, message, phone, subject, origin } = req.body;
|
| 88 |
|
| 89 |
const hasIdentifier = name || email || phone ||
|
| 90 |
-
req.body.submitterEmail || req.body.submitterName || req.body.
|
| 91 |
-
req.body.contactName || req.body.groupName;
|
| 92 |
if (!hasIdentifier) {
|
| 93 |
return res.status(400).json({ error: 'Please provide at least a name, email, or phone number.' });
|
| 94 |
}
|
|
@@ -162,7 +163,24 @@ app.post('/api/submit', async (req, res) => {
|
|
| 162 |
];
|
| 163 |
} else if (lowerOrigin.includes('honor')) {
|
| 164 |
range = 'Honor';
|
| 165 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 166 |
rowData = [
|
| 167 |
TIMESTAMP,
|
| 168 |
req.body.vetFirstName || '',
|
|
@@ -176,6 +194,7 @@ app.post('/api/submit', async (req, res) => {
|
|
| 176 |
req.body.isDeceased || 'No',
|
| 177 |
req.body.vetStory || '',
|
| 178 |
req.body.specialRecognition || '',
|
|
|
|
| 179 |
req.body.submitterName || '',
|
| 180 |
req.body.relationship || '',
|
| 181 |
req.body.submitterEmail || '',
|
|
|
|
| 5 |
const bodyParser = require('body-parser');
|
| 6 |
const { google } = require('googleapis');
|
| 7 |
const fs = require('fs');
|
| 8 |
+
const path = require('path');
|
| 9 |
const { Server } = require("socket.io");
|
| 10 |
const nodemailer = require('nodemailer');
|
| 11 |
|
|
|
|
| 22 |
|
| 23 |
// Middleware
|
| 24 |
app.use(cors());
|
| 25 |
+
app.use(bodyParser.json({ limit: '10mb' }));
|
| 26 |
+
app.use(bodyParser.urlencoded({ extended: true, limit: '10mb' }));
|
| 27 |
|
| 28 |
// Basic Auth for UI Protection
|
| 29 |
app.use((req, res, next) => {
|
|
|
|
| 88 |
const { name, email, message, phone, subject, origin } = req.body;
|
| 89 |
|
| 90 |
const hasIdentifier = name || email || phone ||
|
| 91 |
+
req.body.submitterEmail || req.body.submitterName || req.body.submitterPhone ||
|
| 92 |
+
req.body.vetFirstName || req.body.contactName || req.body.groupName;
|
| 93 |
if (!hasIdentifier) {
|
| 94 |
return res.status(400).json({ error: 'Please provide at least a name, email, or phone number.' });
|
| 95 |
}
|
|
|
|
| 163 |
];
|
| 164 |
} else if (lowerOrigin.includes('honor')) {
|
| 165 |
range = 'Honor';
|
| 166 |
+
|
| 167 |
+
let vetPhotoUrl = '';
|
| 168 |
+
const photoData = req.body.vetPhoto;
|
| 169 |
+
if (photoData && photoData.startsWith('data:image')) {
|
| 170 |
+
try {
|
| 171 |
+
const matches = photoData.match(/^data:image\/(\w+);base64,(.+)$/s);
|
| 172 |
+
if (matches) {
|
| 173 |
+
const uploadDir = path.join(__dirname, 'public', 'uploads');
|
| 174 |
+
if (!fs.existsSync(uploadDir)) fs.mkdirSync(uploadDir, { recursive: true });
|
| 175 |
+
const fileName = `${Date.now()}.${matches[1]}`;
|
| 176 |
+
fs.writeFileSync(path.join(uploadDir, fileName), Buffer.from(matches[2], 'base64'));
|
| 177 |
+
vetPhotoUrl = `${req.protocol}://${req.get('host')}/uploads/${fileName}`;
|
| 178 |
+
}
|
| 179 |
+
} catch (photoErr) {
|
| 180 |
+
console.error('Failed to save photo:', photoErr);
|
| 181 |
+
}
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
rowData = [
|
| 185 |
TIMESTAMP,
|
| 186 |
req.body.vetFirstName || '',
|
|
|
|
| 194 |
req.body.isDeceased || 'No',
|
| 195 |
req.body.vetStory || '',
|
| 196 |
req.body.specialRecognition || '',
|
| 197 |
+
vetPhotoUrl,
|
| 198 |
req.body.submitterName || '',
|
| 199 |
req.body.relationship || '',
|
| 200 |
req.body.submitterEmail || '',
|