Skip to content

Commit 6f8ce04

Browse files
committed
Implements new Seedtag adapter
1 parent 53431fb commit 6f8ce04

12 files changed

Lines changed: 777 additions & 0 deletions

File tree

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
package org.prebid.server.bidder.seedtag;
2+
3+
import com.fasterxml.jackson.core.type.TypeReference;
4+
import com.iab.openrtb.request.BidRequest;
5+
import com.iab.openrtb.request.Imp;
6+
import com.iab.openrtb.response.Bid;
7+
import com.iab.openrtb.response.BidResponse;
8+
import com.iab.openrtb.response.SeatBid;
9+
import io.vertx.core.http.HttpMethod;
10+
import org.apache.commons.collections4.CollectionUtils;
11+
import org.apache.commons.lang3.StringUtils;
12+
import org.prebid.server.bidder.Bidder;
13+
import org.prebid.server.bidder.model.BidderBid;
14+
import org.prebid.server.bidder.model.BidderCall;
15+
import org.prebid.server.bidder.model.BidderError;
16+
import org.prebid.server.bidder.model.HttpRequest;
17+
import org.prebid.server.bidder.model.Price;
18+
import org.prebid.server.bidder.model.Result;
19+
import org.prebid.server.currency.CurrencyConversionService;
20+
import org.prebid.server.exception.PreBidException;
21+
import org.prebid.server.json.DecodeException;
22+
import org.prebid.server.json.JacksonMapper;
23+
import org.prebid.server.proto.openrtb.ext.ExtPrebid;
24+
import org.prebid.server.proto.openrtb.ext.request.seedtag.ExtImpSeedtag;
25+
import org.prebid.server.proto.openrtb.ext.response.BidType;
26+
import org.prebid.server.util.BidderUtil;
27+
import org.prebid.server.util.HttpUtil;
28+
29+
import java.math.BigDecimal;
30+
import java.util.ArrayList;
31+
import java.util.Collection;
32+
import java.util.Collections;
33+
import java.util.List;
34+
import java.util.Objects;
35+
36+
public class SeedtagBidder implements Bidder<BidRequest> {
37+
38+
private static final TypeReference<ExtPrebid<?, ExtImpSeedtag>> SEEDTAG_EXT_TYPE_REFERENCE =
39+
new TypeReference<>() {
40+
};
41+
private static final String BIDDER_CURRENCY = "USD";
42+
43+
private final String endpointUrl;
44+
private final JacksonMapper mapper;
45+
private final CurrencyConversionService currencyConversionService;
46+
47+
public SeedtagBidder(String endpointUrl,
48+
CurrencyConversionService currencyConversionService,
49+
JacksonMapper mapper) {
50+
51+
this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
52+
this.currencyConversionService = Objects.requireNonNull(currencyConversionService);
53+
this.mapper = Objects.requireNonNull(mapper);
54+
}
55+
56+
@Override
57+
public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest request) {
58+
59+
final List<Imp> modifiedImps = new ArrayList<>();
60+
final List<BidderError> errors = new ArrayList<>();
61+
62+
for (Imp imp : request.getImp()) {
63+
try {
64+
parseImpExt(imp);
65+
final Price bidFloorPrice = resolveBidFloor(imp, request);
66+
67+
modifiedImps.add(modifyImp(imp, bidFloorPrice));
68+
} catch (PreBidException e) {
69+
errors.add(BidderError.badInput(e.getMessage()));
70+
}
71+
}
72+
73+
if (modifiedImps.size() < 1) {
74+
return Result.withErrors(errors);
75+
}
76+
77+
final BidRequest modifiedBidRequest = request.toBuilder()
78+
.cur(Collections.singletonList(BIDDER_CURRENCY))
79+
.imp(modifiedImps)
80+
.build();
81+
82+
return Result.withValue(HttpRequest.<BidRequest>builder()
83+
.method(HttpMethod.POST)
84+
.uri(endpointUrl)
85+
.headers(HttpUtil.headers())
86+
.payload(modifiedBidRequest)
87+
.body(mapper.encodeToBytes(modifiedBidRequest))
88+
.impIds(BidderUtil.impIds(modifiedBidRequest))
89+
.build());
90+
}
91+
92+
private static Imp modifyImp(Imp imp, Price bidFloorPrice) {
93+
return imp.toBuilder()
94+
.bidfloorcur(bidFloorPrice.getCurrency())
95+
.bidfloor(bidFloorPrice.getValue())
96+
.build();
97+
}
98+
99+
private Price resolveBidFloor(Imp imp, BidRequest bidRequest) {
100+
final Price initialBidFloorPrice = Price.of(imp.getBidfloorcur(), imp.getBidfloor());
101+
return BidderUtil.isValidPrice(initialBidFloorPrice)
102+
&& !StringUtils.equalsIgnoreCase(initialBidFloorPrice.getCurrency(), BIDDER_CURRENCY)
103+
? convertBidFloor(initialBidFloorPrice, imp.getId(), bidRequest)
104+
: initialBidFloorPrice;
105+
}
106+
107+
private Price convertBidFloor(Price bidFloorPrice, String impId, BidRequest bidRequest) {
108+
final String bidFloorCur = bidFloorPrice.getCurrency();
109+
try {
110+
final BigDecimal convertedPrice = currencyConversionService
111+
.convertCurrency(bidFloorPrice.getValue(), bidRequest, bidFloorCur, BIDDER_CURRENCY);
112+
113+
return Price.of(BIDDER_CURRENCY, convertedPrice);
114+
} catch (PreBidException e) {
115+
throw new PreBidException(
116+
"Unable to convert provided bid floor currency from %s to %s for imp `%s`"
117+
.formatted(bidFloorCur, BIDDER_CURRENCY, impId));
118+
}
119+
}
120+
121+
private ExtImpSeedtag parseImpExt(Imp imp) {
122+
try {
123+
return mapper.mapper().convertValue(imp.getExt(), SEEDTAG_EXT_TYPE_REFERENCE).getBidder();
124+
} catch (IllegalArgumentException e) {
125+
throw new PreBidException(e.getMessage());
126+
}
127+
}
128+
129+
@Override
130+
public final Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
131+
try {
132+
final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
133+
final List<BidderError> errors = new ArrayList<>();
134+
final List<BidderBid> bidderBids = extractBids(bidResponse, errors);
135+
return Result.of(bidderBids, errors);
136+
} catch (DecodeException | PreBidException e) {
137+
return Result.withError(BidderError.badServerResponse(e.getMessage()));
138+
}
139+
}
140+
141+
private static List<BidderBid> extractBids(BidResponse bidResponse, List<BidderError> errors) {
142+
if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) {
143+
return Collections.emptyList();
144+
}
145+
146+
return bidResponse.getSeatbid().stream()
147+
.filter(Objects::nonNull)
148+
.map(SeatBid::getBid)
149+
.filter(Objects::nonNull)
150+
.flatMap(Collection::stream)
151+
.map(bid -> makeBidderBid(bid, errors))
152+
.filter(Objects::nonNull)
153+
.toList();
154+
}
155+
156+
private static BidderBid makeBidderBid(Bid bid, List<BidderError> errors) {
157+
final BidType bidType;
158+
try {
159+
bidType = getBidType(bid);
160+
} catch (PreBidException e) {
161+
errors.add(BidderError.badServerResponse(e.getMessage()));
162+
return null;
163+
}
164+
165+
return BidderBid.of(bid, bidType, BIDDER_CURRENCY);
166+
}
167+
168+
private static BidType getBidType(Bid bid) {
169+
return switch (bid.getMtype()) {
170+
case 1 -> BidType.banner;
171+
case 2 -> BidType.video;
172+
default -> throw new PreBidException(
173+
"Invalid bid.mtype for bid.id: '%s'"
174+
.formatted(bid.getId()));
175+
};
176+
}
177+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.prebid.server.proto.openrtb.ext.request.seedtag;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import lombok.Value;
5+
6+
@Value(staticConstructor = "of")
7+
public class ExtImpSeedtag {
8+
9+
@JsonProperty("adUnitId")
10+
String adUnitId;
11+
12+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package org.prebid.server.spring.config.bidder;
2+
3+
import org.prebid.server.bidder.BidderDeps;
4+
import org.prebid.server.bidder.seedtag.SeedtagBidder;
5+
import org.prebid.server.currency.CurrencyConversionService;
6+
import org.prebid.server.json.JacksonMapper;
7+
import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties;
8+
import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler;
9+
import org.prebid.server.spring.config.bidder.util.UsersyncerCreator;
10+
import org.prebid.server.spring.env.YamlPropertySourceFactory;
11+
import org.springframework.beans.factory.annotation.Value;
12+
import org.springframework.boot.context.properties.ConfigurationProperties;
13+
import org.springframework.context.annotation.Bean;
14+
import org.springframework.context.annotation.Configuration;
15+
import org.springframework.context.annotation.PropertySource;
16+
17+
import jakarta.validation.constraints.NotBlank;
18+
19+
@Configuration
20+
@PropertySource(value = "classpath:/bidder-config/seedtag.yaml", factory = YamlPropertySourceFactory.class)
21+
public class SeedtagConfiguration {
22+
23+
private static final String BIDDER_NAME = "seedtag";
24+
25+
@Bean("seedtagConfigurationProperties")
26+
@ConfigurationProperties("adapters.seedtag")
27+
BidderConfigurationProperties configurationProperties() {
28+
return new BidderConfigurationProperties();
29+
}
30+
31+
@Bean
32+
BidderDeps seedtagBidderDeps(BidderConfigurationProperties seedtagConfigurationProperties,
33+
@NotBlank @Value("${external-url}") String externalUrl,
34+
CurrencyConversionService currencyConversionService,
35+
JacksonMapper mapper) {
36+
37+
return BidderDepsAssembler.forBidder(BIDDER_NAME)
38+
.withConfig(seedtagConfigurationProperties)
39+
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
40+
.bidderCreator(config -> new SeedtagBidder(config.getEndpoint(),
41+
currencyConversionService,
42+
mapper))
43+
.assemble();
44+
}
45+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
adapters:
2+
seedtag:
3+
endpoint: "https://s.seedtag.com/c/openrtb?partner=prebidserver"
4+
endpoint-compression: gzip
5+
meta-info:
6+
maintainer-email: prebid@seedtag.com
7+
site-media-types:
8+
- banner
9+
- video
10+
supported-vendors:
11+
vendor-id: 157
12+
usersync:
13+
cookie-family-name: seedtag
14+
iframe:
15+
url: https://s.seedtag.com/cs/cookiesync/prebid?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&usp_consent={{us_privacy}}&redirect={{redirect_url}}
16+
support-cors: false
17+
uid-macro: '$UID'
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-04/schema#",
3+
"title": "Seedtag Adapter Params",
4+
"description": "A schema which validates params accepted by the Seedtag adapter",
5+
"type": "object",
6+
"properties": {
7+
"adUnitId": {
8+
"type": "string",
9+
"description": "Ad Unit ID",
10+
"minLength": 1
11+
}
12+
},
13+
"required": [
14+
"adUnitId"
15+
]
16+
}

0 commit comments

Comments
 (0)