0

I am making an Ionic app with Drupal as the backend. Users log in via JWT. I'm using JSON:API to send data to Ionic. Now I want to allow users to cancel their accounts in the app (by JSON:API) instead of forcing them to log into the Drupal website and cancel there.

How can I do that?

1 Answer 1

0

This method uses the JSON:API Resources module.

It assumes:

  • Upon cancellation, blocking the user is OK. (Admins will manually delete the accounts completely later.)
  • Logged-in users will simply make a GET request to www.example.com/jsonapi/MYMODULE/cancelaccount to cancel their accounts (the app will provide an "Are you sure?" dialog and so forth).

There is probably a better way to do this but I am sharing my solution here because a Google search didn't reveal anything on this topic.

What to do

First, create a custom module.

Then, in MYMODULE.routing.yml:

MYMODULE.user_cancel:
  path: '/%jsonapi%/MYMODULE/cancelaccount'
  defaults:
    _jsonapi_resource: Drupal\MYMODULE\Resource\MYMODULEUserCancel
  requirements:
    # Give all authenticated users permission.
    _permission: 'view own commerce_order'
  options:
    no_cache: 'TRUE

Next, in MYMODULE/src/Resource/MYMODULEUserCancel.php:

<?php

namespace Drupal\MYMODULE\Resource;

use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Logger\LoggerChannelFactory;
use Drupal\Core\Session\AccountInterface;
use Drupal\jsonapi\JsonApiResource\LinkCollection;
use Drupal\jsonapi\JsonApiResource\ResourceObject;
use Drupal\jsonapi\JsonApiResource\ResourceObjectData;
use Drupal\jsonapi\ResourceResponse;
use Drupal\jsonapi\ResourceType\ResourceType;
use Drupal\jsonapi\ResourceType\ResourceTypeAttribute;
use Drupal\jsonapi_resources\Resource\EntityQueryResourceBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;

/**
 * Processes a request for a collection containing a user's article nodes.
 *
 * @internal
 */
class MYMODULEUserCancel extends EntityQueryResourceBase implements ContainerInjectionInterface {

  /**
   * The current user account.
   *
   * @var \Drupal\Core\Session\AccountInterface
   */
  protected AccountInterface $currentUser;

  /**
   * Logger factory.
   *
   * @var \Drupal\Core\Logger\LoggerChannelFactory
   */
  protected $loggerFactory;

  /**
   * Constructs a new EntityResourceBase object.
   *
   * @param \Drupal\Core\Session\AccountInterface $account
   *   The current user.
   * @param \Drupal\Core\Logger\LoggerChannelFactory $logger_factory
   *   The Watchdog logger.
   */
  public function __construct(AccountInterface $account, LoggerChannelFactory $logger_factory) {
    $this->currentUser = $account;
    $this->loggerFactory = $logger_factory->get('MYMODULE');
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('current_user'),
      $container->get('logger.factory'),
    );
  }

  /**
   * Process the resource request.
   *
   * Code example:
   * https://git.drupalcode.org/project/jsonapi_resources/tree/9.x-1.x/tests/modules/jsonapi_resources_test/src/Resource.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request.
   * @param \Drupal\jsonapi\ResourceType\ResourceType[] $resource_types
   *   The route resource types.
   *
   * @return \Drupal\jsonapi\ResourceResponse
   *   The response.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function process(Request $request, array $resource_types): ResourceResponse {
    $uid = $this->currentUser->id();
    // Make sure that we do not try to cancel user 1 or anonymous.
    if ($uid !== 1 && $uid !== 0) {
      $user_storage = $this->entityTypeManager->getStorage('user');
      $current_user = $user_storage->load($uid);
      // See: https://drupal.stackexchange.com/a/204422.
      // Send the user the "account blocked" confirmation email.
      _user_mail_notify('status_blocked', $current_user);
      $current_user->set('status', FALSE);
      $current_user->save();
      $this->loggerFactory->notice("User account for UID @uid cancelled by JSON:API.", [
        '@uid' => $current_user->id(),
      ]);
    }

    // Some data to return with JSON:API.
    $custom_field_data = [
      'cancelled' => TRUE,
    ];

    $links = new LinkCollection([]);
    $resource_type = reset($resource_types);
    // To disable caching, set metadata.
    $cacheability = new CacheableMetadata();
    $cacheability->setCacheMaxAge(1);
    $primary_data = new ResourceObject(
      $cacheability,
      $resource_type,
      $current_user->uuid(),
      NULL,
      $custom_field_data,
      $links
    );
    $top_level_data = new ResourceObjectData([$primary_data], 2);
    $response = $this->createJsonapiResponse($top_level_data, $request, 201, []);
    $response->addCacheableDependency($cacheability);

    return $response;
  }

  /**
   * {@inheritdoc}
   */
  public function getRouteResourceTypes(Route $route, string $route_name): array {
    $fields = [
      'drupalUid' => new ResourceTypeAttribute('drupalUid'),
      'status' => new ResourceTypeAttribute('status'),
    ];
    $resource_type = new ResourceType('current_user', 'current_user', NULL, FALSE, TRUE, TRUE, FALSE, $fields);
    // @todo Add role entities as a relatable resource type.
    $resource_type->setRelatableResourceTypes([]);
    return [$resource_type];
  }

}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.