Skip to content
Open
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
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ CFP_APP_BASE_URL=
CFP_SUPPORT_EMAIL=
CFP_OAUTH2_SCOPES=
CFP_OAUTH2_CLIENT_ID=
# ceiling and default for an admin-granted per-presentation submission reopen window, in hours
CFP_MAX_REOPEN_HOURS=168
CFP_DEFAULT_REOPEN_HOURS=24

# RABBIT MQ
RABBITMQ_EXCHANGE_NAME=databus-exchange
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ jobs:
- { name: "SummitRSVPServiceTest", filter: "--filter SummitRSVPServiceTest" }
- { name: "SummitRSVPInvitationServiceTest", filter: "--filter SummitRSVPInvitationServiceTest" }
- { name: "EntityModelUnitTests", filter: "tests/Unit/Entities/" }
- { name: "ModelUnitTests", filter: "tests/Unit/Models/" }
- { name: "AuditUnitTests", filter: "tests/Unit/Audit/" }
- { name: "AuditOtlpStrategyTest", filter: "--filter AuditOtlpStrategyTest" }
- { name: "AuditEventTypesTest", filter: "--filter AuditEventTypesTest" }
Expand All @@ -68,7 +69,7 @@ jobs:
- { name: "CacheOptimizations", filter: "--filter '(PresentationSpeakerCacheTest|ResourceServerContextTest)'" }
# Named by path because no job in this matrix runs the tests/ root, only its
# subdirectories - a file added there runs nowhere unless it is listed here.
- { name: "PresentationMediaUploads", filter: "tests/PresentationMediaUploadsTest.php tests/PresentationMediaUploadsVisibilityTest.php tests/PresentationSerializerCacheKeyTest.php" }
- { name: "PresentationMediaUploads", filter: "tests/PresentationMediaUploadsTest.php tests/PresentationMediaUploadsVisibilityTest.php tests/PresentationSerializerCacheKeyTest.php tests/PresentationReopenModelTest.php tests/PresentationReopenApiTest.php tests/PresentationReopenAuthzTest.php" }
env:
OTEL_SERVICE_ENABLED: false
APP_ENV: testing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
use ModelSerializers\SerializerRegistry;
use OpenApi\Attributes as OA;
use services\model\IPresentationService;
use services\model\IPresentationSubmissionReopenService;
use utils\Filter;
use utils\FilterElement;
use utils\FilterParser;
Expand Down Expand Up @@ -84,6 +85,11 @@ final class OAuth2PresentationApiController extends OAuth2ProtectedController
*/
private $presentation_comments_repository;

/**
* @var IPresentationSubmissionReopenService
*/
private $presentation_submission_reopen_service;

/**
* OAuth2PresentationApiController constructor.
* @param IPresentationService $presentation_service
Expand All @@ -92,6 +98,7 @@ final class OAuth2PresentationApiController extends OAuth2ProtectedController
* @param IMemberRepository $member_repository
* @param ISummitPresentationCommentRepository $presentation_comments_repository
* @param IResourceServerContext $resource_server_context
* @param IPresentationSubmissionReopenService $presentation_submission_reopen_service
*/
public function __construct
(
Expand All @@ -100,7 +107,8 @@ public function __construct
ISummitEventRepository $presentation_repository,
IMemberRepository $member_repository,
ISummitPresentationCommentRepository $presentation_comments_repository,
IResourceServerContext $resource_server_context
IResourceServerContext $resource_server_context,
IPresentationSubmissionReopenService $presentation_submission_reopen_service
)
{
parent::__construct($resource_server_context);
Expand All @@ -109,6 +117,7 @@ public function __construct
$this->member_repository = $member_repository;
$this->summit_repository = $summit_repository;
$this->presentation_comments_repository = $presentation_comments_repository;
$this->presentation_submission_reopen_service = $presentation_submission_reopen_service;
}

//presentations
Expand Down Expand Up @@ -525,6 +534,113 @@ public function updatePresentationSubmission($summit_id, $presentation_id)
});
}

#[OA\Put(
path: "/api/v1/summits/{id}/presentations/{presentation_id}/submission-period/reopen",
summary: "Admin-only: reopen the submission period for a presentation",
operationId: "reopenSubmissionPeriod",
security: [['summit_presentations_auth' => [SummitScopes::WriteSummitData, SummitScopes::WriteEventData, SummitScopes::WritePresentationData]]],
tags: ['Presentations'],
parameters: [
new OA\Parameter(name: 'id', in: 'path', required: true, schema: new OA\Schema(type: 'integer')),
new OA\Parameter(name: 'presentation_id', in: 'path', required: true, schema: new OA\Schema(type: 'integer')),
],
requestBody: new OA\RequestBody(
required: false,
content: new OA\JsonContent(
properties: [
new OA\Property(property: 'hours', type: 'integer'),
]
)
),
responses: [
new OA\Response(
response: Response::HTTP_CREATED,
description: "Created",
content: new OA\JsonContent(ref: "#/components/schemas/Presentation")
),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
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: "Not Found"),
new OA\Response(response: Response::HTTP_PRECONDITION_FAILED, description: "Validation Error"),
new OA\Response(response: Response::HTTP_INTERNAL_SERVER_ERROR, description: "Server Error"),
]
)]
public function reopenSubmissionPeriod($summit_id, $presentation_id)
{
return $this->processRequest(function () use ($summit_id, $presentation_id) {

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

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

$isAdmin = $current_member->isAdmin()
|| $current_member->hasPermissionForOnGroup($summit, IGroup::SummitAdministrators);
if (!$isAdmin) return $this->error403();

$payload = $this->getJsonPayload(['hours' => 'sometimes|integer|min:1']);

// null, not the default: the hours rule (default AND ceiling) lives in the service.
$presentation = $this->presentation_submission_reopen_service->reopen(
$summit,
intval($presentation_id),
isset($payload['hours']) ? intval($payload['hours']) : null,
$current_member
);

// Private, NOT Admin: SerializerRegistry has no Admin key for Presentation and an
// unknown type silently falls back to Public, stripping the reopen fields.
return $this->updated(SerializerRegistry::getInstance()->getSerializer(
$presentation, SerializerRegistry::SerializerType_Private
)->serialize(
SerializerUtils::getExpand(),
SerializerUtils::getFields(),
SerializerUtils::getRelations()
));
});
}

#[OA\Delete(
path: "/api/v1/summits/{id}/presentations/{presentation_id}/submission-period/reopen",
summary: "Admin-only: close the reopened submission period for a presentation",
operationId: "closeSubmissionPeriod",
security: [['summit_presentations_auth' => [SummitScopes::WriteSummitData, SummitScopes::WriteEventData, SummitScopes::WritePresentationData]]],
tags: ['Presentations'],
parameters: [
new OA\Parameter(name: 'id', in: 'path', required: true, schema: new OA\Schema(type: 'integer')),
new OA\Parameter(name: 'presentation_id', in: 'path', required: true, schema: new OA\Schema(type: 'integer')),
],
responses: [
new OA\Response(response: Response::HTTP_NO_CONTENT, description: "No Content"),
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: "Not Found"),
new OA\Response(response: Response::HTTP_INTERNAL_SERVER_ERROR, description: "Server Error"),
]
)]
public function closeSubmissionPeriod($summit_id, $presentation_id)
{
return $this->processRequest(function () use ($summit_id, $presentation_id) {

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

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

$isAdmin = $current_member->isAdmin()
|| $current_member->hasPermissionForOnGroup($summit, IGroup::SummitAdministrators);
if (!$isAdmin) return $this->error403();

$this->presentation_submission_reopen_service->closeNow(
$summit, intval($presentation_id), $current_member
);

return $this->deleted();
});
}

#[OA\Put(
path: "/api/v1/summits/{id}/presentations/{presentation_id}/completed",
summary: "Mark a presentation submission as completed",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
use models\summit\ISummitEventRepository;
use models\summit\ISummitRepository;
use models\summit\PresentationSpeaker;
use ModelSerializers\IPresentationSerializerTypes;
use ModelSerializers\ISerializerTypeSelector;
use ModelSerializers\SerializerRegistry;
use services\model\ISpeakerService;
Expand Down Expand Up @@ -2342,7 +2343,9 @@ public function getMySpeakerPresentationsByRoleAndBySelectionPlan($role, $select
return $this->ok($response->toArray(
SerializerUtils::getExpand(),
SerializerUtils::getFields(),
SerializerUtils::getRelations()
SerializerUtils::getRelations(),
[],
IPresentationSerializerTypes::Submission
));
});
}
Expand Down Expand Up @@ -2449,7 +2452,9 @@ public function getMySpeakerPresentationsByRoleAndBySummit($role, $summit_id)
return $this->ok($response->toArray(
SerializerUtils::getExpand(),
SerializerUtils::getFields(),
SerializerUtils::getRelations()
SerializerUtils::getRelations(),
[],
IPresentationSerializerTypes::Submission
));
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ class AdminPresentationSerializer extends PresentationSerializer
'OverflowStreamIsSecure' => 'overflow_stream_is_secure:json_boolean',
'OverflowStreamKey' => 'overflow_stream_key:json_string',
'TrackChairAvgScoresPerRakingType' => 'track_chair_scores_avg:json_string_array',
'SubmissionReopenedUntil' => 'submission_reopened_until:datetime_epoch',
'SubmissionReopenedById' => 'submission_reopened_by_id:json_int',
'SubmissionReopenedByNice' => 'submission_reopened_by:json_string',
];

protected static $allowed_fields = [
Expand All @@ -64,7 +67,10 @@ class AdminPresentationSerializer extends PresentationSerializer
'etherpad_link',
'overflow_streaming_url',
'overflow_stream_is_secure',
'overflow_stream_key'
'overflow_stream_key',
'submission_reopened_until',
'submission_reopened_by_id',
'submission_reopened_by',
];

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@
*/
class SubmissionPresentationSerializer extends PresentationSerializer
{
protected static $array_mappings = [
'SubmissionReopenedUntil' => 'submission_reopened_until:datetime_epoch',
];

protected static $allowed_fields = [
'submission_reopened_until',
];

/**
* @param string|null $relation
* @return string
Expand Down
Loading
Loading