feat(cloudron): add tirreno package artifacts

- Add CloudronStack/output/CloudronPackages-Artifacts/tirreno/ directory and its contents
- Includes package manifest, Dockerfile, source code, documentation, and build artifacts
- Add tirreno-1761840148.tar.gz as a build artifact
- Add tirreno-cloudron-package-1761841304.tar.gz as the Cloudron package
- Include all necessary files for the tirreno Cloudron package

This adds the complete tirreno Cloudron package artifacts to the repository.
This commit is contained in:
2025-10-30 11:43:06 -05:00
parent 0ce353ea9d
commit 91d52d2de5
1692 changed files with 202851 additions and 0 deletions

View File

@@ -0,0 +1,215 @@
<?php
/**
* Tirreno ~ Open source user analytics
* Copyright (c) Tirreno Technologies Sàrl (https://www.tirreno.com)
*
* Licensed under GNU Affero General Public License version 3 of the or any later version.
* For full copyright and license information, please see the LICENSE
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Tirreno Technologies Sàrl (https://www.tirreno.com)
* @license https://opensource.org/licenses/AGPL-3.0 AGPL License
* @link https://www.tirreno.com Tirreno(tm)
*/
declare(strict_types=1);
namespace Sensor\Factory;
use Sensor\Model\Enriched\EnrichedData;
use Sensor\Model\Enriched\DomainEnriched;
use Sensor\Model\Enriched\DomainNotFoundEnriched;
use Sensor\Model\Enriched\DomainEnrichFailed;
use Sensor\Model\Enriched\EmailEnriched;
use Sensor\Model\Enriched\EmailEnrichFailed;
use Sensor\Model\Enriched\IpAddressEnriched;
use Sensor\Model\Enriched\IpAddressLocalhostEnriched;
use Sensor\Model\Enriched\IpAddressEnrichFailed;
use Sensor\Model\Enriched\IspEnriched;
use Sensor\Model\Enriched\IspEnrichedEmpty;
use Sensor\Model\Enriched\IspEnrichedLocalhost;
use Sensor\Model\Enriched\PhoneEnriched;
use Sensor\Model\Enriched\PhoneInvalidEnriched;
use Sensor\Model\Enriched\PhoneEnrichFailed;
use Sensor\Service\Enrichment\DataEnrichmentClientInterface;
use Sensor\Service\Logger;
/**
* @phpstan-import-type EnrichmentClientResponse from DataEnrichmentClientInterface
*/
class EnrichedDataFactory {
private const IP_IS_BOGON_ERROR_TEXT = 'IP is bogon';
private const VALIDATION_ERROR_TEXT = 'Validation failed';
private const SERVER_ERROR = 'Server Error';
public function __construct(
private Logger $logger,
) {
}
/**
* @phpstan-param EnrichmentClientResponse $data
*/
public function createFromResponse(array $data, array $origin): EnrichedData {
$email = null;
if (isset($data['email']['email'])) {
try {
$earliestBreach = $data['email']['earliest_breach'] !== null ? new \DateTimeImmutable($data['email']['earliest_breach']) : null;
$email = new EmailEnriched(
$data['email']['email'],
$data['email']['blockemails'],
$data['email']['data_breach'],
$data['email']['data_breaches'],
$earliestBreach,
$data['email']['profiles'],
$data['email']['domain_contact_email'],
$data['email']['alert_list'],
);
} catch (\Throwable $e) {
$this->logger->logWarning('Error during parsing email response', $e);
}
} elseif (isset($data['email']['value'])) {
$email = new EmailEnrichFailed(
$data['email']['error'] === self::SERVER_ERROR ? $origin['email'] : $data['email']['value'],
$data['email']['error'] === self::VALIDATION_ERROR_TEXT, // checked must be true on validation error to prevent repeating requests
);
}
$domain = null;
if (isset($data['domain']['domain']) && array_key_exists('ip', $data['domain'])) {
try {
$domain = new DomainEnriched(
$data['domain']['domain'],
$data['domain']['blockdomains'],
$data['domain']['disposable_domains'],
$data['domain']['free_email_provider'],
$data['domain']['ip'],
$data['domain']['geo_ip'],
$data['domain']['geo_html'],
$data['domain']['web_server'],
$data['domain']['hostname'],
$data['domain']['emails'],
$data['domain']['phone'],
$data['domain']['discovery_date'],
$data['domain']['tranco_rank'],
$data['domain']['creation_date'],
$data['domain']['expiration_date'],
$data['domain']['return_code'],
$data['domain']['disabled'],
$data['domain']['closest_snapshot'],
$data['domain']['mx_record'],
);
} catch (\Throwable $e) {
$this->logger->logWarning('Error during parsing domain response', $e);
}
} elseif (isset($data['domain']['domain'])) {
$domain = new DomainNotFoundEnriched(
$data['domain']['domain'],
$data['domain']['blockdomains'],
$data['domain']['disposable_domains'],
$data['domain']['free_email_provider'],
$data['domain']['creation_date'],
$data['domain']['expiration_date'],
$data['domain']['return_code'],
$data['domain']['disabled'],
$data['domain']['closest_snapshot'],
$data['domain']['mx_record'],
);
} elseif (isset($data['domain']['value'])) {
$domain = new DomainEnrichFailed(
$data['domain']['error'] === self::SERVER_ERROR ? $origin['domain'] : $data['domain']['value'],
$data['domain']['error'] === self::VALIDATION_ERROR_TEXT, // checked must be true on validation error to prevent repeating requests
);
}
$ip = $isp = null;
if (isset($data['ip']['ip'])) {
try {
$ip = new IpAddressEnriched(
$data['ip']['ip'],
$data['ip']['country'],
$data['ip']['hosting'],
$data['ip']['vpn'],
$data['ip']['tor'],
$data['ip']['relay'],
$data['ip']['starlink'],
$data['ip']['blocklist'],
$data['ip']['domains_count'],
$data['ip']['cidr'],
$data['ip']['alert_list'],
);
if (isset($data['ip']['asn'])) {
$isp = new IspEnriched(
$data['ip']['asn'],
$data['ip']['name'],
$data['ip']['description'],
);
} else {
$isp = new IspEnrichedEmpty();
}
} catch (\Throwable $e) {
$this->logger->logWarning('Error during parsing IP response', $e);
}
} elseif (isset($data['ip']['error'])) {
if ($data['ip']['error'] === self::IP_IS_BOGON_ERROR_TEXT) {
$ip = new IpAddressLocalhostEnriched(
$data['ip']['value'],
);
$isp = new IspEnrichedLocalhost();
} else {
$ip = new IpAddressEnrichFailed(
$data['ip']['error'] === self::SERVER_ERROR ? $origin['ip'] : $data['ip']['value'],
$data['ip']['error'] === self::VALIDATION_ERROR_TEXT, // checked must be true on validation error to prevent repeating requests
);
$isp = new IspEnrichedEmpty();
}
}
$phone = null;
if (isset($data['phone']['phone_number']) && isset($data['phone']['profiles'])) {
try {
$phone = new PhoneEnriched(
$data['phone']['phone_number'],
$data['phone']['profiles'],
$data['phone']['iso_country_code'],
$data['phone']['calling_country_code'],
$data['phone']['national_format'],
$data['phone']['invalid'],
$data['phone']['validation_error'],
$data['phone']['carrier_name'],
$data['phone']['type'],
$data['phone']['alert_list'],
);
} catch (\Throwable $e) {
$this->logger->logWarning('Error during parsing phone response', $e);
}
} elseif (isset($data['phone']['validation_error'])) {
$this->logger->logWarning('Error getting phone from Enrichment API: ' . json_encode($data['phone']));
$phone = new PhoneInvalidEnriched(
$data['phone']['phone_number'],
$data['phone']['invalid'],
$data['phone']['validation_error'],
);
} elseif (isset($data['phone']['value'])) {
$phone = new PhoneEnrichFailed(
$data['phone']['error'] === self::SERVER_ERROR ? $origin['phone'] : $data['phone']['value'],
$data['phone']['error'] === self::VALIDATION_ERROR_TEXT, // checked must be true on validation error to prevent repeating requests
);
}
// Check/log errors
foreach ($data as $key => $value) {
if (isset($value['error'])) {
$this->logger->logWarning(sprintf(
'Error getting %s from Enrichment API: %s',
$key,
json_encode($value),
));
}
}
return new EnrichedData($email, $domain, $ip, $isp, $phone, true);
}
}

View File

@@ -0,0 +1,393 @@
<?php
/**
* Tirreno ~ Open source user analytics
* Copyright (c) Tirreno Technologies Sàrl (https://www.tirreno.com)
*
* Licensed under GNU Affero General Public License version 3 of the or any later version.
* For full copyright and license information, please see the LICENSE
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Tirreno Technologies Sàrl (https://www.tirreno.com)
* @license https://opensource.org/licenses/AGPL-3.0 AGPL License
* @link https://www.tirreno.com Tirreno(tm)
*/
declare(strict_types=1);
namespace Sensor\Factory;
use Sensor\Entity\DeviceEntity;
use Sensor\Entity\DomainEnrichedEntity;
use Sensor\Entity\DomainEntity;
use Sensor\Entity\DomainNotFoundEntity;
use Sensor\Entity\EmailEnrichedEntity;
use Sensor\Entity\EmailEntity;
use Sensor\Entity\EventEntity;
use Sensor\Entity\IpAddressEnrichedEntity;
use Sensor\Entity\IpAddressLocalhostEnrichedEntity;
use Sensor\Entity\IpAddressEntity;
use Sensor\Entity\CountryEntity;
use Sensor\Entity\IspEntity;
use Sensor\Entity\IspEnrichedEntity;
use Sensor\Entity\IspLocalhostEntity;
use Sensor\Entity\PhoneEnrichedEntity;
use Sensor\Entity\PhoneEntity;
use Sensor\Entity\PhoneInvalidEntity;
use Sensor\Entity\RefererEntity;
use Sensor\Entity\SessionEntity;
use Sensor\Entity\UrlEntity;
use Sensor\Entity\UrlQueryEntity;
use Sensor\Entity\UserAgentEnrichedEntity;
use Sensor\Entity\UserAgentEntity;
use Sensor\Entity\PayloadEntity;
use Sensor\Model\Blacklist\FraudDetected;
use Sensor\Model\CreateEventDto;
use Sensor\Model\DeviceDetected;
use Sensor\Repository\CountryRepository;
use Sensor\Model\Enriched\EnrichedData;
use Sensor\Model\Enriched\IpAddressEnriched;
use Sensor\Model\Enriched\IpAddressLocalhostEnriched;
use Sensor\Model\Enriched\IpAddressEnrichFailed;
use Sensor\Model\Enriched\PhoneEnriched;
use Sensor\Model\Enriched\PhoneInvalidEnriched;
use Sensor\Model\Enriched\PhoneEnrichFailed;
use Sensor\Model\Enriched\DomainEnriched;
use Sensor\Model\Enriched\DomainNotFoundEnriched;
use Sensor\Model\Enriched\DomainEnrichFailed;
use Sensor\Model\Enriched\EmailEnriched;
use Sensor\Model\Enriched\EmailEnrichFailed;
class EventFactory {
public function __construct(
private CountryRepository $countryRepository,
) {
}
public function createFromDto(
int $accountId,
int $sessionId,
int $apiKeyId,
CreateEventDto $dto,
?EnrichedData $enrichedData,
FraudDetected $fraudDetected,
?DeviceDetected $deviceDetected,
?string $query,
): EventEntity {
$lastSeen = $dto->eventTime;
// Remove query from url
$urlWithoutQuery = $query !== null ? str_replace($query, '', $dto->url) : $dto->url;
$queryEntity = null;
if ($query !== null) {
$queryEntity = new UrlQueryEntity($apiKeyId, $query, $lastSeen);
}
$url = new UrlEntity($apiKeyId, $urlWithoutQuery, $queryEntity, $dto->pageTitle, $dto->httpCode, $lastSeen);
$eventType = $dto->eventType;
$httpMethod = $dto->httpMethod;
if ($dto->httpReferer !== null) {
$referer = new RefererEntity($apiKeyId, $dto->httpReferer, $lastSeen);
}
$session = new SessionEntity($sessionId, $accountId, $apiKeyId, $lastSeen);
// set countryId after inserting ip
$country = new CountryEntity($apiKeyId, 0, $lastSeen);
$ipAddress = null;
if ($dto->ipAddress->localhost) {
// sensor-defined localhost
$isp = new IspLocalhostEntity($apiKeyId, $lastSeen);
$ipAddress = new IpAddressLocalhostEnrichedEntity(
$apiKeyId,
$dto->ipAddress->value,
$dto->ipAddress->hash,
$fraudDetected->ip,
$isp,
$lastSeen,
);
} else {
if ($enrichedData?->ip instanceof IpAddressEnriched || $enrichedData?->ip instanceof IpAddressLocalhostEnriched) {
// isp IspEnriched or IspEnrichedEmpty
// it's a new ip, checked is true; should be INSERTed or UPDATEd
$countryId = $this->countryRepository->getCountryIdByCode($enrichedData->ip->countryCode);
$isp = new IspEnrichedEntity(
$apiKeyId,
$enrichedData->isp->asn,
$enrichedData->isp->name,
$enrichedData->isp->description,
$lastSeen,
);
$ipAddress = new IpAddressEnrichedEntity(
$apiKeyId,
$enrichedData->ip->ipAddress,
$dto->ipAddress->hash,
$countryId,
$enrichedData->ip->hosting,
$enrichedData->ip->vpn,
$enrichedData->ip->tor,
$enrichedData->ip->relay,
$enrichedData->ip->starlink,
$enrichedData->ip->blocklist,
$enrichedData->ip->domainsCount,
$enrichedData->ip->cidr,
$enrichedData->ip->alertList,
$fraudDetected->ip,
$isp,
true,
$lastSeen,
);
} elseif ($enrichedData?->ip instanceof IpAddressEnrichFailed) {
// checked can be false or true on bogon ip; should be INSERTed or UPDATEd (if it is reenrichment)
// isp made of IspEnrichedLocalhost or IspEnrichedEmpty
$isp = new IspEnrichedEntity(
$apiKeyId,
$enrichedData->isp->asn,
$enrichedData->isp->name,
$enrichedData->isp->description,
$lastSeen,
);
$ipAddress = new IpAddressEntity(
$apiKeyId,
$enrichedData->ip->ipAddress,
$dto->ipAddress->hash,
$fraudDetected->ip,
$isp,
$enrichedData->ip->checked,
$lastSeen,
);
} else {
// ip already exists and has checked true or enrichment is off; should be UPDATEd or INSERTed if enrichment is off
// isp unknown, N/A will be used if enrichment is off or failed
$isp = new IspEntity(
$apiKeyId,
$lastSeen,
);
$ipAddress = new IpAddressEntity(
$apiKeyId,
$dto->ipAddress->value,
$dto->ipAddress->hash,
$fraudDetected->ip,
$isp,
null, // enrichment is off or was enriched earlier
$lastSeen,
);
}
}
$domain = null;
if ($dto->emailDomain !== null) {
if ($enrichedData?->domain instanceof DomainEnriched) {
$domain = new DomainEnrichedEntity(
$apiKeyId,
$enrichedData->domain->domain,
$enrichedData->domain->blockdomains,
$enrichedData->domain->disposableDomains,
$enrichedData->domain->freeEmailProvider,
$enrichedData->domain->ip,
$enrichedData->domain->geoIp,
$enrichedData->domain->geoHtml,
$enrichedData->domain->webServer,
$enrichedData->domain->hostname,
$enrichedData->domain->emails,
$enrichedData->domain->phone,
$enrichedData->domain->discoveryDate,
$enrichedData->domain->trancoRank,
$enrichedData->domain->creationDate,
$enrichedData->domain->expirationDate,
$enrichedData->domain->returnCode,
$enrichedData->domain->disabled,
$enrichedData->domain->closestSnapshot,
$enrichedData->domain->mxRecord,
true,
$lastSeen,
);
} elseif ($enrichedData?->domain instanceof DomainNotFoundEnriched) {
$domain = new DomainNotFoundEntity(
$apiKeyId,
$enrichedData->domain->domain,
$enrichedData->domain->blockdomains,
$enrichedData->domain->disposableDomains,
$enrichedData->domain->freeEmailProvider,
$enrichedData->domain->creationDate,
$enrichedData->domain->expirationDate,
$enrichedData->domain->returnCode,
$enrichedData->domain->disabled,
$enrichedData->domain->closestSnapshot,
$enrichedData->domain->mxRecord,
true,
$lastSeen,
);
} elseif ($enrichedData?->domain instanceof DomainEnrichFailed) {
$domain = new DomainEntity(
$apiKeyId,
$enrichedData->domain->domain,
$enrichedData->domain->checked,
$lastSeen,
);
} else {
$domain = new DomainEntity(
$apiKeyId,
$dto->emailDomain,
null, // enrichment is off or was enriched earlier
$lastSeen,
);
}
}
$email = null;
if ($dto->emailAddress !== null && $domain !== null) {
if ($enrichedData?->email instanceof EmailEnriched) {
$email = new EmailEnrichedEntity(
$accountId,
$apiKeyId,
$enrichedData->email->email,
$dto->emailAddress->hash,
$domain,
$enrichedData->email->blockEmails,
$enrichedData->email->dataBreach,
$enrichedData->email->dataBreaches,
$enrichedData->email->earliestBreach,
$enrichedData->email->profiles,
$enrichedData->email->domainContactEmail,
$enrichedData->email->alertList,
$fraudDetected->email,
true,
$lastSeen,
);
} elseif ($enrichedData?->email instanceof EmailEnrichFailed) {
$email = new EmailEntity(
$accountId,
$apiKeyId,
$enrichedData->email->email,
$dto->emailAddress->hash,
$domain,
$fraudDetected->email,
$enrichedData->email->checked,
$lastSeen,
);
} else {
$email = new EmailEntity(
$accountId,
$apiKeyId,
$dto->emailAddress->value,
$dto->emailAddress->hash,
$domain,
$fraudDetected->email,
null, // enrichment is off or was enriched earlier
$lastSeen,
);
}
}
$phone = null;
if ($dto->phoneNumber !== null) {
$countryId = 0;
if ($enrichedData?->phone instanceof PhoneEnriched) {
if ($enrichedData->phone->countryCode !== null) {
$countryId = $this->countryRepository->getCountryIdByCode($enrichedData->phone->countryCode);
}
$phone = new PhoneEnrichedEntity(
$accountId,
$apiKeyId,
$enrichedData->phone->phoneNumber,
$dto->phoneNumber->hash,
$enrichedData->phone->profiles,
$countryId,
$enrichedData->phone->callingCountryCode,
$enrichedData->phone->nationalFormat,
$enrichedData->phone->invalid,
$enrichedData->phone->validationErrors,
$enrichedData->phone->carrierName,
$enrichedData->phone->type,
$enrichedData->phone->alertList,
$fraudDetected->phone,
true,
$lastSeen,
);
} elseif ($enrichedData?->phone instanceof PhoneInvalidEnriched) {
$phone = new PhoneInvalidEntity(
$accountId,
$apiKeyId,
$enrichedData->phone->phoneNumber,
$dto->phoneNumber->hash,
$countryId,
$fraudDetected->phone,
$enrichedData->phone->validationErrors,
true,
$lastSeen,
);
} elseif ($enrichedData?->phone instanceof PhoneEnrichFailed) {
$phone = new PhoneEntity(
$accountId,
$apiKeyId,
$enrichedData->phone->phoneNumber,
$dto->phoneNumber->hash,
$countryId,
$fraudDetected->phone,
$enrichedData->phone->checked,
$lastSeen,
);
} else {
$phone = new PhoneEntity(
$accountId,
$apiKeyId,
$dto->phoneNumber->value,
$dto->phoneNumber->hash,
$countryId, // set country to 0 if enrichment is off or keep existing country in query
$fraudDetected->phone,
null, // enrichment is off or was enriched earlier
$lastSeen,
);
}
}
$userAgent = null;
if ($deviceDetected instanceof DeviceDetected) {
$userAgent = new UserAgentEnrichedEntity(
$apiKeyId,
$deviceDetected->userAgent,
$deviceDetected->device,
$deviceDetected->browserName,
$deviceDetected->browserVersion,
$deviceDetected->osName,
$deviceDetected->osVersion,
$deviceDetected->modified,
true,
$lastSeen,
);
} else {
$userAgent = new UserAgentEntity(
$apiKeyId,
$dto->userAgent,
null, // ua is null, was enriched earlier or enrichment is off
$lastSeen,
);
}
$device = new DeviceEntity($accountId, $apiKeyId, $userAgent, $dto->browserLanguage, $lastSeen);
$payload = ($dto->payload) ? (new PayloadEntity($accountId, $apiKeyId, $dto->payload, $lastSeen)) : null;
return new EventEntity(
$accountId,
$session,
$apiKeyId,
$ipAddress,
$url,
$eventType,
$httpMethod,
$device,
$referer ?? null,
$email ?? null,
$phone ?? null,
$dto->httpCode,
$dto->eventTime,
$dto->traceId,
$payload,
$country,
);
}
}

View File

@@ -0,0 +1,86 @@
<?php
/**
* Tirreno ~ Open source user analytics
* Copyright (c) Tirreno Technologies Sàrl (https://www.tirreno.com)
*
* Licensed under GNU Affero General Public License version 3 of the or any later version.
* For full copyright and license information, please see the LICENSE
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Tirreno Technologies Sàrl (https://www.tirreno.com)
* @license https://opensource.org/licenses/AGPL-3.0 AGPL License
* @link https://www.tirreno.com Tirreno(tm)
*/
namespace Sensor\Factory;
use Sensor\Entity\LogbookEntity;
use Sensor\Model\Http\ErrorResponse;
use Sensor\Model\Http\RegularResponse;
use Sensor\Model\Http\Request;
use Sensor\Model\Http\ValidationFailedResponse;
use Sensor\Repository\ApiKeyRepository;
class LogbookEntityFactory {
public function create(
int $apiKeyId,
\DateTime $startedTime,
RegularResponse|ErrorResponse $response,
): LogbookEntity {
$eventId = null;
if ($response instanceof RegularResponse) {
$errorText = $response->validationErrors();
$errorType = $errorText !== null
? LogbookEntity::ERROR_TYPE_VALIDATION_ERROR
: LogbookEntity::ERROR_TYPE_SUCCESS
;
$eventId = $response->eventId;
} elseif ($response instanceof ValidationFailedResponse) {
$errorType = LogbookEntity::ERROR_TYPE_CRITICAL_VALIDATION_ERROR;
$errorText = json_encode([$response->jsonSerialize()]);
} elseif ($response instanceof ErrorResponse) {
$errorType = LogbookEntity::ERROR_TYPE_CRITICAL_ERROR;
$errorText = json_encode([$response->jsonSerialize()]);
} else {
$errorType = LogbookEntity::ERROR_TYPE_CRITICAL_ERROR;
$errorText = json_encode(['Undefined error']);
}
return new LogbookEntity(
$apiKeyId,
$_SERVER['REMOTE_ADDR'],
$eventId,
$errorType,
$errorText,
$this->getRawRequest(),
$this->formatStarted($startedTime),
);
}
public function createFromException(
int $apiKeyId,
\DateTime $startedTime,
string $errorText,
): LogbookEntity {
return new LogbookEntity(
$apiKeyId,
$_SERVER['REMOTE_ADDR'],
null,
3,
$errorText,
$this->getRawRequest(),
$this->formatStarted($startedTime),
);
}
private function getRawRequest(): string {
return json_encode(array_intersect_key($_POST, array_flip(Request::ACCEPTABLE_FIELDS)));
}
private function formatStarted(\DateTime $startedTime): string {
$milliseconds = (int) ($startedTime->format('u') / 1000);
return $startedTime->format('Y-m-d H:i:s') . '.' . sprintf('%03d', $milliseconds);
}
}

View File

@@ -0,0 +1,158 @@
<?php
/**
* Tirreno ~ Open source user analytics
* Copyright (c) Tirreno Technologies Sàrl (https://www.tirreno.com)
*
* Licensed under GNU Affero General Public License version 3 of the or any later version.
* For full copyright and license information, please see the LICENSE
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Tirreno Technologies Sàrl (https://www.tirreno.com)
* @license https://opensource.org/licenses/AGPL-3.0 AGPL License
* @link https://www.tirreno.com Tirreno(tm)
*/
declare(strict_types=1);
namespace Sensor\Factory;
use Sensor\Exception\ValidationException;
use Sensor\Model\CreateEventDto;
use Sensor\Model\HashedValue;
use Sensor\Model\Validated\Email;
use Sensor\Model\Validated\IpAddress;
use Sensor\Model\Validated\Phone;
use Sensor\Model\Validated\Timestamp;
use Sensor\Model\Validated\HttpCode;
use Sensor\Model\Validated\Firstname;
use Sensor\Model\Validated\Lastname;
use Sensor\Model\Validated\Fullname;
use Sensor\Model\Validated\HttpReferer;
use Sensor\Model\Validated\Userid;
use Sensor\Model\Validated\PageTitle;
use Sensor\Model\Validated\Url;
use Sensor\Model\Validated\UserAgent;
use Sensor\Model\Validated\BrowserLanguage;
use Sensor\Model\Validated\EventType;
use Sensor\Model\Validated\HttpMethod;
use Sensor\Model\Validated\UserCreated;
use Sensor\Model\Validated\Payloads\FieldEditPayload;
use Sensor\Model\Validated\Payloads\PageSearchPayload;
use Sensor\Model\Validated\Payloads\EmailChangePayload;
use Sensor\Repository\EventRepository;
use Sensor\Service\Constants;
class RequestFactory {
private const REQUIRED_FIELDS = ['ipAddress', 'url', 'eventTime'];
/**
* @param array<string, string> $data
*/
public static function createFromArray(array $data, ?string $traceId, EventRepository $eventRepository): CreateEventDto {
foreach (self::REQUIRED_FIELDS as $key) {
if (!isset($data[$key])) {
throw new ValidationException('Required field is missing or empty', $key);
}
}
$eventTime = new Timestamp($data['eventTime']);
$userCreated = isset($data['userCreated']) ? (new UserCreated($data['userCreated'])) : null;
$referer = isset($data['httpReferer']) ? (new HttpReferer($data['httpReferer'])) : null;
$httpCode = isset($data['httpCode']) ? (new HttpCode($data['httpCode'])) : null;
$ipAddress = new IpAddress($data['ipAddress']);
$phone = isset($data['phoneNumber']) ? (new Phone($data['phoneNumber'])) : null;
$email = isset($data['emailAddress']) ? (new Email($data['emailAddress'])) : null;
$firstname = isset($data['firstName']) ? (new Firstname($data['firstName'])) : null;
$lastname = isset($data['lastName']) ? (new Lastname($data['lastName'])) : null;
$fullname = isset($data['fullName']) ? (new Fullname($data['fullName'])) : null;
$username = isset($data['userName']) ? (new Userid($data['userName'])) : null;
$pageTitle = isset($data['pageTitle']) ? (new PageTitle($data['pageTitle'])) : null;
$url = isset($data['url']) ? (new Url($data['url'])) : null;
$userAgent = isset($data['userAgent']) ? (new UserAgent($data['userAgent'])) : null;
$browserLang = isset($data['browserLanguage']) ? (new BrowserLanguage($data['browserLanguage'])) : null;
$eventType = isset($data['eventType']) ? (new EventType($data['eventType'])) : null;
$httpMethod = isset($data['httpMethod']) ? (new HttpMethod($data['httpMethod'])) : null;
$payload = null;
$payloadRaw = $data['payload'] ?? null;
if ($eventType?->value) {
$eventTypeId = $eventRepository->getEventType($eventType->value);
if ($eventTypeId === Constants::FIELD_EDIT_EVENT_TYPE_ID) {
$payload = new FieldEditPayload($payloadRaw);
} elseif ($payloadRaw) {
switch ($eventTypeId) {
case Constants::ACCOUNT_EMAIL_CHANGE_EVENT_TYPE_ID:
$payload = new EmailChangePayload($payloadRaw);
break;
case Constants::PAGE_SEARCH_EVENT_TYPE_ID:
$payload = new PageSearchPayload($payloadRaw);
break;
}
}
}
$validatedParams = [
$email, $eventTime, $userCreated, $referer, $httpCode, $ipAddress,
$phone, $firstname, $lastname, $fullname, $username, $pageTitle,
$url, $userAgent, $browserLang, $eventType, $httpMethod, $payload,
];
$changedParams = self::changedParams($validatedParams);
//$email = !isset($data['emailAddress']) && !isset($data['userName']) ? Email::makePlaceholder() : $email;
//$username = $username === null ? md5($email->value) : $username->value;
if ($email === null && $username === null) {
$username = 'N/A';
} else {
$username = $username === null ? md5($email->value) : $username->value;
}
return new CreateEventDto(
$firstname?->value,
$lastname?->value,
$fullname?->value,
$pageTitle?->value,
$username,
$email !== null ? new HashedValue($email) : null,
$email !== null ? explode('@', $email->value)[1] : null,
$phone !== null && !$phone->isEmpty() ? new HashedValue($phone) : null,
new HashedValue($ipAddress),
$url?->value,
$userAgent?->value,
$eventTime->value,
$referer?->value,
$httpCode?->value,
$browserLang?->value,
$eventType?->value,
$httpMethod?->value,
$userCreated?->value,
$traceId,
$payload?->value,
$changedParams,
);
}
private static function changedParams(array $validatedParams): array {
$result = [];
foreach ($validatedParams as $param) {
if ($param !== null) {
$validation = $param->validationStatement();
if ($validation !== null) {
$result[] = $validation;
}
}
}
return $result;
}
}