Skip to content

Commit dde3eac

Browse files
caseylockerclaude
andcommitted
feat: Add dropbox_sync_enabled flag and materializer proxy routes
Add Summit model flag to control Dropbox sync activation per-summit, and proxy routes that forward admin requests to the dropbox-materializer microservice through Summit API's OAuth2 auth layer. - Doctrine migration for DropboxSyncEnabled column - Summit model property, serializer, validation, factory - DropboxMaterializerApi Guzzle client with HMAC X-Internal-Key auth - Controller with admin permission checks and sync-enabled gating - Routes: materialize, materializeRoom, backfill, rebuild, preflight, status Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 65b9bf4 commit dde3eac

12 files changed

Lines changed: 503 additions & 0 deletions

File tree

.env.example

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,11 @@ PAYMENTS_SERVICE_OAUTH2_CLIENT_ID=
266266
PAYMENTS_SERVICE_OAUTH2_CLIENT_SECRET=
267267
PAYMENTS_SERVICE_OAUTH2_SCOPES=payment-profile/read
268268

269+
# DROPBOX MATERIALIZER SERVICE
270+
271+
DROPBOX_MATERIALIZER_URL=http://localhost:8100
272+
DROPBOX_MATERIALIZER_KEY=
273+
DROPBOX_MATERIALIZER_TIMEOUT=10
269274

270275
# L5_FORMAT_TO_USE_FOR_DOCS=yaml
271276
# L5_SWAGGER_GENERATE_ALWAYS=true # Dev setting

app/Http/Controllers/Apis/Protected/Summit/Factories/SummitValidationRulesFactory.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ public static function buildForAdd(array $payload = []): array
9898
'registration_send_order_email_automatically' => 'sometimes|boolean',
9999
'registration_allow_automatic_reminder_emails' => 'sometimes|boolean',
100100
'allow_update_attendee_extra_questions' => 'sometimes|boolean',
101+
'dropbox_sync_enabled' => 'sometimes|boolean',
101102
'time_zone_label' => 'sometimes|string',
102103
'registration_allowed_refund_request_till_date' => 'nullable|date_format:U|epoch_seconds',
103104
'registration_slug_prefix' => 'required|string|max:50',
@@ -181,6 +182,7 @@ public static function buildForUpdate(array $payload = []): array
181182
'registration_send_order_email_automatically' => 'sometimes|boolean',
182183
'registration_allow_automatic_reminder_emails' => 'sometimes|boolean',
183184
'allow_update_attendee_extra_questions' => 'sometimes|boolean',
185+
'dropbox_sync_enabled' => 'sometimes|boolean',
184186
'time_zone_label' => 'sometimes|string',
185187
'registration_allowed_refund_request_till_date' => 'nullable|date_format:U|epoch_seconds',
186188
'registration_slug_prefix' => 'sometimes|string|max:50',
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
<?php namespace App\Http\Controllers;
2+
/**
3+
* Copyright 2026 OpenStack Foundation
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
**/
14+
15+
use App\Models\Exceptions\AuthzException;
16+
use App\Models\Foundation\Main\IGroup;
17+
use App\Models\Foundation\Summit\Locations\SummitVenue;
18+
use App\Services\Apis\IDropboxMaterializerApi;
19+
use models\oauth2\IResourceServerContext;
20+
use models\summit\ISummitRepository;
21+
use models\summit\Summit;
22+
use models\exceptions\ValidationException;
23+
use models\exceptions\EntityNotFoundException;
24+
25+
/**
26+
* Class OAuth2SummitDropboxSyncApiController
27+
* @package App\Http\Controllers
28+
*/
29+
final class OAuth2SummitDropboxSyncApiController extends OAuth2ProtectedController
30+
{
31+
/**
32+
* @var IDropboxMaterializerApi
33+
*/
34+
private $materializer_api;
35+
36+
/**
37+
* @param ISummitRepository $summit_repository
38+
* @param IDropboxMaterializerApi $materializer_api
39+
* @param IResourceServerContext $resource_server_context
40+
*/
41+
public function __construct(
42+
ISummitRepository $summit_repository,
43+
IDropboxMaterializerApi $materializer_api,
44+
IResourceServerContext $resource_server_context
45+
)
46+
{
47+
parent::__construct($resource_server_context);
48+
$this->repository = $summit_repository;
49+
$this->materializer_api = $materializer_api;
50+
}
51+
52+
/**
53+
* @param Summit $summit
54+
* @return void
55+
* @throws \Exception
56+
*/
57+
private function checkAdminPermission(Summit $summit): void
58+
{
59+
$current_member = $this->resource_server_context->getCurrentUser();
60+
if (!is_null($current_member) && !$current_member->isAdmin() && !$current_member->hasPermissionForOnGroup($summit, IGroup::SummitAdministrators))
61+
throw new AuthzException(
62+
sprintf("Member %s has not permission for this Summit", $current_member->getId())
63+
);
64+
}
65+
66+
/**
67+
* @param int $summit_id
68+
* @return Summit
69+
* @throws EntityNotFoundException
70+
*/
71+
private function findSummit(int $summit_id): Summit
72+
{
73+
$summit = $this->repository->getById($summit_id);
74+
if (is_null($summit) || !$summit instanceof Summit)
75+
throw new EntityNotFoundException(sprintf("Summit %s not found", $summit_id));
76+
return $summit;
77+
}
78+
79+
/**
80+
* @param Summit $summit
81+
* @throws ValidationException
82+
*/
83+
private function requireSyncEnabled(Summit $summit): void
84+
{
85+
if (!$summit->isDropboxSyncEnabled())
86+
throw new ValidationException("Dropbox sync is not enabled for this summit.");
87+
}
88+
89+
/**
90+
* POST /api/v1/summits/{id}/dropbox-sync/materialize
91+
*/
92+
public function materialize($summit_id)
93+
{
94+
return $this->processRequest(function () use ($summit_id) {
95+
$summit = $this->findSummit(intval($summit_id));
96+
$this->checkAdminPermission($summit);
97+
$this->requireSyncEnabled($summit);
98+
99+
$result = $this->materializer_api->materialize($summit->getId());
100+
return $this->ok($result);
101+
});
102+
}
103+
104+
/**
105+
* POST /api/v1/summits/{id}/dropbox-sync/materialize/{location_id}/{room_id}
106+
*/
107+
public function materializeRoom($summit_id, $location_id, $room_id)
108+
{
109+
return $this->processRequest(function () use ($summit_id, $location_id, $room_id) {
110+
$summit = $this->findSummit(intval($summit_id));
111+
$this->checkAdminPermission($summit);
112+
$this->requireSyncEnabled($summit);
113+
114+
$venue = $summit->getLocation(intval($location_id));
115+
if (is_null($venue) || !$venue instanceof SummitVenue)
116+
throw new EntityNotFoundException(sprintf("Venue %s not found", $location_id));
117+
118+
$room = $venue->getRoom(intval($room_id));
119+
if (is_null($room))
120+
throw new EntityNotFoundException(sprintf("Room %s not found", $room_id));
121+
122+
$result = $this->materializer_api->materializeRoom(
123+
$summit->getId(),
124+
$venue->getName(),
125+
$room->getName()
126+
);
127+
return $this->ok($result);
128+
});
129+
}
130+
131+
/**
132+
* POST /api/v1/summits/{id}/dropbox-sync/backfill
133+
*/
134+
public function backfill($summit_id)
135+
{
136+
return $this->processRequest(function () use ($summit_id) {
137+
$summit = $this->findSummit(intval($summit_id));
138+
$this->checkAdminPermission($summit);
139+
$this->requireSyncEnabled($summit);
140+
141+
$result = $this->materializer_api->backfill($summit->getId());
142+
return $this->ok($result);
143+
});
144+
}
145+
146+
/**
147+
* POST /api/v1/summits/{id}/dropbox-sync/rebuild
148+
*/
149+
public function rebuild($summit_id)
150+
{
151+
return $this->processRequest(function () use ($summit_id) {
152+
$summit = $this->findSummit(intval($summit_id));
153+
$this->checkAdminPermission($summit);
154+
155+
$result = $this->materializer_api->rebuild($summit->getId());
156+
return $this->ok($result);
157+
});
158+
}
159+
160+
/**
161+
* GET /api/v1/summits/{id}/dropbox-sync/preflight
162+
*/
163+
public function preflight($summit_id)
164+
{
165+
return $this->processRequest(function () use ($summit_id) {
166+
$summit = $this->findSummit(intval($summit_id));
167+
$this->checkAdminPermission($summit);
168+
169+
$result = $this->materializer_api->preflight($summit->getId());
170+
return $this->ok($result);
171+
});
172+
}
173+
174+
/**
175+
* GET /api/v1/summits/{id}/dropbox-sync/status
176+
*/
177+
public function status($summit_id)
178+
{
179+
return $this->processRequest(function () use ($summit_id) {
180+
$summit = $this->findSummit(intval($summit_id));
181+
$this->checkAdminPermission($summit);
182+
183+
$result = $this->materializer_api->status($summit->getId());
184+
return $this->ok($result);
185+
});
186+
}
187+
}

app/ModelSerializers/Summit/SummitSerializer.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ class SummitSerializer extends SilverStripeSerializer
9191
'RegistrationAllowAutomaticReminderEmails' => 'registration_allow_automatic_reminder_emails:json_boolean',
9292
'Modality' => 'modality:json_string',
9393
'AllowUpdateAttendeeExtraQuestions' => 'allow_update_attendee_extra_questions:json_boolean',
94+
'DropboxSyncEnabled' => 'dropbox_sync_enabled:json_boolean',
9495
'TimeZoneLabel' => 'time_zone_label:json_string',
9596
'RegistrationAllowedRefundRequestTillDate' => 'registration_allowed_refund_request_till_date:datetime_epoch',
9697
'RegistrationSlugPrefix' => 'registration_slug_prefix:json_string',
@@ -162,6 +163,7 @@ class SummitSerializer extends SilverStripeSerializer
162163
'registration_allow_automatic_reminder_emails',
163164
'modality',
164165
'allow_update_attendee_extra_questions',
166+
'dropbox_sync_enabled',
165167
'time_zone_label',
166168
'registration_allowed_refund_request_till_date',
167169
'registration_slug_prefix',

app/Models/Foundation/Summit/Factories/SummitFactory.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ public static function populate(Summit $summit, array $data){
8383
$summit->setAllowUpdateAttendeeExtraQuestions(boolval($data['allow_update_attendee_extra_questions']));
8484
}
8585

86+
if(isset($data['dropbox_sync_enabled'])){
87+
$summit->setDropboxSyncEnabled(boolval($data['dropbox_sync_enabled']));
88+
}
89+
8690
if(isset($data['dates_label']) ){
8791
$summit->setDatesLabel(trim($data['dates_label']));
8892
}

app/Models/Foundation/Summit/Summit.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,12 @@ public function setMarketingSiteOauth2ClientScopes(string $marketing_site_oauth2
498498
#[ORM\Column(name: 'RegistrationAllowAutomaticReminderEmails', type: 'boolean')]
499499
private $registration_allow_automatic_reminder_emails;
500500

501+
/**
502+
* @var bool
503+
*/
504+
#[ORM\Column(name: 'DropboxSyncEnabled', type: 'boolean')]
505+
private $dropbox_sync_enabled;
506+
501507
#[ORM\OneToMany(targetEntity: \SummitEvent::class, mappedBy: 'summit', cascade: ['persist', 'remove'], orphanRemoval: true, fetch: 'EXTRA_LAZY')]
502508
private $events;
503509

@@ -1190,6 +1196,7 @@ public function __construct()
11901196
$this->registration_allow_automatic_reminder_emails = true;
11911197
$this->registration_send_order_email_automatically = true;
11921198
$this->allow_update_attendee_extra_questions = false;
1199+
$this->dropbox_sync_enabled = false;
11931200
$this->registration_companies = new ArrayCollection();
11941201
$this->external_registration_feed_last_ingest_date = null;
11951202
$this->speakers_announcement_emails = new ArrayCollection();
@@ -6362,6 +6369,22 @@ public function setAllowUpdateAttendeeExtraQuestions(bool $allow_update_attendee
63626369
$this->allow_update_attendee_extra_questions = $allow_update_attendee_extra_questions;
63636370
}
63646371

6372+
/**
6373+
* @return bool
6374+
*/
6375+
public function isDropboxSyncEnabled(): bool
6376+
{
6377+
return $this->dropbox_sync_enabled;
6378+
}
6379+
6380+
/**
6381+
* @param bool $dropbox_sync_enabled
6382+
*/
6383+
public function setDropboxSyncEnabled(bool $dropbox_sync_enabled): void
6384+
{
6385+
$this->dropbox_sync_enabled = $dropbox_sync_enabled;
6386+
}
6387+
63656388
/**
63666389
* @return bool
63676390
*/

0 commit comments

Comments
 (0)