Sync user absence / presence within MapAndSubscribeUserCalendarCommand

This commit is contained in:
Julien Fastré 2023-07-06 21:33:01 +02:00
parent 2861945a52
commit 77d4b13c1b
Signed by: julienfastre
GPG Key ID: BDE2190974723FCB
5 changed files with 109 additions and 87 deletions

View File

@ -18,9 +18,12 @@ declare(strict_types=1);
namespace Chill\CalendarBundle\Command;
use Chill\CalendarBundle\Exception\UserAbsenceSyncException;
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\EventsOnUserSubscriptionCreator;
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MapCalendarToUser;
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MSGraphUserRepository;
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MSUserAbsenceSync;
use Chill\MainBundle\Repository\UserRepositoryInterface;
use DateInterval;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
@ -30,32 +33,17 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class MapAndSubscribeUserCalendarCommand extends Command
final class MapAndSubscribeUserCalendarCommand extends Command
{
private EntityManagerInterface $em;
private EventsOnUserSubscriptionCreator $eventsOnUserSubscriptionCreator;
private LoggerInterface $logger;
private MapCalendarToUser $mapCalendarToUser;
private MSGraphUserRepository $userRepository;
public function __construct(
EntityManagerInterface $em,
EventsOnUserSubscriptionCreator $eventsOnUserSubscriptionCreator,
LoggerInterface $logger,
MapCalendarToUser $mapCalendarToUser,
MSGraphUserRepository $userRepository
private readonly EntityManagerInterface $em,
private readonly EventsOnUserSubscriptionCreator $eventsOnUserSubscriptionCreator,
private readonly LoggerInterface $logger,
private readonly MapCalendarToUser $mapCalendarToUser,
private readonly UserRepositoryInterface $userRepository,
private readonly MSUserAbsenceSync $userAbsenceSync,
) {
parent::__construct('chill:calendar:msgraph-user-map-subscribe');
$this->em = $em;
$this->eventsOnUserSubscriptionCreator = $eventsOnUserSubscriptionCreator;
$this->logger = $logger;
$this->mapCalendarToUser = $mapCalendarToUser;
$this->userRepository = $userRepository;
}
public function execute(InputInterface $input, OutputInterface $output): int
@ -67,83 +55,109 @@ class MapAndSubscribeUserCalendarCommand extends Command
/** @var DateInterval $interval the interval before the end of the expiration */
$interval = new DateInterval('P1D');
$expiration = (new DateTimeImmutable('now'))->add(new DateInterval($input->getOption('subscription-duration')));
$total = $this->userRepository->countByMostOldSubscriptionOrWithoutSubscriptionOrData($interval);
$users = $this->userRepository->findAllAsArray('fr');
$created = 0;
$renewed = 0;
$this->logger->info(self::class . ' the number of user to get - renew', [
'total' => $total,
$this->logger->info(self::class . ' start user to get - renew', [
'expiration' => $expiration->format(DateTimeImmutable::ATOM),
]);
while ($offset < $total) {
$users = $this->userRepository->findByMostOldSubscriptionOrWithoutSubscriptionOrData(
$interval,
$limit,
$offset
);
foreach ($users as $u) {
++$offset;
foreach ($users as $user) {
if (!$this->mapCalendarToUser->hasUserId($user)) {
$this->mapCalendarToUser->writeMetadata($user);
}
if ($this->mapCalendarToUser->hasUserId($user)) {
// we first try to renew an existing subscription, if any.
// if not, or if it fails, we try to create a new one
if ($this->mapCalendarToUser->hasActiveSubscription($user)) {
$this->logger->debug(self::class . ' renew a subscription for', [
'userId' => $user->getId(),
'username' => $user->getUsernameCanonical(),
]);
['secret' => $secret, 'id' => $id, 'expiration' => $expirationTs]
= $this->eventsOnUserSubscriptionCreator->renewSubscriptionForUser($user, $expiration);
$this->mapCalendarToUser->writeSubscriptionMetadata($user, $expirationTs, $id, $secret);
if (0 !== $expirationTs) {
++$renewed;
} else {
$this->logger->warning(self::class . ' could not renew subscription for a user', [
'userId' => $user->getId(),
'username' => $user->getUsernameCanonical(),
]);
}
}
if (!$this->mapCalendarToUser->hasActiveSubscription($user)) {
$this->logger->debug(self::class . ' create a subscription for', [
'userId' => $user->getId(),
'username' => $user->getUsernameCanonical(),
]);
['secret' => $secret, 'id' => $id, 'expiration' => $expirationTs]
= $this->eventsOnUserSubscriptionCreator->createSubscriptionForUser($user, $expiration);
$this->mapCalendarToUser->writeSubscriptionMetadata($user, $expirationTs, $id, $secret);
if (0 !== $expirationTs) {
++$created;
} else {
$this->logger->warning(self::class . ' could not create subscription for a user', [
'userId' => $user->getId(),
'username' => $user->getUsernameCanonical(),
]);
}
}
}
++$offset;
if (false === $u['enabled']) {
continue;
}
$this->em->flush();
$this->em->clear();
$user = $this->userRepository->find($u['id']);
if (null === $user) {
$this->logger->error("could not find user by id", ['uid' => $u['id']]);
$output->writeln("could not find user by id : " . $u['id']);
continue;
}
if (!$this->mapCalendarToUser->hasUserId($user)) {
$user = $this->mapCalendarToUser->writeMetadata($user);
// if user still does not have userid, continue
if (!$this->mapCalendarToUser->hasUserId($user)) {
$this->logger->warning("user does not have a counterpart on ms api", ['userId' => $user->getId(), 'email' => $user->getEmail()]);
$output->writeln(sprintf("giving up for user with email %s and id %s", $user->getEmail(), $user->getId()));
continue;
}
}
// sync user absence
try {
$this->userAbsenceSync->syncUserAbsence($user);
} catch (UserAbsenceSyncException $e) {
$this->logger->error("could not sync user absence", ['userId' => $user->getId(), 'email' => $user->getEmail(), 'exception' => $e->getTraceAsString(), "message" => $e->getMessage()]);
$output->writeln(sprintf("Could not sync user absence: id: %s and email: %s", $user->getId(), $user->getEmail()));
throw $e;
}
// we first try to renew an existing subscription, if any.
// if not, or if it fails, we try to create a new one
if ($this->mapCalendarToUser->hasActiveSubscription($user)) {
$this->logger->debug(self::class . ' renew a subscription for', [
'userId' => $user->getId(),
'username' => $user->getUsernameCanonical(),
]);
['secret' => $secret, 'id' => $id, 'expiration' => $expirationTs]
= $this->eventsOnUserSubscriptionCreator->renewSubscriptionForUser($user, $expiration);
$this->mapCalendarToUser->writeSubscriptionMetadata($user, $expirationTs, $id, $secret);
if (0 !== $expirationTs) {
++$renewed;
} else {
$this->logger->warning(self::class . ' could not renew subscription for a user', [
'userId' => $user->getId(),
'username' => $user->getUsernameCanonical(),
]);
}
}
if (!$this->mapCalendarToUser->hasActiveSubscription($user)) {
$this->logger->debug(self::class . ' create a subscription for', [
'userId' => $user->getId(),
'username' => $user->getUsernameCanonical(),
]);
['secret' => $secret, 'id' => $id, 'expiration' => $expirationTs]
= $this->eventsOnUserSubscriptionCreator->createSubscriptionForUser($user, $expiration);
$this->mapCalendarToUser->writeSubscriptionMetadata($user, $expirationTs, $id, $secret);
if (0 !== $expirationTs) {
++$created;
} else {
$this->logger->warning(self::class . ' could not create subscription for a user', [
'userId' => $user->getId(),
'username' => $user->getUsernameCanonical(),
]);
}
}
if (0 === $offset % $limit) {
$this->em->flush();
$this->em->clear();
}
}
$this->em->flush();
$this->em->clear();
$this->logger->warning(self::class . ' process executed', [
'created' => $created,
'renewed' => $renewed,
]);
$output->writeln("users synchronized");
return 0;
}
@ -152,7 +166,7 @@ class MapAndSubscribeUserCalendarCommand extends Command
parent::configure();
$this
->setDescription('MSGraph: collect user metadata and create subscription on events for users')
->setDescription('MSGraph: collect user metadata and create subscription on events for users, and sync the user absence-presence')
->addOption(
'renew-before-end-interval',
'r',

View File

@ -65,7 +65,7 @@ class MSGraphUserRepository
}
/**
* @return array|User[]
* @return array<User>
*/
public function findByMostOldSubscriptionOrWithoutSubscriptionOrData(DateInterval $interval, int $limit = 50, int $offset = 0): array
{

View File

@ -13,6 +13,7 @@ namespace Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph;
use Chill\CalendarBundle\Exception\UserAbsenceSyncException;
use Chill\MainBundle\Entity\User;
use Psr\Log\LoggerInterface;
use Symfony\Component\Clock\ClockInterface;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
@ -43,7 +44,7 @@ final readonly class MSUserAbsenceReader implements MSUserAbsenceReaderInterface
try {
$automaticRepliesSettings = $this->machineHttpClient
->request('GET', '/users/' . $id . '/mailboxSettings/automaticRepliesSetting')
->request('GET', 'users/' . $id . '/mailboxSettings/automaticRepliesSetting')
->toArray(true);
} catch (ClientExceptionInterface|DecodingExceptionInterface|RedirectionExceptionInterface|TransportExceptionInterface $e) {
throw new UserAbsenceSyncException("Error receiving response for mailboxSettings", 0, $e);

View File

@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph;
use Chill\MainBundle\Entity\User;
use Psr\Log\LoggerInterface;
use Symfony\Component\Clock\ClockInterface;
readonly class MSUserAbsenceSync
@ -19,6 +20,7 @@ readonly class MSUserAbsenceSync
public function __construct(
private MSUserAbsenceReaderInterface $absenceReader,
private ClockInterface $clock,
private LoggerInterface $logger,
) {
}
@ -35,9 +37,13 @@ readonly class MSUserAbsenceSync
return;
}
$this->logger->info("will change user absence", ['userId' => $user->getId()]);
if ($absence) {
$this->logger->debug("make user absent", ['userId' => $user->getId()]);
$user->setAbsenceStart($this->clock->now());
} else {
$this->logger->debug("make user present", ['userId' => $user->getId()]);
$user->setAbsenceStart(null);
}
}

View File

@ -17,6 +17,7 @@ use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MSUserAbsenceSync;
use Chill\MainBundle\Entity\User;
use PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use Psr\Log\NullLogger;
use Symfony\Component\Clock\MockClock;
/**
@ -37,7 +38,7 @@ class MSUserAbsenceSyncTest extends TestCase
$clock = new MockClock(new \DateTimeImmutable('2023-07-01T12:00:00'));
$syncer = new MSUserAbsenceSync($userAbsenceReader->reveal(), $clock);
$syncer = new MSUserAbsenceSync($userAbsenceReader->reveal(), $clock, new NullLogger());
$syncer->syncUserAbsence($user);