Skip to content

Commit

Permalink
PROD-31232 - Implement 'group create' event for the EDA
Browse files Browse the repository at this point in the history
  • Loading branch information
nkoporec authored and ribel committed Nov 21, 2024
1 parent c055472 commit 2950518
Show file tree
Hide file tree
Showing 9 changed files with 865 additions and 0 deletions.
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'
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,15 @@ function social_group_flexible_group_group_access(EntityInterface $entity, $oper
return $result;
}

/**
* Implements hook_ENTITY_TYPE_insert().
*/
function social_group_flexible_group_group_insert(GroupInterface $group): void {
if ($group->bundle() == "flexible_group") {
\Drupal::service('social_group_flexible_group.eda_handler')->groupCreate($group);
}
}

/**
* Implements template_preprocess_form_element().
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,9 @@ services:
tags:
- { name: config.factory.override, priority: 5 }
- { name: social_language_defaults }

social_group_flexible_group.eda_handler:
autowire: true
class: Drupal\social_group_flexible_group\EdaHandler
tags:
- { name: social.eda.handler }
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);
}

}
Loading

0 comments on commit 2950518

Please sign in to comment.