Skip to content
Draft
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 @@ -20,6 +20,9 @@
use models\summit\SpeakersSummitRegistrationPromoCode;
use models\summit\SpeakerSummitRegistrationDiscountCode;
use models\summit\SpeakerSummitRegistrationPromoCode;
use models\summit\DomainAuthorizedSummitRegistrationDiscountCode;
use models\summit\DomainAuthorizedSummitRegistrationPromoCode;
use App\Rules\AllowedEmailDomainsArray;
use models\summit\SponsorSummitRegistrationDiscountCode;
use models\summit\SponsorSummitRegistrationPromoCode;
/**
Expand Down Expand Up @@ -72,19 +75,21 @@ public static function buildForAdd(array $payload = []): array
switch ($class_name){
case MemberSummitRegistrationPromoCode::ClassName:{
$specific_rules = [
'first_name' => 'required_without:owner_id|string',
'last_name' => 'required_without:owner_id|string',
'email' => 'required_without:owner_id|email|max:254',
'type' => 'required|string|in:'.join(",", PromoCodesConstants::MemberSummitRegistrationPromoCodeTypes),
'owner_id' => 'required_without:first_name,last_name,email|integer'
'first_name' => 'required_without:owner_id|string',
'last_name' => 'required_without:owner_id|string',
'email' => 'required_without:owner_id|email|max:254',
'type' => 'required|string|in:'.join(",", PromoCodesConstants::MemberSummitRegistrationPromoCodeTypes),
'owner_id' => 'required_without:first_name,last_name,email|integer',
'auto_apply' => 'sometimes|boolean',
];
}
break;
case SpeakerSummitRegistrationPromoCode::ClassName:
{
$specific_rules = [
'type' => 'required|string|in:'.join(",", PromoCodesConstants::SpeakerSummitRegistrationPromoCodeTypes),
'speaker_id' => 'sometimes|integer'
'speaker_id' => 'sometimes|integer',
'auto_apply' => 'sometimes|boolean',
];
}
break;
Expand All @@ -106,11 +111,12 @@ public static function buildForAdd(array $payload = []): array
case MemberSummitRegistrationDiscountCode::ClassName:
{
$specific_rules = array_merge([
'first_name' => 'required_without:owner_id|string',
'last_name' => 'required_without:owner_id|string',
'email' => 'required_without:owner_id|email|max:254',
'type' => 'required|string|in:'.join(",", PromoCodesConstants::MemberSummitRegistrationPromoCodeTypes),
'owner_id' => 'required_without:first_name,last_name,email|integer',
'first_name' => 'required_without:owner_id|string',
'last_name' => 'required_without:owner_id|string',
'email' => 'required_without:owner_id|email|max:254',
'type' => 'required|string|in:'.join(",", PromoCodesConstants::MemberSummitRegistrationPromoCodeTypes),
'owner_id' => 'required_without:first_name,last_name,email|integer',
'auto_apply' => 'sometimes|boolean',
], $discount_code_rules);
}
break;
Expand All @@ -119,6 +125,7 @@ public static function buildForAdd(array $payload = []): array
$specific_rules = array_merge([
'type' => 'required|string|in:'.join(",", PromoCodesConstants::SpeakerSummitRegistrationPromoCodeTypes),
'speaker_id' => 'sometimes|integer',
'auto_apply' => 'sometimes|boolean',
], $discount_code_rules);
}
break;
Expand All @@ -138,6 +145,24 @@ public static function buildForAdd(array $payload = []): array

}
break;
case DomainAuthorizedSummitRegistrationDiscountCode::ClassName:
{
$specific_rules = array_merge([
'allowed_email_domains' => ['sometimes', new AllowedEmailDomainsArray()],
'quantity_per_account' => 'sometimes|integer|min:0',
'auto_apply' => 'sometimes|boolean',
], $discount_code_rules);
}
break;
case DomainAuthorizedSummitRegistrationPromoCode::ClassName:
{
$specific_rules = [
'allowed_email_domains' => ['sometimes', new AllowedEmailDomainsArray()],
'quantity_per_account' => 'sometimes|integer|min:0',
'auto_apply' => 'sometimes|boolean',
];
}
break;
}

return array_merge($base_rules, $specific_rules);
Expand Down Expand Up @@ -188,19 +213,21 @@ public static function buildForUpdate(array $payload = []): array
switch ($class_name){
case MemberSummitRegistrationPromoCode::ClassName:{
$specific_rules = [
'first_name' => 'required_without:owner_id|string',
'last_name' => 'required_without:owner_id|string',
'email' => 'required_without:owner_id|email|max:254',
'type' => 'required|string|in:'.join(",", PromoCodesConstants::MemberSummitRegistrationPromoCodeTypes),
'owner_id' => 'required_without:first_name,last_name,email|integer'
'first_name' => 'required_without:owner_id|string',
'last_name' => 'required_without:owner_id|string',
'email' => 'required_without:owner_id|email|max:254',
'type' => 'required|string|in:'.join(",", PromoCodesConstants::MemberSummitRegistrationPromoCodeTypes),
'owner_id' => 'required_without:first_name,last_name,email|integer',
'auto_apply' => 'sometimes|boolean',
];
}
break;
case SpeakerSummitRegistrationPromoCode::ClassName:
{
$specific_rules = [
'type' => 'required|string|in:'.join(",", PromoCodesConstants::SpeakerSummitRegistrationPromoCodeTypes),
'speaker_id' => 'sometimes|integer'
'speaker_id' => 'sometimes|integer',
'auto_apply' => 'sometimes|boolean',
];
}
break;
Expand All @@ -222,11 +249,12 @@ public static function buildForUpdate(array $payload = []): array
case MemberSummitRegistrationDiscountCode::ClassName:
{
$specific_rules = array_merge([
'first_name' => 'required_without:owner_id|string',
'last_name' => 'required_without:owner_id|string',
'email' => 'required_without:owner_id|email|max:254',
'type' => 'required|string|in:'.join(",", PromoCodesConstants::MemberSummitRegistrationPromoCodeTypes),
'owner_id' => 'required_without:first_name,last_name,email|integer',
'first_name' => 'required_without:owner_id|string',
'last_name' => 'required_without:owner_id|string',
'email' => 'required_without:owner_id|email|max:254',
'type' => 'required|string|in:'.join(",", PromoCodesConstants::MemberSummitRegistrationPromoCodeTypes),
'owner_id' => 'required_without:first_name,last_name,email|integer',
'auto_apply' => 'sometimes|boolean',
], $discount_code_rules);
}
break;
Expand All @@ -235,6 +263,7 @@ public static function buildForUpdate(array $payload = []): array
$specific_rules = array_merge([
'type' => 'required|string|in:'.join(",", PromoCodesConstants::SpeakerSummitRegistrationPromoCodeTypes),
'speaker_id' => 'sometimes|integer',
'auto_apply' => 'sometimes|boolean',
], $discount_code_rules);
}
break;
Expand All @@ -254,6 +283,24 @@ public static function buildForUpdate(array $payload = []): array

}
break;
case DomainAuthorizedSummitRegistrationDiscountCode::ClassName:
{
$specific_rules = array_merge([
'allowed_email_domains' => ['sometimes', new AllowedEmailDomainsArray()],
'quantity_per_account' => 'sometimes|integer|min:0',
'auto_apply' => 'sometimes|boolean',
], $discount_code_rules);
}
break;
case DomainAuthorizedSummitRegistrationPromoCode::ClassName:
{
$specific_rules = [
'allowed_email_domains' => ['sometimes', new AllowedEmailDomainsArray()],
'quantity_per_account' => 'sometimes|integer|min:0',
'auto_apply' => 'sometimes|boolean',
];
}
break;
}

return array_merge($base_rules, $specific_rules);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1575,4 +1575,66 @@ public function sendSponsorPromoCodes($summit_id)
return $this->ok();
});
}

/**
* Discover qualifying promo codes for the current user.
* Returns domain-authorized codes (matched by email domain) and existing email-linked
* codes (member/speaker, matched by associated email) with auto_apply flag.
* Email is always derived from the authenticated principal — no email query parameter accepted.
*/
#[OA\Get(
path: "/api/v1/summits/{id}/promo-codes/all/discover",
summary: "Discover qualifying promo codes for the current user",
description: "Returns domain-authorized promo codes (matched by email domain) and existing email-linked promo codes (member/speaker, matched by associated email) for the current user",
operationId: "discoverPromoCodesBySummit",
tags: ["Promo Codes"],
security: [['summit_promo_codes_oauth2' => [SummitScopes::ReadSummitData]]],
parameters: [
new OA\Parameter(name: "id", in: "path", required: true, schema: new OA\Schema(type: "integer")),
new OA\Parameter(name: "expand", in: "query", required: false, schema: new OA\Schema(type: "string")),
],
responses: [
new OA\Response(response: Response::HTTP_OK, description: "OK"),
new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: "Unauthorized"),
new OA\Response(response: Response::HTTP_FORBIDDEN, description: "Forbidden"),
new OA\Response(response: Response::HTTP_NOT_FOUND, description: "Summit not found"),
]
)]
public function discover($summit_id)
{
return $this->processRequest(function () use ($summit_id) {

$summit = SummitFinderStrategyFactory::build($this->summit_repository, $this->resource_server_context)->find(intval($summit_id));
if (is_null($summit))
return $this->error404();

$current_member = $this->resource_server_context->getCurrentUser();
if (is_null($current_member))
return $this->error403();

$codes = $this->promo_code_service->discoverPromoCodes($summit, $current_member);

$expand = Request::input('expand', '');
$fields = Request::input('fields', '');
$relations = Request::input('relations', '');

$relations = !empty($relations) ? explode(',', $relations) : ['allowed_ticket_types', 'badge_features', 'tags', 'ticket_types_rules'];
$fields = !empty($fields) ? explode(',', $fields) : [];

$data = [];
foreach ($codes as $code) {
$serializer = SerializerRegistry::getInstance()->getSerializer($code);
$data[] = $serializer->serialize($expand, $fields, $relations);
}

$total = count($data);
return $this->ok([
'total' => $total,
'per_page' => $total,
'current_page' => 1,
'last_page' => 1,
'data' => $data,
]);
});
}
}
12 changes: 12 additions & 0 deletions app/ModelSerializers/SerializerRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,18 @@ private function __construct()
self::SerializerType_PreValidation => SummitRegistrationPromoCodePreValidationSerializer::class,
];

$this->registry['DomainAuthorizedSummitRegistrationDiscountCode'] = [
self::SerializerType_Public => DomainAuthorizedSummitRegistrationDiscountCodeSerializer::class,
self::SerializerType_CSV => DomainAuthorizedSummitRegistrationDiscountCodeSerializer::class,
self::SerializerType_PreValidation => SummitRegistrationPromoCodePreValidationSerializer::class,
];

$this->registry['DomainAuthorizedSummitRegistrationPromoCode'] = [
self::SerializerType_Public => DomainAuthorizedSummitRegistrationPromoCodeSerializer::class,
self::SerializerType_CSV => DomainAuthorizedSummitRegistrationPromoCodeSerializer::class,
self::SerializerType_PreValidation => SummitRegistrationPromoCodePreValidationSerializer::class,
];

$this->registry['PresentationSpeakerSummitAssistanceConfirmationRequest'] = PresentationSpeakerSummitAssistanceConfirmationRequestSerializer::class;
$this->registry['SummitRegistrationDiscountCodeTicketTypeRule'] = SummitRegistrationDiscountCodeTicketTypeRuleSerializer::class;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php namespace ModelSerializers;
/**
* Copyright 2026 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/

use models\summit\DomainAuthorizedSummitRegistrationDiscountCode;

/**
* Class DomainAuthorizedSummitRegistrationDiscountCodeSerializer
* @package ModelSerializers
*/
class DomainAuthorizedSummitRegistrationDiscountCodeSerializer
extends SummitRegistrationDiscountCodeSerializer
{
protected static $array_mappings = [
'AllowedEmailDomains' => 'allowed_email_domains:json_string_array',
'QuantityPerAccount' => 'quantity_per_account:json_int',
'AutoApply' => 'auto_apply:json_boolean',
];

protected static $allowed_relations = [
'allowed_ticket_types',
];

/**
* @param null $expand
* @param array $fields
* @param array $relations
* @param array $params
* @return array
*/
public function serialize($expand = null, array $fields = [], array $relations = [], array $params = [])
{
$code = $this->object;
if (!$code instanceof DomainAuthorizedSummitRegistrationDiscountCode) return [];
$values = parent::serialize($expand, $fields, $relations, $params);

// RE-ADD allowed_ticket_types (parent discount serializer unsets it).
// Check both relations (default serialization) and expand (explicit ?expand= request).
$needs_allowed_ticket_types = in_array('allowed_ticket_types', $relations)
|| (!empty($expand) && str_contains($expand, 'allowed_ticket_types'));
if ($needs_allowed_ticket_types && !isset($values['allowed_ticket_types'])) {
$ticket_types = [];
foreach ($code->getAllowedTicketTypes() as $ticket_type) {
$ticket_types[] = $ticket_type->getId();
}
$values['allowed_ticket_types'] = $ticket_types;
}

// Transient remaining_quantity_per_account (set by service layer)
$values['remaining_quantity_per_account'] = $code->getRemainingQuantityPerAccount();

return $values;
}

protected static $expand_mappings = [
'allowed_ticket_types' => [
'type' => \Libs\ModelSerializers\Many2OneExpandSerializer::class,
'getter' => 'getAllowedTicketTypes',
],
];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php namespace ModelSerializers;
/**
* Copyright 2026 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/

use models\summit\DomainAuthorizedSummitRegistrationPromoCode;

/**
* Class DomainAuthorizedSummitRegistrationPromoCodeSerializer
* @package ModelSerializers
*/
class DomainAuthorizedSummitRegistrationPromoCodeSerializer
extends SummitRegistrationPromoCodeSerializer
{
protected static $array_mappings = [
'AllowedEmailDomains' => 'allowed_email_domains:json_string_array',
'QuantityPerAccount' => 'quantity_per_account:json_int',
'AutoApply' => 'auto_apply:json_boolean',
];

/**
* @param null $expand
* @param array $fields
* @param array $relations
* @param array $params
* @return array
*/
public function serialize($expand = null, array $fields = [], array $relations = [], array $params = [])
{
$code = $this->object;
if (!$code instanceof DomainAuthorizedSummitRegistrationPromoCode) return [];
$values = parent::serialize($expand, $fields, $relations, $params);

// Transient remaining_quantity_per_account (set by service layer)
$values['remaining_quantity_per_account'] = $code->getRemainingQuantityPerAccount();

return $values;
}
}
Loading
Loading