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>

Files changed (2) hide show
  1. public/live-site-integration.js +17 -0
  2. 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.vetFirstName ||
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
- // Map exactly to: Date, Vet First, Vet Middle, Vet Last, Rank, Branch, Years, Era, Hometown, Deceased, Story, Recognition, Submitter Name, Relationship, Email, Phone, Display Permission, Origin
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 || '',