@@ -17,6 +17,7 @@ import {
1717 logInstitutionSubmissionFailure ,
1818 logNewInstitution ,
1919} from "@/lib/integrations/telegram" ;
20+ import { decodeQrFromBuffer } from "@/lib/qr-decode" ;
2021import { isToyyibpay } from "@/lib/qr-utils" ;
2122import { getUserById } from "@/lib/queries/users" ;
2223import { slugify } from "@/lib/utils" ;
@@ -179,31 +180,6 @@ export async function submitInstitution(
179180 } ;
180181 }
181182
182- // --- Duplicate QR content check
183- const qrContentRaw = rawFromForm . qrContent ;
184- if (
185- qrContentRaw &&
186- typeof qrContentRaw === "string" &&
187- qrContentRaw . trim ( ) !== ""
188- ) {
189- const [ existingQr ] = await db
190- . select ( { id : institutions . id } )
191- . from ( institutions )
192- . where ( eq ( institutions . qrContent , qrContentRaw . trim ( ) ) )
193- . limit ( 1 ) ;
194-
195- if ( existingQr ) {
196- return {
197- status : "error" ,
198- errors : {
199- general : [
200- "QR code ini telah pun wujud dalam sistem. Sila semak semula." ,
201- ] ,
202- } ,
203- } ;
204- }
205- }
206-
207183 const socialMedia = {
208184 facebook : formData . get ( "facebook" ) || undefined ,
209185 instagram : formData . get ( "instagram" ) || undefined ,
@@ -261,10 +237,10 @@ export async function submitInstitution(
261237
262238 console . log ( "Validation passed, proceeding with submission" ) ;
263239
264- // --- Handle QR image (optional)
240+ // --- Handle QR image upload + server-side QR extraction
265241 let qrImageUrl : string | undefined ;
266- // We get qrContent from the form data now, no more backend processing
267- const qrContent = formData . get ( "qrContent" ) as string | null ;
242+ let qrContent : string | null = null ;
243+ let qrBuffer : Buffer | undefined ;
268244
269245 try {
270246 if ( qrImageFile && qrImageFile . size > 0 ) {
@@ -290,65 +266,21 @@ export async function submitInstitution(
290266 }
291267
292268 const arrayBuffer = await qrImageFile . arrayBuffer ( ) ;
293- const buffer = Buffer . from ( arrayBuffer ) ;
269+ qrBuffer = Buffer . from ( arrayBuffer ) ;
294270
295- // Upload to R2
296- try {
297- qrImageUrl = await r2Storage . uploadFile ( buffer , qrImageFile . name ) ;
298- } catch ( uploadError ) {
299- console . error ( "Failed to upload QR image to R2:" , uploadError ) ;
300-
301- // Log to Telegram with error details
302- try {
303- await logInstitutionSubmissionFailure ( {
304- error :
305- uploadError instanceof Error
306- ? uploadError . message
307- : String ( uploadError ) ,
308- institutionName : parsed . data . name ,
309- category : parsed . data . category ,
310- state : parsed . data . state ,
311- city : parsed . data . city ,
312- contributorName : user ?. name || undefined ,
313- contributorEmail : user ?. email ,
314- errorType : "R2 image upload failure" ,
315- } ) ;
316- } catch ( telegramError ) {
317- console . error (
318- "Failed to log upload failure to Telegram:" ,
319- telegramError ,
320- ) ;
321- }
271+ // Server-side QR extraction (more reliable than browser-side canvas decode)
272+ qrContent = await decodeQrFromBuffer ( qrBuffer ) ;
322273
323- return {
324- status : "error" ,
325- errors : {
326- qrImage : [ "Gagal memuat naik imej QR. Sila cuba lagi." ] ,
327- } ,
328- } ;
274+ // Fall back to client-provided value if server extraction fails
275+ if ( ! qrContent ) {
276+ const clientQrContent = formData . get ( "qrContent" ) as string | null ;
277+ if ( clientQrContent ?. trim ( ) ) {
278+ qrContent = clientQrContent . trim ( ) ;
279+ }
329280 }
330281 }
331282 } catch ( error ) {
332- console . error ( "Error handling QR image upload:" , error ) ;
333-
334- // Log to Telegram with error details
335- try {
336- await logInstitutionSubmissionFailure ( {
337- error : error instanceof Error ? error . message : String ( error ) ,
338- institutionName : parsed ?. data ?. name ,
339- category : parsed ?. data ?. category ,
340- state : parsed ?. data ?. state ,
341- city : parsed ?. data ?. city ,
342- contributorName : user ?. name || undefined ,
343- contributorEmail : user ?. email ,
344- errorType : "QR image processing failure" ,
345- } ) ;
346- } catch ( telegramError ) {
347- console . error (
348- "Failed to log QR processing failure to Telegram:" ,
349- telegramError ,
350- ) ;
351- }
283+ console . error ( "Error handling QR image processing:" , error ) ;
352284
353285 return {
354286 status : "error" ,
@@ -358,6 +290,64 @@ export async function submitInstitution(
358290 } ;
359291 }
360292
293+ // --- Duplicate QR check (before R2 upload to avoid orphaned objects)
294+ if ( qrContent ) {
295+ const [ existingQr ] = await db
296+ . select ( { id : institutions . id } )
297+ . from ( institutions )
298+ . where ( eq ( institutions . qrContent , qrContent ) )
299+ . limit ( 1 ) ;
300+
301+ if ( existingQr ) {
302+ return {
303+ status : "error" ,
304+ errors : {
305+ general : [
306+ "QR code ini telah pun wujud dalam sistem. Sila semak semula." ,
307+ ] ,
308+ } ,
309+ } ;
310+ }
311+ }
312+
313+ // --- Upload to R2 (only after duplicate check passes)
314+ if ( qrBuffer && qrImageFile ) {
315+ try {
316+ qrImageUrl = await r2Storage . uploadFile ( qrBuffer , qrImageFile . name ) ;
317+ } catch ( uploadError ) {
318+ console . error ( "Failed to upload QR image to R2:" , uploadError ) ;
319+
320+ // Log to Telegram with error details
321+ try {
322+ await logInstitutionSubmissionFailure ( {
323+ error :
324+ uploadError instanceof Error
325+ ? uploadError . message
326+ : String ( uploadError ) ,
327+ institutionName : parsed . data . name ,
328+ category : parsed . data . category ,
329+ state : parsed . data . state ,
330+ city : parsed . data . city ,
331+ contributorName : user ?. name || undefined ,
332+ contributorEmail : user ?. email ,
333+ errorType : "R2 image upload failure" ,
334+ } ) ;
335+ } catch ( telegramError ) {
336+ console . error (
337+ "Failed to log upload failure to Telegram:" ,
338+ telegramError ,
339+ ) ;
340+ }
341+
342+ return {
343+ status : "error" ,
344+ errors : {
345+ qrImage : [ "Gagal memuat naik imej QR. Sila cuba lagi." ] ,
346+ } ,
347+ } ;
348+ }
349+ }
350+
361351 // Geocode if coords not provided
362352 if ( ! coords ) {
363353 const geocoded = await geocodeInstitutionWithFallback (
@@ -398,6 +388,7 @@ export async function submitInstitution(
398388 state : parsed . data . state as ( typeof states ) [ number ] ,
399389 coords : coords , // Coords may be updated by geocoding
400390 qrImage : qrImageUrl ,
391+ qrContent : qrContent || null ,
401392 contributorId : contributorId , // Include the contributor ID
402393 status : "pending" , // Always pending for new submissions
403394 supportedPayment : [ isToyyibpay ( qrContent ) ? "toyyibpay" : "duitnow" ] ,
0 commit comments