Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,8 @@ public function __construct
#[OA\Get(
path: '/api/v1/summits/{id}/attendees/me',
operationId: 'getCurrentAttendee',
summary: 'Get current user attendee profile',
description: 'Returns the attendee profile for the currently authenticated user in the specified summit',
summary: 'Get current user attendee profile for a particular summit',
description: 'Returns the attendee profile for the currently authenticated user in the specified summit (regenerates the Badge QR code of the active tickets when tickets.badge is expanded)',
tags: ['Summit Attendees'],
security: [
[
Expand Down Expand Up @@ -184,8 +184,15 @@ public function getOwnAttendee($summit_id)
$type = CheckAttendeeStrategyFactory::Me;
$attendee = CheckAttendeeStrategyFactory::build($type, $this->resource_server_context)->check('me', $summit);
if (is_null($attendee)) return $this->error404();

$expand = SerializerUtils::getExpand();
$expand_relations = array_map('trim', explode(',', $expand));
if (in_array('tickets.badge', $expand_relations, true)) {
$this->attendee_service->regenerateAttendeeBadgesQRCodes($attendee);
}

return $this->ok(SerializerRegistry::getInstance()->getSerializer($attendee)->serialize(
SerializerUtils::getExpand(),
$expand,
SerializerUtils::getFields(),
SerializerUtils::getRelations()
));
Expand Down
48 changes: 47 additions & 1 deletion app/Services/Model/AttendeeService.php
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,15 @@ public function reassignAttendeeTicketByMember(Summit $summit, SummitAttendee $a

$ticket->generateQRCode();
$ticket->generateHash();

if ($ticket->hasBadge()) {
try {
$ticket->getBadge()->generateQRCode();
} catch (\Exception $ex) {
Log::error($ex);
}
}

if ($summit->isRegistrationSendTicketEmailAutomatically())
$new_owner->sendInvitationEmail($ticket);

Expand Down Expand Up @@ -535,6 +544,13 @@ public function reassignAttendeeTicket(Summit $summit, SummitAttendee $attendee,
$ticket->generateHash();
$new_owner->updateStatus();

if ($ticket->hasBadge()) {
try {
$ticket->getBadge()->generateQRCode();
} catch (\Exception $ex) {
Log::error($ex);
}
}

if ($summit->isRegistrationSendTicketEmailAutomatically()) {
Log::debug
Expand All @@ -554,6 +570,36 @@ public function reassignAttendeeTicket(Summit $summit, SummitAttendee $attendee,

}

/**
* @inheritDoc
*/
public function regenerateAttendeeBadgesQRCodes(SummitAttendee $attendee): void
{
Log::debug(sprintf("AttendeeService::regenerateAttendeeBadgesQRCodes attendee %s", $attendee->getId()));
$badge_ids_to_generate = [];
foreach ($attendee->getTickets() as $ticket) {
if (!$ticket->isActive()) continue;
if (!$ticket->hasBadge()) continue;
$badge = $ticket->getBadge();
$badge_ids_to_generate[] = $badge->getId();
}

if (empty($badge_ids_to_generate)) return;

foreach ($badge_ids_to_generate as $badge_id) {
$this->tx_service->transaction(function () use ($attendee, $badge_id) {
$badge = $this->badge_repository->getByIdExclusiveLock($badge_id);
if (!$badge instanceof SummitAttendeeBadge) return;
Log::debug(sprintf("AttendeeService::regenerateAttendeeBadgesQRCodes attendee %s badge %s", $attendee->getId(), $badge_id));
try {
$badge->generateQRCode();
Comment thread
coderabbitai[bot] marked this conversation as resolved.
} catch (\Exception $ex) {
Log::error($ex);
}
});
}
}

/**
* @inheritDoc
*/
Expand Down Expand Up @@ -1086,4 +1132,4 @@ public function processAttendeeCheckStatusUpdate(int $attendee_id):void{
$feed->unCheckAttendee($external_id);
});
}
}
}
10 changes: 9 additions & 1 deletion app/Services/Model/IAttendeeService.php
Original file line number Diff line number Diff line change
Expand Up @@ -152,4 +152,12 @@ public function deleteAttendeeNote(Summit $summit, int $attendee_id, int $note_i
* @throws \Exception
*/
public function processAttendeeCheckStatusUpdate(int $attendee_id):void;
}

/**
* Generates and persists the badge qr_code for any of the attendee's own active
* tickets whose badge does not have one yet (generate-on-first-read).
* @param SummitAttendee $attendee
* @return void
*/
public function regenerateAttendeeBadgesQRCodes(SummitAttendee $attendee): void;
}
8 changes: 8 additions & 0 deletions app/Services/Model/Imp/SummitOrderService.php
Original file line number Diff line number Diff line change
Expand Up @@ -4027,6 +4027,14 @@ public function updateTicket(Summit $summit, int $order_id, int $ticket_id, arra
$ticket->generateHash();
$new_owner->updateStatus();
$shouldSendInvitationEmail = true;

if ($ticket->hasBadge()) {
try {
$ticket->getBadge()->generateQRCode();
} catch (\Exception $ex) {
Log::error($ex);
}
}
}

if (isset($payload['ticket_type_id'])) {
Expand Down
22 changes: 11 additions & 11 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ services:
RABBITMQ_DEFAULT_USER: ${DOMAIN_EVENTS_RABBITMQ_LOGIN}
RABBITMQ_DEFAULT_PASS: ${DOMAIN_EVENTS_RABBITMQ_PASSWORD}
ports:
- "15672:15672" # admin
- "5672:5672" # RabbitMQ
- "15673:15672" # admin
- "5673:5672" # RabbitMQ
networks:
- summit-api-local-net
env_file: ./.env
Expand Down Expand Up @@ -115,13 +115,13 @@ services:
- ./docker-compose/opentelemetry/otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml
- ./docker-compose/opentelemetry/otel-traces.json:/tmp/otel-traces.json
ports:
- 1888:1888 # pprof extension
- 8888:8888 # Prometheus metrics exposed by the Collector
- 8889:8889 # Prometheus exporter metrics
- 13133:13133 # health_check extension
- 4317:4317 # OTLP gRPC receiver
- 4318:4318 # OTLP http receiver
- 55679:55679 # zpages extension
- 1988:1888 # pprof extension
- 8988:8888 # Prometheus metrics exposed by the Collector
- 8989:8889 # Prometheus exporter metrics
- 13233:13133 # health_check extension
- 4417:4317 # OTLP gRPC receiver
- 4418:4318 # OTLP http receiver
- 55779:55679 # zpages extension
networks:
- summit-api-local-net
depends_on:
Expand All @@ -137,8 +137,8 @@ services:
- cluster.name=summit-api-cluster
- node.name=summit-api-node
ports:
- "9200:9200"
- "9300:9300"
- "9201:9200"
- "9301:9300"
volumes:
- elasticsearch_data:/usr/share/elasticsearch/data
networks:
Expand Down
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,5 +110,5 @@ OTEL_INSTRUMENTATION_GUZZLE=true
docker compose up -d otel-collector elasticsearch

# View traces
curl http://localhost:55679/debug/tracez
curl http://localhost:55779/debug/tracez
```
115 changes: 115 additions & 0 deletions tests/AttendeeServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
use App\Services\Model\IAttendeeService;
use Illuminate\Support\Facades\App;
use LaravelDoctrine\ORM\Facades\EntityManager;
use models\summit\Summit;
use models\summit\SummitAttendeeBadge;
use models\summit\SummitAttendeeTicket;
/**
* Class AttendeeServiceTest
*/
Expand Down Expand Up @@ -85,4 +88,116 @@ public function testSendRegistrationIncompleteReminderByAttendeeIds() {

$service->send(self::$summit->getId(), $payload);
}

public function testReassignAttendeeTicketRegeneratesBadgeQRCode(){

$attendee = self::$summit->getAttendeeByMember(self::$defaultMember);
$this->assertNotNull($attendee);
$ticket = $attendee->getTickets()->first();
$this->assertNotNull($ticket);
$this->assertTrue($ticket->hasBadge());
$badge_id = $ticket->getBadge()->getId();

$summit_id = self::$summit->getId();
$attendee_id = $attendee->getId();
$member2_email = self::$member2->getEmail();
$member2_first = self::$member2->getFirstName();
$member2_last = self::$member2->getLastName();
$member2_fullname = self::$member2->getFullName();
$default_email = self::$defaultMember->getEmail();

// clear the identity map so the service performs a genuinely fresh load of the
// ticket, matching how a real HTTP request behaves.
EntityManager::clear();

$summit = EntityManager::getRepository(Summit::class)->find($summit_id);
$attendee = EntityManager::getRepository(\models\summit\SummitAttendee::class)->find($attendee_id);
$ticket_id = $this->resolveRealTicketIdForBadge($badge_id);

$service = App::make(IAttendeeService::class);
$payload = [
'attendee_email' => $member2_email,
'attendee_first_name' => $member2_first,
'attendee_last_name' => $member2_last,
];

$reassigned_ticket = $service->reassignAttendeeTicket($summit, $attendee, $ticket_id, $payload);

$this->assertBadgeQRRegeneratedForNewOwner(
$reassigned_ticket, $badge_id, $summit, $member2_email, $member2_fullname, $default_email
);
}

public function testReassignAttendeeTicketByMemberRegeneratesBadgeQRCode(){

$attendee = self::$summit->getAttendeeByMember(self::$defaultMember);
$this->assertNotNull($attendee);
$ticket = $attendee->getTickets()->first();
$this->assertNotNull($ticket);
$this->assertTrue($ticket->hasBadge());
$badge_id = $ticket->getBadge()->getId();

$summit_id = self::$summit->getId();
$attendee_id = $attendee->getId();
$member2_id = self::$member2->getId();
$member2_email = self::$member2->getEmail();
$member2_fullname = self::$member2->getFullName();
$default_email = self::$defaultMember->getEmail();

// see comments in testReassignAttendeeTicketRegeneratesBadgeQRCode
EntityManager::clear();

$summit = EntityManager::getRepository(Summit::class)->find($summit_id);
$attendee = EntityManager::getRepository(\models\summit\SummitAttendee::class)->find($attendee_id);
$member2 = EntityManager::getRepository(\models\main\Member::class)->find($member2_id);
$ticket_id = $this->resolveRealTicketIdForBadge($badge_id);

$service = App::make(IAttendeeService::class);
$reassigned_ticket = $service->reassignAttendeeTicketByMember($summit, $attendee, $member2, $ticket_id);

$this->assertBadgeQRRegeneratedForNewOwner(
$reassigned_ticket, $badge_id, $summit, $member2_email, $member2_fullname, $default_email
);
}

/**
* The fixture (InsertSummitTestData) reuses one SummitAttendeeBadge PHP object
* across several tickets, so only the LAST ticket it was attached to is the one
* actually persisted as this badge's TicketID in the DB - resolve the real ticket
* via the badge's own association rather than trusting collection order.
*/
private function resolveRealTicketIdForBadge(int $badge_id): int
{
return EntityManager::getRepository(SummitAttendeeBadge::class)->find($badge_id)->getTicket()->getId();
}

/**
* Shared post-reassignment assertions: the returned ticket's own badge
* association must already reflect the regenerated badge (not stale/absent),
* since API responses serialize this same object directly without a reload;
* and the persisted badge, once reloaded independently, decodes to the new
* owner's email/full name.
*/
private function assertBadgeQRRegeneratedForNewOwner(
SummitAttendeeTicket $reassigned_ticket,
int $badge_id,
Summit $summit,
string $new_owner_email,
string $new_owner_fullname,
string $previous_owner_email
): void {
$this->assertTrue($reassigned_ticket->hasBadge());
$this->assertEquals($badge_id, $reassigned_ticket->getBadge()->getId());
$this->assertNotEmpty($reassigned_ticket->getBadge()->getQRCode());

EntityManager::clear();
$badge = EntityManager::getRepository(SummitAttendeeBadge::class)->find($badge_id);
$qr_code = $badge->getQRCode();
$this->assertNotEmpty($qr_code);
$decoded = SummitAttendeeBadge::parseQRCode(SummitAttendeeBadge::decodeQRCodeFor($summit, $qr_code));

$this->assertEquals($new_owner_email, $decoded['owner_email']);
$this->assertEquals($new_owner_fullname, $decoded['owner_fullname']);
$this->assertNotEquals($previous_owner_email, $decoded['owner_email']);
}
}
Loading
Loading