Skip to content

Commit 6b73664

Browse files
Port FeedAd: New Adapter (#3869)
1 parent 9a160bf commit 6b73664

11 files changed

Lines changed: 659 additions & 0 deletions

File tree

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package org.prebid.server.bidder.feedad;
2+
3+
import com.iab.openrtb.request.BidRequest;
4+
import com.iab.openrtb.request.Device;
5+
import com.iab.openrtb.response.BidResponse;
6+
import com.iab.openrtb.response.SeatBid;
7+
import io.vertx.core.MultiMap;
8+
import org.apache.commons.collections4.CollectionUtils;
9+
import org.prebid.server.bidder.Bidder;
10+
import org.prebid.server.bidder.model.BidderBid;
11+
import org.prebid.server.bidder.model.BidderCall;
12+
import org.prebid.server.bidder.model.BidderError;
13+
import org.prebid.server.bidder.model.HttpRequest;
14+
import org.prebid.server.bidder.model.Result;
15+
import org.prebid.server.json.DecodeException;
16+
import org.prebid.server.json.JacksonMapper;
17+
import org.prebid.server.proto.openrtb.ext.response.BidType;
18+
import org.prebid.server.util.BidderUtil;
19+
import org.prebid.server.util.HttpUtil;
20+
21+
import java.util.Collection;
22+
import java.util.Collections;
23+
import java.util.List;
24+
import java.util.Objects;
25+
26+
public class FeedAdBidder implements Bidder<BidRequest> {
27+
28+
private static final String OPENRTB_VERSION = "2.5";
29+
private static final String X_FA_PBS_ADAPTER_VERSION_HEADER = "X-FA-PBS-Adapter-Version";
30+
private static final String FEED_AD_ADAPTER_VERSION = "1.0.0";
31+
32+
private final String endpointUrl;
33+
private final JacksonMapper mapper;
34+
35+
public FeedAdBidder(String endpointUrl, JacksonMapper mapper) {
36+
this.endpointUrl = HttpUtil.validateUrl(endpointUrl);
37+
this.mapper = Objects.requireNonNull(mapper);
38+
}
39+
40+
@Override
41+
public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest bidRequest) {
42+
final MultiMap headers = resolveHeaders(bidRequest.getDevice());
43+
return Result.withValue(BidderUtil.defaultRequest(bidRequest, headers, endpointUrl, mapper));
44+
}
45+
46+
private MultiMap resolveHeaders(Device device) {
47+
final MultiMap headers = HttpUtil.headers()
48+
.add(X_FA_PBS_ADAPTER_VERSION_HEADER, FEED_AD_ADAPTER_VERSION)
49+
.add(HttpUtil.X_OPENRTB_VERSION_HEADER, OPENRTB_VERSION);
50+
if (device != null) {
51+
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER, device.getIpv6());
52+
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER, device.getIp());
53+
}
54+
return headers;
55+
}
56+
57+
@Override
58+
public Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
59+
try {
60+
final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
61+
return Result.withValues(extractBids(bidResponse));
62+
} catch (DecodeException e) {
63+
return Result.withError(BidderError.badServerResponse(e.getMessage()));
64+
}
65+
}
66+
67+
private static List<BidderBid> extractBids(BidResponse bidResponse) {
68+
if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) {
69+
return Collections.emptyList();
70+
}
71+
return bidsFromResponse(bidResponse);
72+
}
73+
74+
private static List<BidderBid> bidsFromResponse(BidResponse bidResponse) {
75+
return bidResponse.getSeatbid().stream()
76+
.filter(Objects::nonNull)
77+
.map(SeatBid::getBid)
78+
.filter(Objects::nonNull)
79+
.flatMap(Collection::stream)
80+
.map(bid -> BidderBid.of(bid, BidType.banner, bidResponse.getCur()))
81+
.toList();
82+
}
83+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package org.prebid.server.spring.config.bidder;
2+
3+
import org.prebid.server.bidder.BidderDeps;
4+
import org.prebid.server.bidder.feedad.FeedAdBidder;
5+
import org.prebid.server.json.JacksonMapper;
6+
import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties;
7+
import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler;
8+
import org.prebid.server.spring.config.bidder.util.UsersyncerCreator;
9+
import org.prebid.server.spring.env.YamlPropertySourceFactory;
10+
import org.springframework.beans.factory.annotation.Value;
11+
import org.springframework.boot.context.properties.ConfigurationProperties;
12+
import org.springframework.context.annotation.Bean;
13+
import org.springframework.context.annotation.Configuration;
14+
import org.springframework.context.annotation.PropertySource;
15+
16+
import jakarta.validation.constraints.NotBlank;
17+
18+
@Configuration
19+
@PropertySource(value = "classpath:/bidder-config/feedad.yaml", factory = YamlPropertySourceFactory.class)
20+
public class FeedAdConfiguration {
21+
22+
private static final String BIDDER_NAME = "feedad";
23+
24+
@Bean("feedadConfigurationProperties")
25+
@ConfigurationProperties("adapters.feedad")
26+
BidderConfigurationProperties configurationProperties() {
27+
return new BidderConfigurationProperties();
28+
}
29+
30+
@Bean
31+
BidderDeps feedadBidderDeps(BidderConfigurationProperties feedadConfigurationProperties,
32+
@NotBlank @Value("${external-url}") String externalUrl,
33+
JacksonMapper mapper) {
34+
35+
return BidderDepsAssembler.forBidder(BIDDER_NAME)
36+
.withConfig(feedadConfigurationProperties)
37+
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
38+
.bidderCreator(config -> new FeedAdBidder(config.getEndpoint(), mapper))
39+
.assemble();
40+
}
41+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
adapters:
2+
feedad:
3+
endpoint: https://ortb.feedad.com/1/prebid/requests
4+
endpoint-compression: gzip
5+
modifying-vast-xml-allowed: true
6+
meta-info:
7+
maintainer-email: support@feedad.com
8+
app-media-types:
9+
- banner
10+
site-media-types:
11+
- banner
12+
vendor-id: 781
13+
usersync:
14+
cookie-family-name: feedad
15+
iframe:
16+
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}}
17+
support-cors: false
18+
uid-macro: $UID
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-04/schema#",
3+
"title": "FeedAd Adapter Params",
4+
"description": "A schema which validates params accepted by the FeedAd adapter",
5+
"properties": {
6+
"clientToken": {
7+
"description": "Your FeedAd client token. Check your FeedAd admin panel.",
8+
"minLength": 1,
9+
"type": "string"
10+
},
11+
"decoration": {
12+
"description": "A decoration to apply to the ad slot. See our documentation at https://docs.feedad.com/web/feed_ad/#decorations",
13+
"type": "string"
14+
},
15+
"placementId": {
16+
"description": "A FeedAd placement ID of your choice",
17+
"minLength": 1,
18+
"pattern": "^(([a-z0-9])+[-_]?)+$",
19+
"type": "string"
20+
},
21+
"sdkOptions": {
22+
"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",
23+
"properties": {
24+
"advertising_id": {
25+
"type": "string",
26+
"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."
27+
},
28+
"app_name": {
29+
"type": "string",
30+
"description": "The name of your app. This name will identify your app within the FeedAd admin dashboard."
31+
},
32+
"bundle_id": {
33+
"type": "string",
34+
"description": "The unique package name or bundle id of your app."
35+
},
36+
"hybrid_app": {
37+
"type": "boolean",
38+
"description": "Boolean indicating that the SDK is loaded within a hybrid app."
39+
},
40+
"hybrid_platform": {
41+
"description": "String identifying the device platform.",
42+
"enum": [
43+
"",
44+
"android",
45+
"ios",
46+
"windows"
47+
]
48+
},
49+
"limit_ad_tracking": {
50+
"type": "boolean",
51+
"description": "Whether the app's user has limited ad tracking enabled."
52+
}
53+
},
54+
"type": [
55+
"object",
56+
"null"
57+
]
58+
}
59+
},
60+
"required": [
61+
"clientToken",
62+
"placementId"
63+
],
64+
"type": "object"
65+
}

0 commit comments

Comments
 (0)