-
Notifications
You must be signed in to change notification settings - Fork 2
feat(events): add PUT /events/{id}/draft endpoint to save incomplete submissions #555
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -617,9 +617,9 @@ public function addEvent(Summit $summit, array $data) | |
| * @param array $data | ||
| * @return SummitEvent | ||
| */ | ||
| public function updateEvent(Summit $summit, $event_id, array $data, bool $trigger_data_update = true) | ||
| public function updateEvent(Summit $summit, $event_id, array $data, bool $trigger_data_update = true, bool $saveAsIncomplete = false) | ||
| { | ||
| return $this->saveOrUpdateEvent($summit, $data, $event_id, $trigger_data_update); | ||
| return $this->saveOrUpdateEvent($summit, $data, $event_id, $trigger_data_update, $saveAsIncomplete); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -660,9 +660,9 @@ private function canPerformEventTypeTransition(SummitEventType $old_event_type, | |
| * @return SummitEvent | ||
| * @throws Exception | ||
| */ | ||
| private function saveOrUpdateEvent(Summit $summit, array $data, $event_id = null, bool $trigger_data_update = true) | ||
| private function saveOrUpdateEvent(Summit $summit, array $data, $event_id = null, bool $trigger_data_update = true, bool $saveAsIncomplete = false) | ||
| { | ||
| return $this->tx_service->transaction(function () use ($summit, $data, $event_id, $trigger_data_update) { | ||
| return $this->tx_service->transaction(function () use ($summit, $data, $event_id, $trigger_data_update, $saveAsIncomplete) { | ||
|
|
||
| Log::debug | ||
| ( | ||
|
|
@@ -832,7 +832,7 @@ private function saveOrUpdateEvent(Summit $summit, array $data, $event_id = null | |
| } | ||
| } | ||
|
|
||
| $this->saveOrUpdatePresentationData($event, $event_type, $data); | ||
| $this->saveOrUpdatePresentationData($event, $event_type, $data, $saveAsIncomplete); | ||
| $this->saveOrUpdateSummitGroupEventData($event, $event_type, $data); | ||
|
|
||
| if (!$event_type->isAllowsLocation()) | ||
|
|
@@ -881,17 +881,23 @@ private function saveOrUpdateSummitGroupEventData(SummitEvent $event, SummitEven | |
| * @param SummitEvent $event | ||
| * @param SummitEventType $event_type | ||
| * @param array $data | ||
| * @param bool $saveAsIncomplete | ||
| * @throws EntityNotFoundException | ||
| * @throws ValidationException | ||
| */ | ||
| private function saveOrUpdatePresentationData(SummitEvent $event, SummitEventType $event_type, array $data) | ||
| private function saveOrUpdatePresentationData(SummitEvent $event, SummitEventType $event_type, array $data, bool $saveAsIncomplete = false) | ||
| { | ||
| if (!$event instanceof Presentation) return; | ||
|
|
||
| // if we are creating the presentation from admin, then | ||
| // we should mark it as received and complete | ||
| $event->setStatus(Presentation::STATUS_RECEIVED); | ||
| $event->setProgress(Presentation::PHASE_COMPLETE); | ||
| if ($saveAsIncomplete && $event->isPublished()) | ||
| throw new ValidationException('Cannot save a published event as incomplete.'); | ||
|
|
||
| if (!$saveAsIncomplete || $event->isNew()) { | ||
| // if we are creating the presentation from admin, then | ||
| // we should mark it as received and complete | ||
| $event->setStatus(Presentation::STATUS_RECEIVED); | ||
| $event->setProgress(Presentation::PHASE_COMPLETE); | ||
| } | ||
|
|
||
| // speakers | ||
|
|
||
|
|
@@ -900,13 +906,15 @@ private function saveOrUpdatePresentationData(SummitEvent $event, SummitEventTyp | |
| $shouldClearSpeakers = isset($data['speakers']) && count($data['speakers']) == 0; | ||
| $speakers = $data['speakers'] ?? []; | ||
|
|
||
| if ($event_type->isAreSpeakersMandatory()) { | ||
| if ($shouldClearSpeakers || ($event->isNew() && count($speakers) == 0)) | ||
| throw new ValidationException('Speakers are mandatory.'); | ||
| } | ||
| if (!$saveAsIncomplete || $event->isNew()) { | ||
| if ($event_type->isAreSpeakersMandatory()) { | ||
| if ($shouldClearSpeakers || ($event->isNew() && count($speakers) == 0)) | ||
| throw new ValidationException('Speakers are mandatory.'); | ||
| } | ||
|
|
||
| if ($shouldClearSpeakers) { | ||
| $event->clearSpeakers(); | ||
| if ($shouldClearSpeakers) { | ||
| $event->clearSpeakers(); | ||
| } | ||
| } | ||
|
Comment on lines
+909
to
918
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Draft updates ignore explicit speaker/moderator clears on existing presentations. At Line 909 and Line 937, the 🔧 Proposed fix- if (!$saveAsIncomplete || $event->isNew()) {
- if ($event_type->isAreSpeakersMandatory()) {
- if ($shouldClearSpeakers || ($event->isNew() && count($speakers) == 0))
- throw new ValidationException('Speakers are mandatory.');
- }
-
- if ($shouldClearSpeakers) {
- $event->clearSpeakers();
- }
- }
+ if ($event_type->isAreSpeakersMandatory() && (!$saveAsIncomplete || $event->isNew())) {
+ if ($shouldClearSpeakers || ($event->isNew() && count($speakers) == 0))
+ throw new ValidationException('Speakers are mandatory.');
+ }
+
+ if ($shouldClearSpeakers) {
+ $event->clearSpeakers();
+ }- if (!$saveAsIncomplete || $event->isNew()) {
- if ($event_type->isModeratorMandatory()) {
- if ($shouldClearModerator || ($event->isNew() && $moderator_id == 0))
- throw new ValidationException('moderator_speaker_id is mandatory.');
- }
-
- if ($shouldClearModerator) $event->unsetModerator();
- }
+ if ($event_type->isModeratorMandatory() && (!$saveAsIncomplete || $event->isNew())) {
+ if ($shouldClearModerator || ($event->isNew() && $moderator_id == 0))
+ throw new ValidationException('moderator_speaker_id is mandatory.');
+ }
+
+ if ($shouldClearModerator) $event->unsetModerator();Also applies to: 937-944 🤖 Prompt for AI Agents |
||
|
|
||
| if (count($speakers) > 0) { | ||
|
|
@@ -926,12 +934,14 @@ private function saveOrUpdatePresentationData(SummitEvent $event, SummitEventTyp | |
| $shouldClearModerator = isset($data['moderator_speaker_id']) && intval($data['moderator_speaker_id']) == 0; | ||
| $moderator_id = isset($data['moderator_speaker_id']) ? intval($data['moderator_speaker_id']) : 0; | ||
|
|
||
| if ($event_type->isModeratorMandatory()) { | ||
| if ($shouldClearModerator || ($event->isNew() && $moderator_id == 0)) | ||
| throw new ValidationException('moderator_speaker_id is mandatory.'); | ||
| } | ||
| if (!$saveAsIncomplete || $event->isNew()) { | ||
| if ($event_type->isModeratorMandatory()) { | ||
| if ($shouldClearModerator || ($event->isNew() && $moderator_id == 0)) | ||
| throw new ValidationException('moderator_speaker_id is mandatory.'); | ||
| } | ||
|
|
||
| if ($shouldClearModerator) $event->unsetModerator(); | ||
| if ($shouldClearModerator) $event->unsetModerator(); | ||
| } | ||
|
|
||
| if ($moderator_id > 0) { | ||
| $moderator = $this->speaker_repository->getById($moderator_id); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| <?php namespace Database\Migrations\Config; | ||
| /** | ||
| * 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 App\Models\Foundation\Main\IGroup; | ||
| use App\Security\SummitScopes; | ||
| use Doctrine\DBAL\Schema\Schema; | ||
| use Doctrine\Migrations\AbstractMigration; | ||
|
|
||
| /** | ||
| * Seed the update-draft-event endpoint. | ||
| * | ||
| * Idempotent via WHERE NOT EXISTS in APIEndpointsMigrationHelper. | ||
| */ | ||
| final class Version20260609175051 extends AbstractMigration | ||
| { | ||
| use APIEndpointsMigrationHelper; | ||
|
|
||
| private const API_NAME = 'summits'; | ||
| private const ENDPOINT_NAME = 'update-draft-event'; | ||
| private const ENDPOINT_ROUTE = '/api/v1/summits/{id}/events/{event_id}/draft'; | ||
|
|
||
| private const AUTH_GROUPS = [ | ||
| IGroup::SuperAdmins, | ||
| IGroup::Administrators, | ||
| IGroup::SummitAdministrators, | ||
| IGroup::TrackChairsAdmins, | ||
| ]; | ||
|
|
||
| private const SCOPES = [ | ||
| SummitScopes::WriteSummitData, | ||
| SummitScopes::WriteEventData | ||
| ]; | ||
|
|
||
| public function getDescription(): string | ||
| { | ||
| return 'Seed update-draft-event endpoint with scope and authz groups'; | ||
| } | ||
|
|
||
| public function up(Schema $schema): void | ||
| { | ||
| $this->addSql($this->insertEndpoint( | ||
| self::API_NAME, | ||
| self::ENDPOINT_NAME, | ||
| self::ENDPOINT_ROUTE, | ||
| 'PUT' | ||
| )); | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| foreach (self::SCOPES as $scope) { | ||
| $this->addSql($this->insertEndpointScope( | ||
| self::API_NAME, | ||
| self::ENDPOINT_NAME, | ||
| $scope | ||
| )); | ||
| } | ||
|
|
||
| foreach (self::AUTH_GROUPS as $groupSlug) { | ||
| $this->addSql($this->insertEndpointAuthzGroup(self::API_NAME, self::ENDPOINT_NAME, $groupSlug)); | ||
| } | ||
| } | ||
|
|
||
| public function down(Schema $schema): void | ||
| { | ||
| foreach (self::AUTH_GROUPS as $groupSlug) { | ||
| $this->addSql($this->deleteEndpointAuthzGroup(self::API_NAME, self::ENDPOINT_NAME, $groupSlug)); | ||
| } | ||
|
|
||
| $this->addSql($this->deleteScopesEndpoints(self::API_NAME, self::SCOPES)); | ||
| $this->addSql($this->deleteEndpoint(self::API_NAME, self::ENDPOINT_NAME)); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.