diff --git a/bubble-server/src/main/java/bubble/dao/app/AppMessageDAO.java b/bubble-server/src/main/java/bubble/dao/app/AppMessageDAO.java index 5f326c69..0b6f9a17 100644 --- a/bubble-server/src/main/java/bubble/dao/app/AppMessageDAO.java +++ b/bubble-server/src/main/java/bubble/dao/app/AppMessageDAO.java @@ -33,6 +33,14 @@ public class AppMessageDAO extends AccountOwnedTemplateDAO { return messages.isEmpty() ? null : messages.get(0); } + public AppMessage findByAccountAndAppBestEffort (String account, String app, String locale) { + AppMessage message = findByAccountAndAppAndLocale(account, app, locale); + if (message != null) return message; + message = findByAccountAndAppAndDefaultLocale(account, app); + if (message != null) return message; + return findByAccountAndAppAndHighestPriority(account, app); + } + public List findByApp(String app) { return findByField("app", app); } } diff --git a/bubble-server/src/main/java/bubble/model/app/AppDataConfig.java b/bubble-server/src/main/java/bubble/model/app/AppDataConfig.java index 043d32ec..db27eb68 100644 --- a/bubble-server/src/main/java/bubble/model/app/AppDataConfig.java +++ b/bubble-server/src/main/java/bubble/model/app/AppDataConfig.java @@ -23,8 +23,10 @@ public class AppDataConfig { public boolean hasFields () { return !empty(fields); } @Getter @Setter private EntityFieldConfig[] params; + public boolean hasParams () { return !empty(params); } @Getter @Setter private AppDataAction[] actions; + public boolean hasActions () { return !empty(actions); } @Getter @Setter private AppDataView[] views; public boolean hasViews () { return !empty(views); } diff --git a/bubble-server/src/main/java/bubble/model/app/BubbleApp.java b/bubble-server/src/main/java/bubble/model/app/BubbleApp.java index 89d613b4..4b98575e 100644 --- a/bubble-server/src/main/java/bubble/model/app/BubbleApp.java +++ b/bubble-server/src/main/java/bubble/model/app/BubbleApp.java @@ -42,7 +42,7 @@ import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENC_PAD; }) public class BubbleApp extends IdentifiableBaseParentEntity implements AccountTemplate { - private static final String[] VALUE_FIELDS = {"url", "description", "template", "enabled"}; + private static final String[] VALUE_FIELDS = {"url", "description", "template", "enabled", "dataConfig"}; public BubbleApp(Account account, BubbleApp app) { copy(this, app); diff --git a/bubble-server/src/main/java/bubble/resources/message/MessagesResource.java b/bubble-server/src/main/java/bubble/resources/message/MessagesResource.java index 7b9c558d..cef3ee9d 100644 --- a/bubble-server/src/main/java/bubble/resources/message/MessagesResource.java +++ b/bubble-server/src/main/java/bubble/resources/message/MessagesResource.java @@ -2,6 +2,7 @@ package bubble.resources.message; import bubble.model.account.Account; import bubble.server.BubbleConfiguration; +import bubble.service.message.AppMessageService; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.ArrayUtils; import org.cobbzilla.util.collection.ArrayUtil; @@ -34,10 +35,24 @@ public class MessagesResource { public static final String RESOURCE_MESSAGES_PROPS = "ResourceMessages.properties"; public static final String[] PRE_AUTH_MESSAGE_GROUPS = {"pre_auth", "countries", "timezones"}; - public static final String[] ALL_MESSAGE_GROUPS = ArrayUtil.append(PRE_AUTH_MESSAGE_GROUPS, "post_auth"); + public static final String APPS_MESSAGE_GROUP = "apps"; + public static final String[] ALL_MESSAGE_GROUPS + = ArrayUtil.append(PRE_AUTH_MESSAGE_GROUPS, "post_auth", APPS_MESSAGE_GROUP); + + @Autowired private AppMessageService appMessageService; @Autowired private BubbleConfiguration configuration; + private Map> messageCache = new ConcurrentHashMap<>(); + + @DELETE + public Response flushMessageCache (@Context ContainerRequest ctx) { + final Account caller = userPrincipal(ctx); + if (!caller.admin()) return forbidden(); + messageCache.clear(); + return ok_empty(); + } + @GET @Path("/{locale}/{group}") public Response loadMessagesByGroup(@Context ContainerRequest ctx, @PathParam("locale") String locale, @@ -59,22 +74,29 @@ public class MessagesResource { for (String loc : locales) { try { - return ok_utf8(loadMessages(loc, group, format)); + return ok_utf8(loadMessages(caller, loc, group, format)); } catch (Exception e) { - log.debug("loadMessagesByGroup: error loading group "+group+" for locale "+loc+": "+e); + log.debug("loadMessagesByGroup: error loading group "+group+" for locale "+loc+": "+e, e); } } log.error("loadMessagesByGroup: error loading group "+group+" for any locale: "+locales); return notFound(locale+"/"+group); } - private Map> messageCache = new ConcurrentHashMap<>(); + private Map loadMessages(Account caller, String locale, String group, MessageResourceFormat format) throws IOException { - private Map loadMessages(String locale, String group, MessageResourceFormat format) throws IOException { - final String cacheKey = locale+"/"+group+"/"+format; + final boolean isAppsGroup = group.equalsIgnoreCase(APPS_MESSAGE_GROUP); + if (isAppsGroup && caller == null) return Collections.emptyMap(); + + final String cacheKey = (isAppsGroup ? caller.getUuid()+"/" : "") + locale + "/" + group + "/" + format; if (!messageCache.containsKey(cacheKey)) { - final Properties props = new Properties(); - props.load(new BufferedReader(new InputStreamReader(loadResourceAsStream(MESSAGE_RESOURCE_BASE + locale + MESSAGE_RESOURCE_PATH + group + "/" + RESOURCE_MESSAGES_PROPS), UTF8cs))); + final Properties props; + if (isAppsGroup) { + props = appMessageService.loadAppMessages(caller, locale); + } else { + props = new Properties(); + props.load(new BufferedReader(new InputStreamReader(loadResourceAsStream(MESSAGE_RESOURCE_BASE + locale + MESSAGE_RESOURCE_PATH + group + "/" + RESOURCE_MESSAGES_PROPS), UTF8cs))); + } final Map messages = new LinkedHashMap<>(); props.forEach((key, value) -> messages.put(format.format(key.toString()), value.toString())); messageCache.put(cacheKey, messages); diff --git a/bubble-server/src/main/java/bubble/service/message/AppMessageService.java b/bubble-server/src/main/java/bubble/service/message/AppMessageService.java new file mode 100644 index 00000000..bf58f27f --- /dev/null +++ b/bubble-server/src/main/java/bubble/service/message/AppMessageService.java @@ -0,0 +1,88 @@ +package bubble.service.message; + +import bubble.dao.app.AppMessageDAO; +import bubble.dao.app.BubbleAppDAO; +import bubble.model.account.Account; +import bubble.model.app.*; +import lombok.extern.slf4j.Slf4j; +import org.cobbzilla.util.collection.NameAndValue; +import org.cobbzilla.wizard.model.entityconfig.EntityFieldConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Map; +import java.util.Properties; + +@Service @Slf4j +public class AppMessageService { + + public static final String MSG_PREFIX_APP = "app."; + public static final String MSG_SUFFIX_DESCRIPTION = "description"; + public static final String MSG_SUFFIX_NAME = "name"; + public static final String MSG_SUFFIX_FIELD = "field."; + public static final String MSG_SUFFIX_PARAM = "param."; + public static final String MSG_SUFFIX_VIEW = "view."; + public static final String MSG_SUFFIX_ACTION = "action."; + + @Autowired private BubbleAppDAO appDAO; + @Autowired private AppMessageDAO appMessageDAO; + + public Properties loadAppMessages(Account account, String locale) { + final Properties props = new Properties(); + for (BubbleApp app : appDAO.findByAccount(account.getUuid())) { + final AppMessage appMessage = appMessageDAO.findByAccountAndAppBestEffort(account.getUuid(), app.getUuid(), locale); + final Map messages = NameAndValue.toMap(appMessage == null ? null : appMessage.getMessages()); + + final String msgPrefix = MSG_PREFIX_APP + app.getName() + "."; + for (Map.Entry message : messages.entrySet()) { + props.setProperty(msgPrefix+message.getKey(), message.getValue()); + } + + // Check for 'name' message, if not found use default + final String nameKey = msgPrefix + MSG_SUFFIX_NAME; + if (!props.containsKey(nameKey)) props.setProperty(nameKey, app.getName()); + + // Check for 'description' message, if not found use default + final String descriptionKey = msgPrefix + MSG_SUFFIX_DESCRIPTION; + if (!props.containsKey(descriptionKey)) props.setProperty(descriptionKey, app.getDescription()); + + if (app.hasDataConfig()) { + final AppDataConfig cfg = app.getDataConfig(); + + // Check for field messages + if (cfg.hasFields()) { + for (String field : cfg.getFields()) { + final String fieldKey = msgPrefix + MSG_SUFFIX_FIELD + field; + if (!props.containsKey(fieldKey)) props.setProperty(fieldKey, field); + } + } + + // Check for view messages + if (cfg.hasViews()) { + for (AppDataView view : cfg.getViews()) { + final String viewKey = msgPrefix + MSG_SUFFIX_VIEW + view.getName(); + if (!props.containsKey(viewKey)) props.setProperty(viewKey, view.getName()); + } + } + + // Check for param messages + if (cfg.hasParams()) { + for (EntityFieldConfig param : cfg.getParams()) { + final String paramKey = msgPrefix + MSG_SUFFIX_PARAM + param.getName(); + if (!props.containsKey(paramKey)) props.setProperty(paramKey, param.getName()); + } + } + + // Check for action messages + if (cfg.hasActions()) { + for (AppDataAction action : cfg.getActions()) { + final String actionKey = msgPrefix + MSG_SUFFIX_ACTION + action.getName(); + if (!props.containsKey(actionKey)) props.setProperty(actionKey, action.getName()); + } + } + } + } + return props; + } + +} diff --git a/bubble-server/src/main/resources/logback.xml b/bubble-server/src/main/resources/logback.xml index d9f0d7ac..ccdb535f 100644 --- a/bubble-server/src/main/resources/logback.xml +++ b/bubble-server/src/main/resources/logback.xml @@ -38,6 +38,7 @@ + diff --git a/bubble-server/src/main/resources/models/apps/analytics/bubbleApp_analytics.json b/bubble-server/src/main/resources/models/apps/analytics/bubbleApp_analytics.json index 7594b549..6f0521ae 100644 --- a/bubble-server/src/main/resources/models/apps/analytics/bubbleApp_analytics.json +++ b/bubble-server/src/main/resources/models/apps/analytics/bubbleApp_analytics.json @@ -13,7 +13,7 @@ {"name": "most_recent"}, {"name": "today"}, {"name": "last_24_hours"}, - {"name": "last_week"}, + {"name": "last_7_days"}, {"name": "last_30_days"} ] }, @@ -33,13 +33,16 @@ "AppMessage": [{ "locale": "en_US", "messages": [ - {"name": "app.description", "value": "Traffic analytics for your Bubble. Manage block lists."}, + {"name": "name", "value": "Traffic Analytics"}, + {"name": "description", "value": "Traffic analytics for your Bubble. Manage block lists."}, + {"name": "field.key", "value": "URL"}, + {"name": "field.data", "value": "Count"}, {"name": "param.site", "value": "Site"}, {"name": "view.most_recent", "value": "Most Recent Traffic"}, {"name": "view.today", "value": "Today"}, {"name": "view.last_24_hours", "value": "Last 24 Hours"}, - {"name": "view.last_week", "value": "Last Week"}, - {"name": "view.last_30_days", "value": "LAst 30 Days"} + {"name": "view.last_7_days", "value": "Last 7 Days"}, + {"name": "view.last_30_days", "value": "Last 30 Days"} ] }] } diff --git a/bubble-server/src/main/resources/models/apps/user_block/bubbleApp_userBlock.json b/bubble-server/src/main/resources/models/apps/user_block/bubbleApp_userBlock.json index 2801fb61..5755b254 100644 --- a/bubble-server/src/main/resources/models/apps/user_block/bubbleApp_userBlock.json +++ b/bubble-server/src/main/resources/models/apps/user_block/bubbleApp_userBlock.json @@ -20,8 +20,14 @@ "AppMessage": [{ "locale": "en_US", "messages": [ - {"name": "app.description", "value": "User Blocker"}, - {"name": "view.blocked_users", "value": "Blocked Users"} + {"name": "name", "value": "User Blocker"}, + {"name": "description", "value": "Throw the garbage to the curb!"}, + {"name": "view.blocked_users", "value": "Blocked Users"}, + {"name": "field.key", "value": "Username"}, + {"name": "field.enabled", "value": "Enforce Block"}, + {"name": "action.enable", "value": "Enforce Block"}, + {"name": "action.disable", "value": "Disable Block"}, + {"name": "action.delete", "value": "Delete Block"} ] }] } diff --git a/bubble-web b/bubble-web index cf9ff75d..75079a27 160000 --- a/bubble-web +++ b/bubble-web @@ -1 +1 @@ -Subproject commit cf9ff75d0eabf7c3d68cb6ca19fd70b74103b70a +Subproject commit 75079a27bbda0fdf72480e79332be960c4cd0676