Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wip] Add DSP Bridge integration #1377

Open
wants to merge 71 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
ee7b798
Add OpenRTB provider to Config
PawelPodkalicki Feb 27, 2023
4c1d84e
Remove unused repository
PawelPodkalicki Feb 27, 2023
4999953
Change deprecated field banners in inventory list
PawelPodkalicki Feb 28, 2023
b41151e
Integrate OpenRTB provider (inventory)
PawelPodkalicki Feb 28, 2023
4817c02
Extract registrar
PawelPodkalicki Feb 28, 2023
8583a58
Do not accept invalid date format
PawelPodkalicki Feb 28, 2023
3015899
extract consts
PawelPodkalicki Mar 1, 2023
539e3a8
build info from response
PawelPodkalicki Mar 3, 2023
210959b
Merge remote-tracking branch 'origin/develop' into it-440-integrate-o…
PawelPodkalicki Mar 6, 2023
42b99a5
fix dummy client
PawelPodkalicki Mar 6, 2023
ef4811b
add serve url
PawelPodkalicki Mar 6, 2023
ca93f91
replace open rtb banners
PawelPodkalicki Mar 6, 2023
ea94f78
fix test
PawelPodkalicki Mar 6, 2023
a39abc5
fix code style
PawelPodkalicki Mar 6, 2023
9cc30ff
null banner if not in response
PawelPodkalicki Mar 6, 2023
3d8eb13
fetch by demand id
PawelPodkalicki Mar 8, 2023
6ac160e
rename provider to bridge
PawelPodkalicki Mar 8, 2023
e9b13c2
extract OpenRtbBridge
PawelPodkalicki Mar 8, 2023
850eba7
Merge remote-tracking branch 'origin/develop' into it-440-integrate-o…
PawelPodkalicki Mar 9, 2023
f7489e8
change request
PawelPodkalicki Mar 9, 2023
e399713
Merge remote-tracking branch 'origin/develop' into it-440-integrate-o…
PawelPodkalicki Mar 10, 2023
70c4f83
Remove serve url
PawelPodkalicki Mar 10, 2023
67c1869
rename class
PawelPodkalicki Mar 10, 2023
a3055d9
move tests
PawelPodkalicki Mar 10, 2023
484f29c
Merge remote-tracking branch 'origin/develop' into it-440-integrate-o…
PawelPodkalicki Mar 10, 2023
0248e0c
Merge remote-tracking branch 'origin/develop' into it-440-integrate-o…
PawelPodkalicki Mar 13, 2023
3c745f4
add removing query params
PawelPodkalicki Mar 14, 2023
17ef68a
extid, handle view/click
PawelPodkalicki Mar 14, 2023
5e2a5e5
fix tests
PawelPodkalicki Mar 14, 2023
85819f3
add view tests
PawelPodkalicki Mar 14, 2023
eb030a0
add click tests
PawelPodkalicki Mar 14, 2023
220055d
test invalid banner id
PawelPodkalicki Mar 14, 2023
e734c12
Merge remote-tracking branch 'origin/develop' into it-440-integrate-o…
PawelPodkalicki Mar 15, 2023
8fe969a
fetch payment metadata
PawelPodkalicki Mar 17, 2023
f54fc6b
add processing payments
PawelPodkalicki Mar 20, 2023
10098fe
invalidate statistics for bridge payments
PawelPodkalicki Mar 20, 2023
f2c25bf
add transaction
PawelPodkalicki Mar 20, 2023
6e35b14
fetch payments before processing
PawelPodkalicki Mar 20, 2023
4dfa914
add test case
PawelPodkalicki Mar 21, 2023
ab5bb85
restrict payment id length to 18 chars
PawelPodkalicki Mar 21, 2023
5103882
fix test
PawelPodkalicki Mar 21, 2023
0e66c30
Increase coverage on new code
PawelPodkalicki Mar 15, 2023
1066614
Merge remote-tracking branch 'origin/develop' into it-440-integrate-o…
PawelPodkalicki Mar 24, 2023
6a95f71
Pass topframe in find
PawelPodkalicki Mar 28, 2023
18aaedd
change key case
PawelPodkalicki Mar 28, 2023
b149837
Merge remote-tracking branch 'origin/add-test' into it-440-integrate-…
PawelPodkalicki Mar 28, 2023
e0fbdcc
fix zone in frame checking
PawelPodkalicki Mar 28, 2023
c8b266d
Merge remote-tracking branch 'origin/add-test' into it-440-integrate-…
PawelPodkalicki Mar 28, 2023
00c0825
Pass zone options to bridge
PawelPodkalicki Mar 29, 2023
0e84178
rename bridge
PawelPodkalicki Mar 29, 2023
cea2d92
change dsp bridge bid endpoint path
PawelPodkalicki Mar 29, 2023
2696146
do not secure urls from bridge
PawelPodkalicki Mar 29, 2023
2ea8a39
Merge remote-tracking branch 'origin/develop' into it-440-integrate-o…
PawelPodkalicki Mar 31, 2023
aa6f368
Merge remote-tracking branch 'origin/develop' into it-440-integrate-o…
PawelPodkalicki Apr 3, 2023
cd16624
import inventory from bridge even if not on whitelist
PawelPodkalicki Apr 3, 2023
af39f4d
repeat adselect query discarding dsp bridge
PawelPodkalicki Apr 4, 2023
91954bb
add test
PawelPodkalicki Apr 4, 2023
0dc6a4d
exclusion value as array
PawelPodkalicki Apr 5, 2023
6316ece
fix test
PawelPodkalicki Apr 5, 2023
19013e6
Why page
PawelPodkalicki Apr 5, 2023
d2c917c
Merge remote-tracking branch 'origin/develop' into it-440-integrate-o…
PawelPodkalicki Apr 26, 2023
17641f0
hide logs
PawelPodkalicki Apr 26, 2023
4e3abd5
fix missing parameter after merge
PawelPodkalicki Apr 26, 2023
631e063
fix tests
PawelPodkalicki Apr 26, 2023
80d062c
Merge remote-tracking branch 'origin/develop' into it-440-integrate-o…
PawelPodkalicki May 5, 2023
fc42b30
fix test
PawelPodkalicki May 5, 2023
325af3c
Merge remote-tracking branch 'origin/develop' into it-440-integrate-o…
PawelPodkalicki May 23, 2023
e2f19a7
Merge remote-tracking branch 'origin/develop' into it-440-integrate-o…
PawelPodkalicki Jul 4, 2023
a180e69
Merge remote-tracking branch 'origin/develop' into it-440-integrate-o…
PawelPodkalicki Aug 18, 2023
859c86c
Fix tests
PawelPodkalicki Aug 18, 2023
28f3b0b
Increase coverage
PawelPodkalicki Aug 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
- DSP Bridge integration

## [2.6.2] - 2023-08-07
### Added
Expand Down
7 changes: 7 additions & 0 deletions app/Client/GuzzleAdSelectClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,12 @@ public function findBanners(array $zones, ImpressionContext $context, string $im
if (isset($zones[$i]['options']['banner_mime'])) {
$sitesMap[$siteId]['filters']['require']['mime'] = (array)$zones[$i]['options']['banner_mime'];
}
if (isset($zones[$i]['options']['exclude'])) {
$sitesMap[$siteId]['filters']['exclude'] = array_merge_recursive(
$sitesMap[$siteId]['filters']['exclude'],
$zones[$i]['options']['exclude'],
);
}
if ($site->only_direct_deals) {
$sitesMap[$siteId]['filters']['require']['require:site:domain'] = $site->domain;
}
Expand Down Expand Up @@ -312,6 +318,7 @@ private function fetchInOrderOfAppearance(
$campaign = $banner->campaign;
$data = [
'id' => $bannerId,
'demand_id' => $banner->demand_banner_id,
'publisher_id' => $zone->site->user->uuid,
'zone_id' => $zone->uuid,
'pay_from' => $campaign->source_address,
Expand Down
26 changes: 16 additions & 10 deletions app/Client/GuzzleDemandClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
use Adshares\Adserver\Models\NetworkCampaign;
use Adshares\Adserver\Repository\Common\ClassifierExternalRepository;
use Adshares\Adserver\Services\Common\ClassifierExternalSignatureVerifier;
use Adshares\Adserver\Services\Supply\DspBridge;
use Adshares\Adserver\Services\Supply\SiteFilteringUpdater;
use Adshares\Adserver\Utilities\AdsAuthenticator;
use Adshares\Adserver\ViewModel\MediumName;
Expand Down Expand Up @@ -260,9 +261,11 @@ private function processData(
'updated_at' => DateTime::createFromFormat(DateTimeInterface::ATOM, $data['updated_at']),
];

$isFromDspBridge = DspBridge::isDspAddress($sourceAddress);
$classifiersRequired = $this->classifierRepository->fetchRequiredClassifiersNames();
$banners = [];
foreach ((array)$data['banners'] as $banner) {
$bannersInput = $data['creatives'] ?? $data['banners'];// legacy fallback, field 'banners' is deprecated
foreach ((array)$bannersInput as $banner) {
$banner['demand_banner_id'] = Uuid::fromString($banner['id']);

if (array_key_exists($banner['id'], $bannerDemandIdsToSupplyIds)) {
Expand All @@ -271,7 +274,9 @@ private function processData(
unset($banner['id']);
}

$mappedClassification = $this->validateAndMapClassification($banner);
$mappedClassification = $isFromDspBridge
? self::flattenClassification($banner['classification'] ?? [])
: $this->validateAndMapClassification($banner);
if ($this->missingRequiredClassifier($classifiersRequired, $mappedClassification)) {
continue;
}
Expand Down Expand Up @@ -365,7 +370,8 @@ private function getBannerDemandIdsToSupplyIds(array $campaigns, string $sourceA
{
$bannerDemandIds = [];
foreach ($campaigns as $campaign) {
foreach ((array)$campaign['banners'] as $banner) {
$banners = $campaign['creatives'] ?? $campaign['banners'];// legacy fallback, field 'banners' is deprecated
foreach ((array)$banners as $banner) {
$bannerDemandIds[] = $banner['id'];
}
}
Expand Down Expand Up @@ -399,17 +405,17 @@ private function validateAndMapClassification(array $banner): array
unset($classification[$invalidClassifier]);
}

return self::flattenClassification($classification);
}

private static function flattenClassification(mixed $classification): array
{
$flatClassification = [];
foreach ($classification as $classifier => $classificationItem) {
$keywords = $classificationItem['keywords'] ?? [];
$keywords[SiteFilteringUpdater::KEYWORD_CLASSIFIED] =
SiteFilteringUpdater::KEYWORD_CLASSIFIED_VALUE;

$flatClassification[$classifier] = AbstractFilterMapper::generateNestedStructure(
$keywords
);
$keywords[SiteFilteringUpdater::KEYWORD_CLASSIFIED] = SiteFilteringUpdater::KEYWORD_CLASSIFIED_VALUE;
$flatClassification[$classifier] = AbstractFilterMapper::generateNestedStructure($keywords);
}

return $flatClassification;
}

Expand Down
23 changes: 16 additions & 7 deletions app/Console/Commands/AdsFetchHosts.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
use Adshares\Adserver\Http\Response\InfoResponse;
use Adshares\Adserver\Models\NetworkHost;
use Adshares\Adserver\Utilities\DomainReader;
use Adshares\Adserver\Services\Supply\DspBridgeRegistrar;
use Adshares\Adserver\ViewModel\ServerEventType;
use Adshares\Common\Exception\RuntimeException;
use Adshares\Config\AppMode;
Expand All @@ -56,16 +57,20 @@ class AdsFetchHosts extends BaseCommand
protected $signature = 'ads:fetch-hosts';
protected $description = 'Fetches Demand AdServers';

public function __construct(Locker $locker, private readonly DemandClient $client)
{
public function __construct(
Locker $locker,
private readonly AdsClient $adsClient,
private readonly DemandClient $client,
private readonly DspBridgeRegistrar $dspBridgeRegistrar,
) {
parent::__construct($locker);
}

public function handle(AdsClient $adsClient): void
public function handle(): int
{
if (!$this->lock()) {
$this->info('Command ' . $this->signature . ' already running');
return;
return self::FAILURE;
}

$this->info('Start command ' . $this->signature);
Expand All @@ -79,12 +84,15 @@ public function handle(AdsClient $adsClient): void
$progressBar->start();
while ($timeBlock <= $timeNow - self::BLOCK_TIME) {
$blockId = dechex($timeBlock);
$found += $this->handleBlock($adsClient, $blockId);
$found += $this->handleBlock($blockId);
$timeBlock += self::BLOCK_TIME;
$progressBar->advance();
}
$progressBar->finish();
$this->newLine();
if ($this->dspBridgeRegistrar->registerAsNetworkHost()) {
$found++;
}

$this->comment('Cleaning up old hosts...');
$added = NetworkHost::all()->count() - $hostsCount;
Expand All @@ -103,6 +111,7 @@ public function handle(AdsClient $adsClient): void
$this->info($removed > 0 ? sprintf('Removed %d hosts', $removed) : 'Nothing to remove');

$this->info('Finished command ' . $this->signature);
return self::SUCCESS;
}

private function getTimeOfFirstBlock(int $timeNow): int
Expand All @@ -119,11 +128,11 @@ private function getTimeOfFirstBlock(int $timeNow): int
return $timeBlock;
}

private function handleBlock(AdsClient $adsClient, string $blockId): int
private function handleBlock(string $blockId): int
{
$foundHosts = 0;
try {
$response = $adsClient->getBroadcast($blockId);
$response = $this->adsClient->getBroadcast($blockId);
$broadcastArray = $response->getBroadcast();

foreach ($broadcastArray as $broadcast) {
Expand Down
96 changes: 69 additions & 27 deletions app/Console/Commands/SupplyProcessPayments.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@
use Adshares\Adserver\Models\NetworkHost;
use Adshares\Adserver\Models\TurnoverEntry;
use Adshares\Adserver\Services\Dto\PaymentProcessingResult;
use Adshares\Adserver\Services\Dto\ProcessedPaymentsMetaData;
use Adshares\Adserver\Services\LicenseFeeSender;
use Adshares\Adserver\Services\PaymentDetailsProcessor;
use Adshares\Adserver\Services\Supply\DspBridge;
use Adshares\Adserver\Utilities\DateUtils;
use Adshares\Adserver\ViewModel\ServerEventType;
use Adshares\Common\Infrastructure\Service\LicenseReader;
Expand All @@ -45,6 +47,8 @@

class SupplyProcessPayments extends BaseCommand
{
private const COLUMN_ADS_PAYMENT_ID = 'ads_payment_id';
private const COLUMN_BRIDGE_PAYMENT_ID = 'bridge_payment_id';
private const TRY_OUT_PERIOD_FOR_EVENT_PAYMENT = '-24 hours';

private const SQL_QUERY_GET_PROCESSED_PAYMENTS_AMOUNT = <<<SQL
Expand All @@ -61,11 +65,14 @@ class SupplyProcessPayments extends BaseCommand
(
SELECT DISTINCT DATE(pay_time) AS d, HOUR(pay_time) AS h
FROM network_case_payments
WHERE ads_payment_id IN (%s)
WHERE :paymentIdColumn IN (:whereInPlaceholder)
UNION
SELECT DISTINCT DATE(created_at) AS d, HOUR(created_at) AS h
FROM network_cases
WHERE id IN (SELECT DISTINCT network_case_id FROM network_case_payments WHERE ads_payment_id IN (%s))) t;
WHERE id IN (
SELECT DISTINCT network_case_id FROM network_case_payments WHERE :paymentIdColumn IN (:whereInPlaceholder)
)
) t;
SQL;

protected $signature = 'ops:supply:payments:process {--c|chunkSize=5000}';
Expand All @@ -76,7 +83,7 @@ public function __construct(
private readonly AdsClient $adsClient,
private readonly DemandClient $demandClient,
private readonly LicenseReader $licenseReader,
private readonly PaymentDetailsProcessor $paymentDetailsProcessor
private readonly PaymentDetailsProcessor $paymentDetailsProcessor,
) {
parent::__construct($locker);
}
Expand All @@ -90,13 +97,56 @@ public function handle(): void

$this->info('Start command ' . $this->getName());

$adsPayments = AdsPayment::fetchByStatus(AdsPayment::STATUS_EVENT_PAYMENT_CANDIDATE);
$processedAdsPaymentMetaData = $this->processAdsPayments();
$processedPaymentsForAds = $processedAdsPaymentMetaData->getProcessedPaymentsForAds();
$processedPaymentsTotal = $processedAdsPaymentMetaData->getProcessedPaymentsTotal();
$timestamps = $this->fetchTimestampsToUpdate(
$processedAdsPaymentMetaData->getPaymentIds(),
self::COLUMN_ADS_PAYMENT_ID,
);

$earliestTryOutDateTime = new DateTimeImmutable(self::TRY_OUT_PERIOD_FOR_EVENT_PAYMENT);
if (DspBridge::isActive()) {
$bridge = new DspBridge();
$bridge->fetchAndStorePayments();

$processedBridgePaymentMetaData = $bridge->processPayments(
$this->demandClient,
$this->paymentDetailsProcessor,
(int)$this->option('chunkSize'),
);
$processedPaymentsForAds += $processedBridgePaymentMetaData->getProcessedPaymentsForAds();
$processedPaymentsTotal += $processedBridgePaymentMetaData->getProcessedPaymentsTotal();
$timestamps = array_unique(
array_merge(
$timestamps,
$this->fetchTimestampsToUpdate(
$processedBridgePaymentMetaData->getPaymentIds(),
self::COLUMN_BRIDGE_PAYMENT_ID,
),
)
);
}

foreach ($timestamps as $timestamp) {
NetworkCaseLogsHourlyMeta::invalidate($timestamp);
}
ServerEvent::dispatch(ServerEventType::IncomingAdPaymentProcessed, [
'adsPaymentCount' => $processedPaymentsForAds,
'totalPaymentCount' => $processedPaymentsTotal,
]);

$this->info('End command ' . $this->getName());
}

private function processAdsPayments(): ProcessedPaymentsMetaData
{
$processedAdsPaymentIds = [];
$processedPaymentsTotal = 0;
$processedPaymentsForAds = 0;

$adsPayments = AdsPayment::fetchByStatus(AdsPayment::STATUS_EVENT_PAYMENT_CANDIDATE);
$earliestTryOutDateTime = new DateTimeImmutable(self::TRY_OUT_PERIOD_FOR_EVENT_PAYMENT);

/** @var AdsPayment $adsPayment */
foreach ($adsPayments as $adsPayment) {
if ($adsPayment->created_at < $earliestTryOutDateTime) {
Expand All @@ -122,24 +172,18 @@ public function handle(): void
DB::rollBack();
$this->error(
sprintf(
'Error during handling paid events for id=%d (%s)',
'Error during handling paid events for ads payment id=%d (%s)',
$adsPayment->id,
$throwable->getMessage()
)
);
}
}

$timestamps = $this->fetchTimestampsToUpdate($processedAdsPaymentIds);
foreach ($timestamps as $timestamp) {
NetworkCaseLogsHourlyMeta::invalidate($timestamp);
}
ServerEvent::dispatch(ServerEventType::IncomingAdPaymentProcessed, [
'adsPaymentCount' => $processedPaymentsForAds,
'totalPaymentCount' => $processedPaymentsTotal,
]);

$this->info('End command ' . $this->getName());
return new ProcessedPaymentsMetaData(
$processedAdsPaymentIds,
$processedPaymentsTotal,
$processedPaymentsForAds,
);
}

private function handleEventPaymentCandidate(AdsPayment $incomingPayment): void
Expand Down Expand Up @@ -205,24 +249,22 @@ private function handleEventPaymentCandidate(AdsPayment $incomingPayment): void
}
}

private function fetchTimestampsToUpdate(array $adsPaymentIds): array
private function fetchTimestampsToUpdate(array $paymentIds, string $paymentIdColumn): array
{
if (empty($adsPaymentIds)) {
if (empty($paymentIds)) {
return [];
}

$whereInPlaceholder = str_repeat('?,', count($adsPaymentIds) - 1) . '?';
$query = sprintf(
$whereInPlaceholder = str_repeat('?,', count($paymentIds) - 1) . '?';
$query = str_replace(
[':paymentIdColumn', ':whereInPlaceholder'],
[$paymentIdColumn, $whereInPlaceholder],
self::SQL_QUERY_SELECT_TIMESTAMPS_TO_UPDATE_TEMPLATE,
$whereInPlaceholder,
$whereInPlaceholder
);

return array_map(
function (stdClass $item) {
return (int)$item->pay_time;
},
DB::select($query, array_merge($adsPaymentIds, $adsPaymentIds))
fn(stdClass $item) => (int)$item->pay_time,
DB::select($query, array_merge($paymentIds, $paymentIds))
);
}

Expand Down
7 changes: 4 additions & 3 deletions app/Http/Controllers/DemandController.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
use Adshares\Common\Exception\RuntimeException;
use Adshares\Demand\Application\Service\PaymentDetailsVerify;
use DateTime;
use DateTimeImmutable;
use DateTimeInterface;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Request;
Expand Down Expand Up @@ -472,9 +473,9 @@ public function paymentDetails(
): JsonResponse {
$transactionIdDecoded = AdsUtils::decodeTxId($transactionId);
$accountAddressDecoded = AdsUtils::decodeAddress($accountAddress);
$datetime = DateTime::createFromFormat(DateTimeInterface::ATOM, $date);
$datetime = DateTimeImmutable::createFromFormat(DateTimeInterface::ATOM, $date);

if ($transactionIdDecoded === null || $accountAddressDecoded === null) {
if (null === $transactionIdDecoded || null === $accountAddressDecoded || false === $datetime) {
throw new BadRequestHttpException('Input data are invalid.');
}

Expand Down Expand Up @@ -587,7 +588,7 @@ public function inventoryList(Request $request, TotalFeeReader $totalFeeReader):
'max_cpc' => $campaign->max_cpc,
'max_cpm' => $campaign->max_cpm,
'budget' => self::calculateBudgetAfterFees($campaign->budget, $totalFee),
'banners' => $banners,
'creatives' => $banners,
'targeting_requires' => (array)$campaign->targeting_requires,
'targeting_excludes' => (array)$campaign->targeting_excludes,
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ class ServerConfigurationController extends Controller
Config::CRM_MAIL_ADDRESS_ON_USER_REGISTERED => 'nullable|email',
Config::CURRENCY => 'nullable|appCurrency',
Config::DEFAULT_USER_ROLES => 'nullable|notEmpty|list:userRole',
Config::DSP_BRIDGE_ACCOUNT_ADDRESS => 'nullable|accountId',
Config::DSP_BRIDGE_URL => 'nullable|url',
Config::EMAIL_VERIFICATION_REQUIRED => 'nullable|boolean',
Config::EXCHANGE_API_KEY => 'nullable',
Config::EXCHANGE_API_SECRET => 'nullable',
Expand Down
Loading