Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
83 changes: 83 additions & 0 deletions src/main/java/org/prebid/server/bidder/feedad/FeedAdBidder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package org.prebid.server.bidder.feedad;

import com.iab.openrtb.request.BidRequest;
import com.iab.openrtb.request.Device;
import com.iab.openrtb.response.BidResponse;
import com.iab.openrtb.response.SeatBid;
import io.vertx.core.MultiMap;
import org.apache.commons.collections4.CollectionUtils;
import org.prebid.server.bidder.Bidder;
import org.prebid.server.bidder.model.BidderBid;
import org.prebid.server.bidder.model.BidderCall;
import org.prebid.server.bidder.model.BidderError;
import org.prebid.server.bidder.model.HttpRequest;
import org.prebid.server.bidder.model.Result;
import org.prebid.server.json.DecodeException;
import org.prebid.server.json.JacksonMapper;
import org.prebid.server.proto.openrtb.ext.response.BidType;
import org.prebid.server.util.BidderUtil;
import org.prebid.server.util.HttpUtil;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

public class FeedAdBidder implements Bidder<BidRequest> {

private static final String OPENRTB_VERSION = "2.5";
private static final String X_FA_PBS_ADAPTER_VERSION_HEADER = "X-FA-PBS-Adapter-Version";
private static final String FEED_AD_ADAPTER_VERSION = "1.0.0";

private final String endpointUrl;
private final JacksonMapper mapper;

public FeedAdBidder(String endpointUrl, JacksonMapper mapper) {
this.endpointUrl = HttpUtil.validateUrl(endpointUrl);
this.mapper = Objects.requireNonNull(mapper);
}

@Override
public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest bidRequest) {
final MultiMap headers = resolveHeaders(bidRequest.getDevice());
return Result.withValue(BidderUtil.defaultRequest(bidRequest, headers, endpointUrl, mapper));
}

private MultiMap resolveHeaders(Device device) {
final MultiMap headers = HttpUtil.headers()
.add(X_FA_PBS_ADAPTER_VERSION_HEADER, FEED_AD_ADAPTER_VERSION)
.add(HttpUtil.X_OPENRTB_VERSION_HEADER, OPENRTB_VERSION);
if (device != null) {
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER, device.getIpv6());
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER, device.getIp());
}
return headers;
}

@Override
public Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
try {
final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
return Result.withValues(extractBids(bidResponse));
} catch (DecodeException e) {
return Result.withError(BidderError.badServerResponse(e.getMessage()));
}
}

private static List<BidderBid> extractBids(BidResponse bidResponse) {
if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) {
return Collections.emptyList();
}
return bidsFromResponse(bidResponse);
}

private static List<BidderBid> bidsFromResponse(BidResponse bidResponse) {
return bidResponse.getSeatbid().stream()
.filter(Objects::nonNull)
.map(SeatBid::getBid)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.map(bid -> BidderBid.of(bid, BidType.banner, bidResponse.getCur()))
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.prebid.server.spring.config.bidder;

import org.prebid.server.bidder.BidderDeps;
import org.prebid.server.bidder.feedad.FeedAdBidder;
import org.prebid.server.json.JacksonMapper;
import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties;
import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler;
import org.prebid.server.spring.config.bidder.util.UsersyncerCreator;
import org.prebid.server.spring.env.YamlPropertySourceFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

import jakarta.validation.constraints.NotBlank;

@Configuration
@PropertySource(value = "classpath:/bidder-config/feedad.yaml", factory = YamlPropertySourceFactory.class)
public class FeedAdConfiguration {

private static final String BIDDER_NAME = "feedad";

@Bean("feedadConfigurationProperties")
@ConfigurationProperties("adapters.feedad")
BidderConfigurationProperties configurationProperties() {
return new BidderConfigurationProperties();
}

@Bean
BidderDeps feedadBidderDeps(BidderConfigurationProperties feedadConfigurationProperties,
@NotBlank @Value("${external-url}") String externalUrl,
JacksonMapper mapper) {

return BidderDepsAssembler.forBidder(BIDDER_NAME)
.withConfig(feedadConfigurationProperties)
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
.bidderCreator(config -> new FeedAdBidder(config.getEndpoint(), mapper))
.assemble();
}
}
18 changes: 18 additions & 0 deletions src/main/resources/bidder-config/feedad.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
adapters:
feedad:
endpoint: https://ortb.feedad.com/1/prebid/requests
endpoint-compression: gzip
modifying-vast-xml-allowed: true
meta-info:
maintainer-email: support@feedad.com
app-media-types:
- banner
site-media-types:
- banner
vendor-id: 781
usersync:
cookie-family-name: feedad
iframe:
url: https://ortb.feedad.com/1/usersyncs/supply?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&gpp={{gpp}}&gpp_sid={{gpp_sid}}&us_privacy={{us_privacy}}&redirect={{redirect_url}}
support-cors: false
uid-macro: $UID
65 changes: 65 additions & 0 deletions src/main/resources/static/bidder-params/feedad.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "FeedAd Adapter Params",
"description": "A schema which validates params accepted by the FeedAd adapter",
"properties": {
"clientToken": {
"description": "Your FeedAd client token. Check your FeedAd admin panel.",
"minLength": 1,
"type": "string"
},
"decoration": {
"description": "A decoration to apply to the ad slot. See our documentation at https://docs.feedad.com/web/feed_ad/#decorations",
"type": "string"
},
"placementId": {
"description": "A FeedAd placement ID of your choice",
"minLength": 1,
"pattern": "^(([a-z0-9])+[-_]?)+$",
"type": "string"
},
"sdkOptions": {
"description": "Optional: Only required if you are using Prebid.JS in an app environment (aka hybrid app). See our documentation at https://docs.feedad.com/web/configuration/#hybrid-app-config-parameters",
"properties": {
"advertising_id": {
"type": "string",
"description": "Optional: The advertising id of the device or user (e.g. Apple IDFA, Google Advertising Client Id). We highly recommend setting this parameter to maximize your fill rate."
},
"app_name": {
"type": "string",
"description": "The name of your app. This name will identify your app within the FeedAd admin dashboard."
},
"bundle_id": {
"type": "string",
"description": "The unique package name or bundle id of your app."
},
"hybrid_app": {
"type": "boolean",
"description": "Boolean indicating that the SDK is loaded within a hybrid app."
},
"hybrid_platform": {
"description": "String identifying the device platform.",
"enum": [
"",
"android",
"ios",
"windows"
]
},
"limit_ad_tracking": {
"type": "boolean",
"description": "Whether the app's user has limited ad tracking enabled."
}
},
"type": [
"object",
"null"
]
}
},
"required": [
"clientToken",
"placementId"
],
"type": "object"
}
Loading
Loading