-
Notifications
You must be signed in to change notification settings - Fork 168
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
PROD-31232 - Implement 'group create' event for the EDA
- Loading branch information
Showing
9 changed files
with
865 additions
and
0 deletions.
There are no files selected for viewing
147 changes: 147 additions & 0 deletions
147
modules/social_features/social_group/modules/social_group_flexible_group/asyncapi.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
channels: | ||
groupCreate: | ||
address: com.getopensocial.cms.group.create | ||
messages: | ||
groupCreate: | ||
payload: | ||
allOf: | ||
- $ref: '#/components/schemas/cloudEventsSchema' | ||
- type: object | ||
properties: | ||
data: | ||
type: object | ||
properties: | ||
id: | ||
type: string | ||
description: Unique group ID (UUIDv4) | ||
created: | ||
type: string | ||
format: date-time | ||
description: Creation time of the group | ||
updated: | ||
type: string | ||
format: date-time | ||
description: Last update time of the group | ||
status: | ||
type: string | ||
description: Status of the group (e.g., published or unpublished) | ||
enum: | ||
- published | ||
- unpublished | ||
label: | ||
type: string | ||
description: Label of the group | ||
visibility: | ||
type: object | ||
properties: | ||
type: | ||
type: string | ||
description: The visibility type. | ||
roles: | ||
type: array | ||
description: The list of roles ids. | ||
contentVisibility: | ||
type: object | ||
properties: | ||
method: | ||
type: string | ||
description: Visibility of content | ||
enum: | ||
- public | ||
- community | ||
- group | ||
membership: | ||
type: object | ||
properties: | ||
method: | ||
type: string | ||
description: Method of membership | ||
enum: | ||
- open | ||
- invite | ||
- request | ||
type: | ||
type: string | ||
nullable: true | ||
description: Type of the group (optional) | ||
address: | ||
type: object | ||
properties: | ||
label: | ||
type: string | ||
nullable: true | ||
description: Label for the address (optional) | ||
countryCode: | ||
type: string | ||
nullable: true | ||
description: Country code (optional) | ||
administrativeArea: | ||
type: string | ||
nullable: true | ||
description: Administrative area (optional) | ||
locality: | ||
type: string | ||
nullable: true | ||
description: Locality (optional) | ||
dependentLocality: | ||
type: string | ||
nullable: true | ||
description: Dependent locality (optional) | ||
postalCode: | ||
type: string | ||
nullable: true | ||
description: Postal code (optional) | ||
sortingCode: | ||
type: string | ||
nullable: true | ||
description: Sorting code (optional) | ||
addressLine1: | ||
type: string | ||
nullable: true | ||
description: Address line 1 (optional) | ||
addressLine2: | ||
type: string | ||
nullable: true | ||
description: Address line 2 (optional) | ||
href: | ||
type: object | ||
properties: | ||
canonical: | ||
type: string | ||
format: uri | ||
description: Canonical URL for the group | ||
actor: | ||
type: object | ||
properties: | ||
application: | ||
type: object | ||
nullable: true | ||
properties: | ||
id: | ||
type: string | ||
description: Application ID (UUIDv4) | ||
name: | ||
type: string | ||
description: Name of the application | ||
user: | ||
type: object | ||
nullable: true | ||
properties: | ||
id: | ||
type: string | ||
description: Actor user ID (UUIDv4) | ||
displayName: | ||
type: string | ||
description: Display name of the actor user | ||
href: | ||
type: object | ||
properties: | ||
canonical: | ||
type: string | ||
format: uri | ||
description: Canonical URL for the actor profile | ||
operations: | ||
onGroupCreate: | ||
action: 'receive' | ||
channel: | ||
$ref: '#/channels/groupCreate' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
216 changes: 216 additions & 0 deletions
216
modules/social_features/social_group/modules/social_group_flexible_group/src/EdaHandler.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
<?php | ||
|
||
namespace Drupal\social_group_flexible_group; | ||
|
||
use CloudEvents\V1\CloudEvent; | ||
use Drupal\Component\Uuid\UuidInterface; | ||
use Drupal\Core\Config\ConfigFactoryInterface; | ||
use Drupal\Core\Entity\EntityTypeManagerInterface; | ||
use Drupal\Core\Extension\ModuleHandlerInterface; | ||
use Drupal\Core\Routing\RouteMatchInterface; | ||
use Drupal\Core\Session\AccountProxyInterface; | ||
use Drupal\group\Entity\GroupInterface; | ||
use Drupal\social_eda\DispatcherInterface; | ||
use Drupal\social_eda\Types\Address; | ||
use Drupal\social_eda\Types\Application; | ||
use Drupal\social_eda\Types\DateTime; | ||
use Drupal\social_eda\Types\Href; | ||
use Drupal\social_eda\Types\User; | ||
use Drupal\social_group_flexible_group\Event\GroupEntityData; | ||
use Drupal\social_group_flexible_group\Types\GroupMembershipMethod; | ||
use Drupal\social_group_flexible_group\Types\GroupVisibility; | ||
use Drupal\user\UserInterface; | ||
use Symfony\Component\HttpFoundation\RequestStack; | ||
|
||
/** | ||
* Handles hook invocations for EDA related operations of the event entity. | ||
*/ | ||
final class EdaHandler { | ||
|
||
/** | ||
* The current logged-in user. | ||
* | ||
* @var \Drupal\user\UserInterface|null | ||
*/ | ||
protected ?UserInterface $currentUser = NULL; | ||
|
||
/** | ||
* The source. | ||
* | ||
* @var string | ||
*/ | ||
protected string $source; | ||
|
||
/** | ||
* The current route name. | ||
* | ||
* @var string | ||
*/ | ||
protected string $routeName; | ||
|
||
/** | ||
* The community namespace. | ||
* | ||
* @var string | ||
*/ | ||
protected string $namespace; | ||
|
||
/** | ||
* The topic name. | ||
* | ||
* @var string | ||
*/ | ||
protected string $topicName; | ||
|
||
/** | ||
* {@inheritDoc} | ||
*/ | ||
public function __construct( | ||
private readonly ?DispatcherInterface $dispatcher, | ||
private readonly UuidInterface $uuid, | ||
private readonly RequestStack $requestStack, | ||
private readonly ModuleHandlerInterface $moduleHandler, | ||
private readonly EntityTypeManagerInterface $entityTypeManager, | ||
private readonly AccountProxyInterface $account, | ||
private readonly RouteMatchInterface $routeMatch, | ||
private readonly ConfigFactoryInterface $configFactory, | ||
) { | ||
// Load the full user entity if the account is authenticated. | ||
$account_id = $this->account->id(); | ||
if ($account_id && $account_id !== 0) { | ||
$user = $this->entityTypeManager->getStorage('user')->load($account_id); | ||
if ($user instanceof UserInterface) { | ||
$this->currentUser = $user; | ||
} | ||
} | ||
|
||
// Set source. | ||
$request = $this->requestStack->getCurrentRequest(); | ||
$this->source = $request ? $request->getPathInfo() : ''; | ||
|
||
// Set route name. | ||
$this->routeName = $this->routeMatch->getRouteName() ?: ''; | ||
|
||
// Set the community namespace. | ||
$this->namespace = $this->configFactory->get('social_eda.settings')->get('namespace') ?? 'com.getopensocial'; | ||
|
||
// Set the community namespace. | ||
$this->topicName = "{$this->namespace}.cms.group.v1"; | ||
} | ||
|
||
/** | ||
* Create event handler. | ||
*/ | ||
public function groupCreate(GroupInterface $group): void { | ||
$this->dispatch( | ||
topic_name: $this->topicName, | ||
event_type: "{$this->namespace}.cms.group.create", | ||
group: $group | ||
); | ||
} | ||
|
||
/** | ||
* Transforms a GroupInterface into a CloudEvent. | ||
* | ||
* @throws \Drupal\Core\Entity\EntityMalformedException | ||
* @throws \Drupal\Core\TypedData\Exception\MissingDataException | ||
*/ | ||
public function fromEntity(GroupInterface $group, string $event_type, string $op = ''): CloudEvent { | ||
// Determine actors. | ||
[$actor_application, $actor_user] = $this->determineActors(); | ||
|
||
// Determine status. | ||
if ($op == 'delete') { | ||
$status = 'removed'; | ||
} | ||
else { | ||
$status = $group->get('status')->value ? 'published' : 'unpublished'; | ||
} | ||
|
||
return new CloudEvent( | ||
id: $this->uuid->generate(), | ||
source: $this->source, | ||
type: $event_type, | ||
data: [ | ||
'group' => new GroupEntityData( | ||
id: $group->get('uuid')->value, | ||
created: DateTime::fromTimestamp($group->getCreatedTime())->toString(), | ||
updated: DateTime::fromTimestamp($group->getChangedTime())->toString(), | ||
status: $status, | ||
label: (string) $group->label(), | ||
visibility: GroupVisibility::fromEntity($group), | ||
contentVisibility: $group->get('field_group_allowed_visibility')->value, | ||
membership: GroupMembershipMethod::fromEntity($group), | ||
type: $group->getGroupType()->get('uuid'), | ||
author: User::fromEntity($group->get('uid')->entity), | ||
address: Address::fromFieldItem( | ||
item: $group->get('field_group_address')->first(), | ||
label: $group->get('field_group_location')->value | ||
), | ||
href: Href::fromEntity($group), | ||
), | ||
'actor' => [ | ||
'application' => $actor_application ? Application::fromId($actor_application) : NULL, | ||
'user' => $actor_user ? User::fromEntity($actor_user) : NULL, | ||
], | ||
], | ||
dataContentType: 'application/json', | ||
dataSchema: NULL, | ||
subject: NULL, | ||
time: DateTime::fromTimestamp($group->getCreatedTime())->toImmutableDateTime(), | ||
); | ||
} | ||
|
||
/** | ||
* Determines the actor (application and user) for the CloudEvent. | ||
* | ||
* @return array | ||
* An array with two elements: the application and the user. | ||
*/ | ||
private function determineActors(): array { | ||
$application = NULL; | ||
$user = NULL; | ||
|
||
if ($this->currentUser instanceof UserInterface) { | ||
$user = $this->currentUser; | ||
} | ||
|
||
if ($this->routeName == 'entity.ultimate_cron_job.run') { | ||
$application = 'cron'; | ||
} | ||
|
||
return [ | ||
$application, | ||
$user, | ||
]; | ||
} | ||
|
||
/** | ||
* Dispatches the event. | ||
* | ||
* @param string $topic_name | ||
* The topic name. | ||
* @param string $event_type | ||
* The event type. | ||
* @param \Drupal\group\Entity\GroupInterface $group | ||
* The group object. | ||
* @param string $op | ||
* The operation. | ||
* | ||
* @throws \Drupal\Core\Entity\EntityMalformedException | ||
* @throws \Drupal\Core\TypedData\Exception\MissingDataException | ||
*/ | ||
private function dispatch(string $topic_name, string $event_type, GroupInterface $group, string $op = ''): void { | ||
// Skip if required modules are not enabled. | ||
if (!$this->moduleHandler->moduleExists('social_eda') || !$this->dispatcher) { | ||
return; | ||
} | ||
|
||
// Build the event. | ||
$event = $this->fromEntity($group, $event_type, $op); | ||
|
||
// Dispatch to message broker. | ||
$this->dispatcher->dispatch($topic_name, $event); | ||
} | ||
|
||
} |
Oops, something went wrong.