11import { FastifyPluginAsync } from "fastify" ;
22import { z } from "zod" ;
33import {
4+ ConditionalCheckFailedException ,
45 QueryCommand ,
56 ScanCommand ,
67 UpdateItemCommand ,
@@ -300,14 +301,17 @@ const ticketsPlugin: FastifyPluginAsync = async (fastify, _options) => {
300301 stripe_pi : { S : ticketId } ,
301302 } ,
302303 UpdateExpression : "SET fulfilled = :true_val" ,
303- ConditionExpression : "#email = :email_val" ,
304+ ConditionExpression :
305+ "#email = :email_val AND (attribute_not_exists(fulfilled) OR fulfilled = :false_val) AND (attribute_not_exists(refunded) OR refunded = :false_val)" ,
304306 ExpressionAttributeNames : {
305307 "#email" : "email" ,
306308 } ,
307309 ExpressionAttributeValues : {
308310 ":true_val" : { BOOL : true } ,
311+ ":false_val" : { BOOL : false } ,
309312 ":email_val" : { S : request . body . email } ,
310313 } ,
314+ ReturnValuesOnConditionCheckFailure : "ALL_OLD" ,
311315 ReturnValues : "ALL_OLD" ,
312316 } ) ;
313317 break ;
@@ -319,12 +323,16 @@ const ticketsPlugin: FastifyPluginAsync = async (fastify, _options) => {
319323 ticket_id : { S : ticketId } ,
320324 } ,
321325 UpdateExpression : "SET #used = :trueValue" ,
326+ ConditionExpression :
327+ "(attribute_not_exists(#used) OR #used = :falseValue) AND (attribute_not_exists(refunded) OR refunded = :falseValue)" ,
322328 ExpressionAttributeNames : {
323329 "#used" : "used" ,
324330 } ,
325331 ExpressionAttributeValues : {
326332 ":trueValue" : { BOOL : true } ,
333+ ":falseValue" : { BOOL : false } ,
327334 } ,
335+ ReturnValuesOnConditionCheckFailure : "ALL_OLD" ,
328336 ReturnValues : "ALL_OLD" ,
329337 } ) ;
330338 break ;
@@ -342,16 +350,6 @@ const ticketsPlugin: FastifyPluginAsync = async (fastify, _options) => {
342350 } ) ;
343351 }
344352 const attributes = unmarshall ( ticketEntry . Attributes ) ;
345- if ( attributes [ "refunded" ] ) {
346- throw new TicketNotValidError ( {
347- message : "Ticket was already refunded." ,
348- } ) ;
349- }
350- if ( attributes [ "used" ] || attributes [ "fulfilled" ] ) {
351- throw new TicketNotValidError ( {
352- message : "Ticket has already been used." ,
353- } ) ;
354- }
355353 if ( request . body . type === "ticket" ) {
356354 const rawData = attributes [ "ticketholder_netid" ] ;
357355 const isEmail = validateEmail ( attributes [ "ticketholder_netid" ] ) ;
@@ -376,65 +374,41 @@ const ticketsPlugin: FastifyPluginAsync = async (fastify, _options) => {
376374 if ( e instanceof BaseError ) {
377375 throw e ;
378376 }
379- if ( e . name === "ConditionalCheckFailedException" ) {
377+ if ( e instanceof ConditionalCheckFailedException ) {
378+ if ( e . Item ) {
379+ const unmarshalled = unmarshall ( e . Item ) ;
380+ if ( unmarshalled [ "fulfilled" ] || unmarshalled [ "used" ] ) {
381+ throw new TicketNotValidError ( {
382+ message : "Ticket has already been used." ,
383+ } ) ;
384+ }
385+ if ( unmarshalled [ "refunded" ] ) {
386+ throw new TicketNotValidError ( {
387+ message : "Ticket was already refunded." ,
388+ } ) ;
389+ }
390+ }
380391 throw new TicketNotFoundError ( {
381- message : "Ticket does not exist" ,
392+ message : "Ticket does not exist. " ,
382393 } ) ;
383394 }
384395 throw new DatabaseFetchError ( {
385396 message : "Could not set ticket to used - database operation failed" ,
386397 } ) ;
387398 }
388- const response = {
399+ reply . send ( {
389400 valid : true ,
390401 type : request . body . type ,
391402 ticketId,
392403 purchaserData,
393- } ;
394- switch ( request . body . type ) {
395- case "merch" :
396- ticketId = request . body . stripePi ;
397- command = new UpdateItemCommand ( {
398- TableName : genericConfig . MerchStorePurchasesTableName ,
399- Key : {
400- stripe_pi : { S : ticketId } ,
401- } ,
402- UpdateExpression :
403- "SET scannerEmail = :scanner_email, scanISOTimestamp = :scan_time" ,
404- ConditionExpression : "email = :email_val" ,
405- ExpressionAttributeValues : {
406- ":scanner_email" : { S : request . username } ,
407- ":scan_time" : { S : new Date ( ) . toISOString ( ) } ,
408- ":email_val" : { S : request . body . email } ,
409- } ,
410- } ) ;
411- break ;
412-
413- case "ticket" :
414- ticketId = request . body . ticketId ;
415- command = new UpdateItemCommand ( {
416- TableName : genericConfig . TicketPurchasesTableName ,
417- Key : {
418- ticket_id : { S : ticketId } ,
419- } ,
420- UpdateExpression :
421- "SET scannerEmail = :scanner_email, scanISOTimestamp = :scan_time" ,
422- ExpressionAttributeValues : {
423- ":scanner_email" : { S : request . username } ,
424- ":scan_time" : { S : new Date ( ) . toISOString ( ) } ,
425- } ,
426- } ) ;
427- break ;
428-
429- default :
430- throw new ValidationError ( {
431- message : `Unknown verification type!` ,
432- } ) ;
433- }
434- await fastify . dynamoClient . send ( command ) ;
435- reply . send ( response ) ;
404+ } ) ;
436405 request . log . info (
437- { type : "audit" , actor : request . username , target : ticketId } ,
406+ {
407+ type : "audit" ,
408+ module : "tickets" ,
409+ actor : request . username ,
410+ target : ticketId ,
411+ } ,
438412 `checked in ticket of type "${ request . body . type } " ${ request . body . type === "merch" ? `purchased by email ${ request . body . email } .` : "." } ` ,
439413 ) ;
440414 } ,
0 commit comments