Bring badge configuration into levels information

This commit is contained in:
Ehren Kret
2021-10-14 11:35:18 -05:00
parent fe21d014f7
commit c0837104cd
5 changed files with 118 additions and 46 deletions

View File

@@ -72,7 +72,6 @@ import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator;
import org.whispersystems.textsecuregcm.auth.WebsocketRefreshApplicationEventListener;
import org.whispersystems.textsecuregcm.badges.ConfiguredProfileBadgeConverter;
import org.whispersystems.textsecuregcm.badges.ProfileBadgeConverter;
import org.whispersystems.textsecuregcm.configuration.DirectoryServerConfiguration;
import org.whispersystems.textsecuregcm.controllers.AccountController;
import org.whispersystems.textsecuregcm.controllers.AttachmentControllerV1;
@@ -296,7 +295,8 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
environment.getObjectMapper().setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
environment.getObjectMapper().setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
ProfileBadgeConverter profileBadgeConverter = new ConfiguredProfileBadgeConverter(clock, config.getBadges());
ConfiguredProfileBadgeConverter profileBadgeConverter =
new ConfiguredProfileBadgeConverter(clock, config.getBadges());
JdbiFactory jdbiFactory = new JdbiFactory(DefaultNameStrategy.CHECK_EMPTY);
Jdbi accountJdbi = jdbiFactory.build(environment, config.getAccountsDatabaseConfiguration(), "accountdb");
@@ -636,7 +636,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
);
if (config.getSubscription() != null) {
commonControllers.add(new SubscriptionController(clock, config.getSubscription(), subscriptionManager,
stripeManager, zkReceiptOperations, issuedReceiptsManager));
stripeManager, zkReceiptOperations, issuedReceiptsManager, profileBadgeConverter));
}
for (Object controller : commonControllers) {

View File

@@ -0,0 +1,14 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.badges;
import java.util.List;
import java.util.Locale;
import org.whispersystems.textsecuregcm.entities.Badge;
public interface BadgeTranslator {
Badge translate(List<Locale> acceptableLanguages, String badgeId);
}

View File

@@ -18,13 +18,14 @@ import java.util.ResourceBundle;
import java.util.ResourceBundle.Control;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import org.whispersystems.textsecuregcm.configuration.BadgeConfiguration;
import org.whispersystems.textsecuregcm.configuration.BadgesConfiguration;
import org.whispersystems.textsecuregcm.entities.Badge;
import org.whispersystems.textsecuregcm.entities.SelfBadge;
import org.whispersystems.textsecuregcm.storage.AccountBadge;
public class ConfiguredProfileBadgeConverter implements ProfileBadgeConverter {
public class ConfiguredProfileBadgeConverter implements ProfileBadgeConverter, BadgeTranslator {
private static final int MAX_LOCALES = 15;
@VisibleForTesting
@@ -53,6 +54,23 @@ public class ConfiguredProfileBadgeConverter implements ProfileBadgeConverter {
this.resourceBundleFactory = resourceBundleFactory;
}
@Override
public Badge translate(final List<Locale> acceptableLanguages, final String badgeId) {
final List<Locale> acceptableLocales = getAcceptableLocales(acceptableLanguages);
final ResourceBundle resourceBundle = getResourceBundle(acceptableLocales);
final BadgeConfiguration configuration = knownBadges.get(badgeId);
return newBadge(
false,
configuration.getId(),
configuration.getCategory(),
resourceBundle.getString(configuration.getId() + "_name"),
resourceBundle.getString(configuration.getId() + "_description"),
configuration.getSprites(),
configuration.getSvgs(),
null,
false);
}
@Override
public List<Badge> convert(
final List<Locale> acceptableLanguages,
@@ -63,35 +81,8 @@ public class ConfiguredProfileBadgeConverter implements ProfileBadgeConverter {
}
final Instant now = clock.instant();
final List<Locale> acceptableLocales = acceptableLanguages.stream().limit(MAX_LOCALES).distinct()
.collect(Collectors.toList());
final Locale desiredLocale = acceptableLocales.isEmpty() ? Locale.getDefault() : acceptableLocales.get(0);
// define a control with a fallback order as specified in the header
ResourceBundle.Control control = new Control() {
@Override
public List<String> getFormats(final String baseName) {
Objects.requireNonNull(baseName);
return Control.FORMAT_PROPERTIES;
}
@Override
public Locale getFallbackLocale(final String baseName, final Locale locale) {
Objects.requireNonNull(baseName);
if (locale.equals(Locale.getDefault())) {
return null;
}
final int localeIndex = acceptableLocales.indexOf(locale);
if (localeIndex < 0 || localeIndex >= acceptableLocales.size() - 1) {
return Locale.getDefault();
}
// [0, acceptableLocales.size() - 2] is now the possible range for localeIndex
return acceptableLocales.get(localeIndex + 1);
}
};
final ResourceBundle resourceBundle = resourceBundleFactory.createBundle(BASE_NAME, desiredLocale, control);
final List<Locale> acceptableLocales = getAcceptableLocales(acceptableLanguages);
final ResourceBundle resourceBundle = getResourceBundle(acceptableLocales);
List<Badge> badges = accountBadges.stream()
.filter(accountBadge -> (isSelf || accountBadge.isVisible())
&& now.isBefore(accountBadge.getExpiration())
@@ -126,6 +117,40 @@ public class ConfiguredProfileBadgeConverter implements ProfileBadgeConverter {
return badges;
}
@Nonnull
private ResourceBundle getResourceBundle(final List<Locale> acceptableLocales) {
final Locale desiredLocale = acceptableLocales.isEmpty() ? Locale.getDefault() : acceptableLocales.get(0);
// define a control with a fallback order as specified in the header
Control control = new Control() {
@Override
public List<String> getFormats(final String baseName) {
Objects.requireNonNull(baseName);
return Control.FORMAT_PROPERTIES;
}
@Override
public Locale getFallbackLocale(final String baseName, final Locale locale) {
Objects.requireNonNull(baseName);
if (locale.equals(Locale.getDefault())) {
return null;
}
final int localeIndex = acceptableLocales.indexOf(locale);
if (localeIndex < 0 || localeIndex >= acceptableLocales.size() - 1) {
return Locale.getDefault();
}
// [0, acceptableLocales.size() - 2] is now the possible range for localeIndex
return acceptableLocales.get(localeIndex + 1);
}
};
return resourceBundleFactory.createBundle(BASE_NAME, desiredLocale, control);
}
@Nonnull
private List<Locale> getAcceptableLocales(final List<Locale> acceptableLanguages) {
return acceptableLanguages.stream().limit(MAX_LOCALES).distinct().collect(Collectors.toList());
}
private Badge newBadge(
final boolean isSelf,
final String id,

View File

@@ -44,7 +44,10 @@ import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.Produces;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
@@ -56,9 +59,11 @@ import org.signal.zkgroup.receipts.ServerZkReceiptOperations;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.badges.BadgeTranslator;
import org.whispersystems.textsecuregcm.configuration.SubscriptionConfiguration;
import org.whispersystems.textsecuregcm.configuration.SubscriptionLevelConfiguration;
import org.whispersystems.textsecuregcm.configuration.SubscriptionPriceConfiguration;
import org.whispersystems.textsecuregcm.entities.Badge;
import org.whispersystems.textsecuregcm.storage.IssuedReceiptsManager;
import org.whispersystems.textsecuregcm.storage.SubscriptionManager;
import org.whispersystems.textsecuregcm.storage.SubscriptionManager.GetResult;
@@ -76,6 +81,7 @@ public class SubscriptionController {
private final StripeManager stripeManager;
private final ServerZkReceiptOperations zkReceiptOperations;
private final IssuedReceiptsManager issuedReceiptsManager;
private final BadgeTranslator badgeTranslator;
public SubscriptionController(
@Nonnull Clock clock,
@@ -83,13 +89,15 @@ public class SubscriptionController {
@Nonnull SubscriptionManager subscriptionManager,
@Nonnull StripeManager stripeManager,
@Nonnull ServerZkReceiptOperations zkReceiptOperations,
@Nonnull IssuedReceiptsManager issuedReceiptsManager) {
@Nonnull IssuedReceiptsManager issuedReceiptsManager,
@Nonnull BadgeTranslator badgeTranslator) {
this.clock = Objects.requireNonNull(clock);
this.config = Objects.requireNonNull(config);
this.subscriptionManager = Objects.requireNonNull(subscriptionManager);
this.stripeManager = Objects.requireNonNull(stripeManager);
this.zkReceiptOperations = Objects.requireNonNull(zkReceiptOperations);
this.issuedReceiptsManager = Objects.requireNonNull(issuedReceiptsManager);
this.badgeTranslator = Objects.requireNonNull(badgeTranslator);
}
@Timed
@@ -328,19 +336,19 @@ public class SubscriptionController {
public static class Level {
private final String badgeId;
private final Badge badge;
private final Map<String, BigDecimal> currencies;
@JsonCreator
public Level(
@JsonProperty("badgeId") String badgeId,
@JsonProperty("badge") Badge badge,
@JsonProperty("currencies") Map<String, BigDecimal> currencies) {
this.badgeId = badgeId;
this.badge = badge;
this.currencies = currencies;
}
public String getBadgeId() {
return badgeId;
public Badge getBadge() {
return badge;
}
public Map<String, BigDecimal> getCurrencies() {
@@ -366,11 +374,13 @@ public class SubscriptionController {
@Path("/levels")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public CompletableFuture<Response> getLevels() {
public CompletableFuture<Response> getLevels(@Context ContainerRequestContext containerRequestContext) {
return CompletableFuture.supplyAsync(() -> {
List<Locale> acceptableLanguages = getAcceptableLanguagesForRequest(containerRequestContext);
GetLevelsResponse getLevelsResponse = new GetLevelsResponse(
config.getLevels().entrySet().stream().collect(Collectors.toMap(Entry::getKey,
entry -> new GetLevelsResponse.Level(entry.getValue().getBadge(),
entry -> new GetLevelsResponse.Level(
badgeTranslator.translate(acceptableLanguages, entry.getValue().getBadge()),
entry.getValue().getPrices().entrySet().stream().collect(
Collectors.toMap(levelEntry -> levelEntry.getKey().toUpperCase(Locale.ROOT),
levelEntry -> levelEntry.getValue().getAmount()))))));
@@ -620,6 +630,15 @@ public class SubscriptionController {
}
}
private List<Locale> getAcceptableLanguagesForRequest(ContainerRequestContext containerRequestContext) {
try {
return containerRequestContext.getAcceptableLanguages();
} catch (final ProcessingException e) {
logger.warn("Could not get acceptable languages", e);
return List.of();
}
}
private static class RequestData {
public final byte[] subscriberBytes;