From 9f3c5946f27d5a77653763e38a63350a6fced140 Mon Sep 17 00:00:00 2001 From: Jonathan Cobb Date: Tue, 31 Dec 2019 11:43:14 -0500 Subject: [PATCH] implement seaching across all entities --- .../src/main/java/bubble/ApiConstants.java | 6 +- .../GeoLocateServiceDriverBase.java | 3 +- .../java/bubble/model/account/Account.java | 2 + .../model/account/message/AccountMessage.java | 10 +- .../main/java/bubble/model/app/AppData.java | 10 ++ .../java/bubble/model/app/AppMatcher.java | 10 ++ .../main/java/bubble/model/app/AppRule.java | 8 ++ .../main/java/bubble/model/app/AppSite.java | 7 + .../main/java/bubble/model/app/BubbleApp.java | 7 + .../java/bubble/model/app/RuleDriver.java | 9 ++ .../bubble/model/bill/AccountPayment.java | 12 ++ .../model/bill/AccountPaymentMethod.java | 10 +- .../java/bubble/model/bill/AccountPlan.java | 13 ++ .../src/main/java/bubble/model/bill/Bill.java | 12 ++ .../java/bubble/model/bill/BubblePlan.java | 14 ++ .../java/bubble/model/cloud/AnsibleRole.java | 7 + .../java/bubble/model/cloud/BubbleBackup.java | 8 +- .../java/bubble/model/cloud/BubbleDomain.java | 1 + .../bubble/model/cloud/BubbleFootprint.java | 1 + .../bubble/model/cloud/BubbleNetwork.java | 3 +- .../java/bubble/model/cloud/BubbleNode.java | 6 +- .../bubble/model/cloud/BubbleNodeKey.java | 8 +- .../java/bubble/model/cloud/CloudService.java | 1 + .../model/cloud/notify/NotificationBase.java | 9 ++ .../cloud/notify/ReceivedNotification.java | 6 +- .../model/cloud/notify/SentNotification.java | 6 +- .../resources/EntityConfigsResource.java | 2 +- .../bubble/resources/IdentityResource.java | 74 +++++++++++ .../java/bubble/resources/SearchResource.java | 121 ++++++++++++++++++ .../bubble/resources/account/MeResource.java | 76 ----------- .../java/bubble/test/dev/DevServerTest.java | 8 ++ utils/cobbzilla-utils | 2 +- utils/cobbzilla-wizard | 2 +- 33 files changed, 370 insertions(+), 104 deletions(-) create mode 100644 bubble-server/src/main/java/bubble/resources/IdentityResource.java create mode 100644 bubble-server/src/main/java/bubble/resources/SearchResource.java diff --git a/bubble-server/src/main/java/bubble/ApiConstants.java b/bubble-server/src/main/java/bubble/ApiConstants.java index c39b3a6d..a7364198 100644 --- a/bubble-server/src/main/java/bubble/ApiConstants.java +++ b/bubble-server/src/main/java/bubble/ApiConstants.java @@ -31,6 +31,8 @@ import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx; @Slf4j public class ApiConstants { + public static final int MAX_SEARCH_PAGE = 50; + @Getter(lazy=true) private static final String bubbleDefaultDomain = initDefaultDomain(); private static String initDefaultDomain() { final File f = new File(HOME_DIR, ".BUBBLE_DEFAULT_DOMAIN"); @@ -149,14 +151,14 @@ public class ApiConstants { public static final String EP_RESTORE = "/restore"; public static final String EP_KEYS = "/keys"; public static final String EP_STATUS = "/status"; - public static final String EP_ID = "/id"; - public static final String EP_SEARCH = "/search"; public static final String EP_FORK = "/fork"; public static final String DETECT_ENDPOINT = "/detect"; public static final String EP_LOCALE = "/locale"; public static final String EP_TIMEZONE = "/timezone"; + public static final String ID_ENDPOINT = "/id"; + public static final String SEARCH_ENDPOINT = "/search"; public static final String DEBUG_ENDPOINT = "/debug"; public static final String BUBBLE_MAGIC_ENDPOINT = "/.bubble"; public static final String EP_ASSETS = "/assets"; diff --git a/bubble-server/src/main/java/bubble/cloud/geoLocation/GeoLocateServiceDriverBase.java b/bubble-server/src/main/java/bubble/cloud/geoLocation/GeoLocateServiceDriverBase.java index a4fc2168..3b829e94 100644 --- a/bubble-server/src/main/java/bubble/cloud/geoLocation/GeoLocateServiceDriverBase.java +++ b/bubble-server/src/main/java/bubble/cloud/geoLocation/GeoLocateServiceDriverBase.java @@ -26,7 +26,6 @@ import static org.cobbzilla.util.daemon.ZillaRuntime.die; import static org.cobbzilla.util.daemon.ZillaRuntime.hashOf; import static org.cobbzilla.util.io.FileUtil.*; import static org.cobbzilla.util.json.JsonUtil.json; -import static org.cobbzilla.util.security.ShaUtil.sha256_hex; import static org.cobbzilla.wizard.cache.redis.RedisService.EX; @Slf4j @@ -75,7 +74,7 @@ public abstract class GeoLocateServiceDriverBase extends CloudServiceDriverBa final HttpRequestBean request = new HttpRequestBean(urlWithLicense).setHeaders(headers); final HttpMeta meta = HttpUtil.getHeadMetadata(request); - final String uniq = sha256_hex(hashOf(url, headers)); + final String uniq = hashOf(url, headers); final String dbKey = "dbcache_" + uniq; final File dbFile = cloudDataDAO.getFile(cloud.getUuid(), dbKey); if (!meta.shouldRefresh(dbFile)) return dbFile; // we are current! diff --git a/bubble-server/src/main/java/bubble/model/account/Account.java b/bubble-server/src/main/java/bubble/model/account/Account.java index cce2c731..f98458ef 100644 --- a/bubble-server/src/main/java/bubble/model/account/Account.java +++ b/bubble-server/src/main/java/bubble/model/account/Account.java @@ -41,6 +41,7 @@ import static org.cobbzilla.util.system.Sleep.sleep; import static org.cobbzilla.util.time.TimeUtil.formatDuration; import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENCRYPTED_STRING; import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENC_PAD; +import static org.cobbzilla.wizard.model.entityconfig.annotations.ECForeignKeySearchDepth.none; import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx; @ECType(root=true) @ECTypeURIs(listFields={"name", "url", "description", "admin", "suspended"}, isDeleteDefined=false) @@ -56,6 +57,7 @@ import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx; @ECTypeChild(type=BubbleNode.class, backref="account"), @ECTypeChild(type=SentNotification.class, backref="account") }) +@ECSearchDepth(fkDepth=none) @Entity @NoArgsConstructor @Accessors(chain=true) @Slf4j public class Account extends IdentifiableBase implements TokenPrincipal, SqlViewSearchResult { diff --git a/bubble-server/src/main/java/bubble/model/account/message/AccountMessage.java b/bubble-server/src/main/java/bubble/model/account/message/AccountMessage.java index 76483c85..70a94ff3 100644 --- a/bubble-server/src/main/java/bubble/model/account/message/AccountMessage.java +++ b/bubble-server/src/main/java/bubble/model/account/message/AccountMessage.java @@ -11,10 +11,7 @@ import lombok.ToString; import lombok.experimental.Accessors; import lombok.extern.slf4j.Slf4j; import org.cobbzilla.wizard.model.IdentifiableBase; -import org.cobbzilla.wizard.model.entityconfig.annotations.ECForeignKey; -import org.cobbzilla.wizard.model.entityconfig.annotations.ECIndex; -import org.cobbzilla.wizard.model.entityconfig.annotations.ECIndexes; -import org.cobbzilla.wizard.model.entityconfig.annotations.ECType; +import org.cobbzilla.wizard.model.entityconfig.annotations.*; import org.hibernate.annotations.Type; import javax.persistence.*; @@ -49,21 +46,26 @@ public class AccountMessage extends IdentifiableBase implements HasAccount { public boolean hasContact () { return !empty(contact); } public boolean isSameContact (String uuid) { return hasContact() && contact.equals(uuid); } + @ECSearchable(filter=true) @ECIndex @Column(length=UUID_MAXLEN, nullable=false, updatable=false) @Getter @Setter private String name; + @ECSearchable(filter=true) @Size(max=100, message="err.remoteHost.length") @Type(type=ENCRYPTED_STRING) @Column(columnDefinition="varchar("+(100+ENC_PAD)+")") @Getter @Setter private String remoteHost; + @ECSearchable @Enumerated(EnumType.STRING) @ECIndex @Column(length=20, nullable=false, updatable=false) @Getter @Setter private AccountMessageType messageType; + @ECSearchable @Enumerated(EnumType.STRING) @ECIndex @Column(length=20, nullable=false, updatable=false) @Getter @Setter private AccountAction action; + @ECSearchable @Enumerated(EnumType.STRING) @ECIndex @Column(length=20, nullable=false, updatable=false) @Getter @Setter private ActionTarget target; diff --git a/bubble-server/src/main/java/bubble/model/app/AppData.java b/bubble-server/src/main/java/bubble/model/app/AppData.java index f6343b54..3d75c914 100644 --- a/bubble-server/src/main/java/bubble/model/app/AppData.java +++ b/bubble-server/src/main/java/bubble/model/app/AppData.java @@ -9,6 +9,7 @@ import lombok.experimental.Accessors; import org.cobbzilla.util.collection.ArrayUtil; import org.cobbzilla.wizard.model.Identifiable; import org.cobbzilla.wizard.model.IdentifiableBase; +import org.cobbzilla.wizard.model.entityconfig.EntityFieldType; import org.cobbzilla.wizard.model.entityconfig.annotations.*; import org.cobbzilla.wizard.validation.HasValue; import org.hibernate.annotations.Type; @@ -46,30 +47,36 @@ public class AppData extends IdentifiableBase implements AppTemplateEntity { @Override @Transient public String getName() { return getKey(); } public AppData setName(String n) { return setKey(n); } + @ECSearchable @ECForeignKey(entity=Account.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String account; + @ECSearchable @ECForeignKey(entity=BubbleApp.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String app; public boolean hasApp () { return app != null; } + @ECSearchable @ECForeignKey(entity=AppMatcher.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String matcher; public boolean hasMatcher() { return matcher != null; } + @ECSearchable @ECForeignKey(entity=AppSite.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String site; public boolean hasSite() { return site != null; } + @ECSearchable(filter=true) @HasValue(message="err.key.required") @ECIndex @Column(nullable=false, updatable=false, length=5000) @Getter @Setter private String key; public boolean hasKey () { return key != null; } + @ECSearchable(filter=true) @Size(max=100000, message="err.data.length") @Type(type=ENCRYPTED_STRING) @Column(columnDefinition="varchar("+(100000+ENC_PAD)+")") @Getter @Setter private String data; @@ -84,12 +91,15 @@ public class AppData extends IdentifiableBase implements AppTemplateEntity { return setData(String.valueOf(val+1)); } + @ECSearchable(type=EntityFieldType.expiration_time) @ECIndex @Getter @Setter private Long expiration; + @ECSearchable @ECIndex @Column(nullable=false) @Getter @Setter private Boolean template = false; public boolean template() { return template != null && template; } + @ECSearchable @ECIndex @Column(nullable=false) @Getter @Setter private Boolean enabled = true; public boolean enabled() { return enabled != null && enabled; } diff --git a/bubble-server/src/main/java/bubble/model/app/AppMatcher.java b/bubble-server/src/main/java/bubble/model/app/AppMatcher.java index d424916f..97a79c63 100644 --- a/bubble-server/src/main/java/bubble/model/app/AppMatcher.java +++ b/bubble-server/src/main/java/bubble/model/app/AppMatcher.java @@ -40,26 +40,32 @@ public class AppMatcher extends IdentifiableBase implements AppTemplateEntity { public static final String[] VALUE_FIELDS = {"fqdn", "urlRegex", "rule", "template", "enabled"}; public static final String[] CREATE_FIELDS = ArrayUtil.append(VALUE_FIELDS, "name", "site"); + @ECSearchable @ECForeignKey(entity=Account.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String account; + @ECSearchable(filter=true) @ECIndex @Column(nullable=false, updatable=false, length=200) @Getter @Setter private String name; + @ECSearchable @ECForeignKey(entity=BubbleApp.class) @Column(nullable=false, length=UUID_MAXLEN) @Getter @Setter private String app; + @ECSearchable @ECForeignKey(entity=AppSite.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String site; + @ECSearchable(filter=true) @HasValue(message="err.fqdn.required") @Size(max=1024, message="err.fqdn.length") @ECIndex @Column(nullable=false, length=1024) @Getter @Setter private String fqdn; + @ECSearchable(filter=true) @HasValue(message="err.urlRegex.required") @Size(max=1024, message="err.urlRegex.length") @Type(type=ENCRYPTED_STRING) @Column(nullable=false, columnDefinition="varchar("+(1024+ENC_PAD)+") UNIQUE") @@ -69,18 +75,22 @@ public class AppMatcher extends IdentifiableBase implements AppTemplateEntity { public boolean matches (String value) { return getPattern().matcher(value).find(); } + @ECSearchable @ECForeignKey(entity=AppRule.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String rule; + @ECSearchable @Column(nullable=false) @Getter @Setter private Boolean blocked = false; public boolean blocked() { return blocked != null && blocked; } + @ECSearchable @ECIndex @Column(nullable=false) @Getter @Setter private Boolean template = false; public boolean template () { return template == null || template; } + @ECSearchable @ECIndex @Column(nullable=false) @Getter @Setter private Boolean enabled = true; public boolean enabled () { return enabled == null || enabled; } diff --git a/bubble-server/src/main/java/bubble/model/app/AppRule.java b/bubble-server/src/main/java/bubble/model/app/AppRule.java index b7841593..3eddcb81 100644 --- a/bubble-server/src/main/java/bubble/model/app/AppRule.java +++ b/bubble-server/src/main/java/bubble/model/app/AppRule.java @@ -46,10 +46,12 @@ public class AppRule extends IdentifiableBaseParentEntity implements AppTemplate public static final String[] VALUE_FIELDS = {"driver", "configJson", "template", "enabled"}; public static final String[] CREATE_FIELDS = ArrayUtil.append(VALUE_FIELDS, "name"); + @ECSearchable(filter=true) @HasValue(message="err.name.required") @ECIndex @Column(nullable=false, updatable=false, length=200) @Getter @Setter private String name; + @ECSearchable @ECForeignKey(entity=Account.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String account; @@ -58,21 +60,26 @@ public class AppRule extends IdentifiableBaseParentEntity implements AppTemplate return (account == null && accountUuid == null) || (account != null && account.equals(accountUuid)); } + @ECSearchable @ECForeignKey(entity=BubbleApp.class) @Column(nullable=false, length=UUID_MAXLEN) @Getter @Setter private String app; + @ECSearchable @ECIndex @Column(nullable=false) @Getter @Setter private Boolean template = false; public boolean template () { return template == null || template; } + @ECSearchable @ECIndex @Column(nullable=false) @Getter @Setter private Boolean enabled = true; public boolean enabled () { return enabled == null || enabled; } + @ECSearchable @Column(nullable=false) @Getter @Setter private Integer priority = 0; + @ECSearchable @ECForeignKey(entity=RuleDriver.class) @HasValue(message="err.driver.required") @Column(nullable=false, length=UUID_MAXLEN) @@ -84,6 +91,7 @@ public class AppRule extends IdentifiableBaseParentEntity implements AppTemplate return d; } + @ECSearchable(filter=true) @Size(max=500000, message="err.configJson.length") @Type(type=ENCRYPTED_STRING) @Column(columnDefinition="varchar("+(500000+ENC_PAD)+")") @JsonIgnore @Getter @Setter private String configJson; diff --git a/bubble-server/src/main/java/bubble/model/app/AppSite.java b/bubble-server/src/main/java/bubble/model/app/AppSite.java index 5cf26859..0eab9450 100644 --- a/bubble-server/src/main/java/bubble/model/app/AppSite.java +++ b/bubble-server/src/main/java/bubble/model/app/AppSite.java @@ -42,28 +42,35 @@ public class AppSite extends IdentifiableBase implements AppTemplateEntity { return this; } + @ECSearchable @ECForeignKey(entity=Account.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String account; + @ECSearchable(filter=true) @ECIndex @Column(nullable=false, updatable=false, length=1000) @Getter @Setter private String name; + @ECSearchable @ECForeignKey(entity=BubbleApp.class) @Column(nullable=false, length=UUID_MAXLEN) @Getter @Setter private String app; + @ECSearchable @ECIndex @Column(nullable=false) @Getter @Setter private Boolean template = false; public boolean template () { return template == null || template; } + @ECSearchable @ECIndex @Column(nullable=false) @Getter @Setter private Boolean enabled = true; public boolean enabled () { return enabled == null || enabled; } + @ECSearchable(filter=true) @Column(nullable=false, length=10000) @Getter @Setter private String description; + @ECSearchable(filter=true) @Column(nullable=false, length=1024) @Getter @Setter private String url; 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 e837f835..8f5d682f 100644 --- a/bubble-server/src/main/java/bubble/model/app/BubbleApp.java +++ b/bubble-server/src/main/java/bubble/model/app/BubbleApp.java @@ -41,31 +41,38 @@ public class BubbleApp extends IdentifiableBaseParentEntity implements AccountTe private static final String[] VALUE_FIELDS = {"url", "description", "template", "enabled"}; + @ECSearchable @ECForeignKey(entity=Account.class) @Column(length=UUID_MAXLEN, nullable=false, updatable=false) @Getter @Setter private String account; + @ECSearchable(filter=true) @HasValue(message="err.name.required") @ECIndex @Column(nullable=false, updatable=false, length=200) @Getter @Setter private String name; + @ECSearchable(filter=true) @HasValue(message="err.url.required") @Size(max=1024, message="err.url.length") @Type(type=ENCRYPTED_STRING) @Column(columnDefinition="varchar("+(1024+ENC_PAD)+") NOT NULL") @Getter @Setter private String url; + @ECSearchable(filter=true) @Size(max=10000, message="err.description.length") @Type(type=ENCRYPTED_STRING) @Column(columnDefinition="varchar("+(10000+ENC_PAD)+")") @Getter @Setter private String description; + @ECSearchable @ECIndex @Column(nullable=false) @Getter @Setter private Boolean template = false; public boolean template() { return template != null && template; } + @ECSearchable @ECIndex @Column(nullable=false) @Getter @Setter private Boolean enabled = true; public boolean enabled () { return enabled == null || enabled; } + @ECSearchable @ECIndex @Getter @Setter private Boolean needsUpdate = false; public BubbleApp(Account account, BubbleApp app) { diff --git a/bubble-server/src/main/java/bubble/model/app/RuleDriver.java b/bubble-server/src/main/java/bubble/model/app/RuleDriver.java index a8710d5b..dae185fc 100644 --- a/bubble-server/src/main/java/bubble/model/app/RuleDriver.java +++ b/bubble-server/src/main/java/bubble/model/app/RuleDriver.java @@ -50,28 +50,35 @@ public class RuleDriver extends IdentifiableBase implements AccountTemplate { return this; } + @ECSearchable @ECForeignKey(entity=Account.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String account; + @ECSearchable(filter=true) @HasValue(message="err.name.required") @ECIndex @Column(length=200, nullable=false, updatable=false) @Getter @Setter private String name; + @ECSearchable @ECIndex @Column(nullable=false) @Getter @Setter private Boolean template = false; public boolean template() { return template != null && template; } + @ECSearchable @ECIndex @Column(nullable=false) @Getter @Setter private Boolean enabled = true; public boolean enabled () { return enabled == null || enabled; } + @ECSearchable(filter=true) @Column(length=200) @Getter @Setter private String author; + @ECSearchable(filter=true) @Column(length=1024) @Getter @Setter private String url; + @ECSearchable @Column(nullable=false, updatable=false, length=1000) @Getter @Setter private String driverClass; @@ -84,6 +91,8 @@ public class RuleDriver extends IdentifiableBase implements AccountTemplate { @Transient @JsonIgnore @Getter(lazy=true) private final AppRuleDriver driver = instantiate(this.driverClass); @Embedded @Getter @Setter private SemanticVersion version; + + @ECSearchable @ECIndex @Getter @Setter private Boolean needsUpdate = false; @Transient @Getter @Setter private AppRuleDriverDescriptor descriptor; diff --git a/bubble-server/src/main/java/bubble/model/bill/AccountPayment.java b/bubble-server/src/main/java/bubble/model/bill/AccountPayment.java index a7e4d220..c6e89440 100644 --- a/bubble-server/src/main/java/bubble/model/bill/AccountPayment.java +++ b/bubble-server/src/main/java/bubble/model/bill/AccountPayment.java @@ -26,35 +26,44 @@ import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.*; @Entity @NoArgsConstructor @Accessors(chain=true) public class AccountPayment extends IdentifiableBase implements HasAccountNoName { + @ECSearchable @ECForeignKey(entity=Account.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String account; + @ECSearchable @ECForeignKey(entity=AccountPaymentMethod.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String paymentMethod; + @ECSearchable @ECForeignKey(entity=BubblePlan.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String plan; + @ECSearchable @ECForeignKey(entity=AccountPlan.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String accountPlan; + @ECSearchable @ECForeignKey(entity=Bill.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String bill; + @ECSearchable @Enumerated(EnumType.STRING) @Column(nullable=false, updatable=false, length=20) @Getter @Setter private AccountPaymentType type; + @ECSearchable @Enumerated(EnumType.STRING) @Column(nullable=false, length=20) @Getter @Setter private AccountPaymentStatus status; + @ECSearchable @Type(type=ENCRYPTED_STRING) @Column(updatable=false, columnDefinition="varchar("+(200+ENC_PAD)+")") @Getter @Setter private String violation; + @ECSearchable @Type(type=ENCRYPTED_STRING) @Column(updatable=false, columnDefinition="varchar("+(10000+ENC_PAD)+")") @JsonIgnore @Getter @Setter private String exception; @@ -68,12 +77,15 @@ public class AccountPayment extends IdentifiableBase implements HasAccountNoName return this; } + @ECSearchable @Type(type=ENCRYPTED_LONG) @Column(updatable=false, columnDefinition="varchar("+(ENC_LONG)+") NOT NULL") @Getter @Setter private Long amount = 0L; + @ECSearchable @ECIndex @Column(nullable=false, updatable=false, length=10) @Getter @Setter private String currency; + @ECSearchable(filter=true) @Type(type=ENCRYPTED_STRING) @Column(updatable=false, columnDefinition="varchar("+(100000+ENC_PAD)+") NOT NULL") @Getter @Setter private String info; diff --git a/bubble-server/src/main/java/bubble/model/bill/AccountPaymentMethod.java b/bubble-server/src/main/java/bubble/model/bill/AccountPaymentMethod.java index c9f2ae47..404576de 100644 --- a/bubble-server/src/main/java/bubble/model/bill/AccountPaymentMethod.java +++ b/bubble-server/src/main/java/bubble/model/bill/AccountPaymentMethod.java @@ -16,10 +16,7 @@ import lombok.extern.slf4j.Slf4j; import org.cobbzilla.wizard.filters.Scrubbable; import org.cobbzilla.wizard.filters.ScrubbableField; import org.cobbzilla.wizard.model.IdentifiableBase; -import org.cobbzilla.wizard.model.entityconfig.annotations.ECForeignKey; -import org.cobbzilla.wizard.model.entityconfig.annotations.ECIndex; -import org.cobbzilla.wizard.model.entityconfig.annotations.ECIndexes; -import org.cobbzilla.wizard.model.entityconfig.annotations.ECType; +import org.cobbzilla.wizard.model.entityconfig.annotations.*; import org.cobbzilla.wizard.validation.ValidationResult; import org.hibernate.annotations.Type; @@ -53,15 +50,18 @@ public class AccountPaymentMethod extends IdentifiableBase implements HasAccount @Override public void beforeCreate() { if (!hasUuid()) initUuid(); } + @ECSearchable @ECForeignKey(entity=Account.class) @Column(length=UUID_MAXLEN, nullable=false, updatable=false) @Getter @Setter private String account; + @ECSearchable @ECForeignKey(entity=CloudService.class) @Column(length=UUID_MAXLEN, nullable=false, updatable=false) @Getter @Setter private String cloud; public boolean hasCloud() { return cloud != null; } + @ECSearchable @Enumerated(EnumType.STRING) @Column(nullable=false, updatable=false, length=20) @Getter @Setter private PaymentMethodType paymentMethodType; public boolean hasPaymentMethodType() { return paymentMethodType != null; } @@ -71,9 +71,11 @@ public class AccountPaymentMethod extends IdentifiableBase implements HasAccount public boolean hasPaymentInfo () { return paymentInfo != null; } public static final String DEFAULT_MASKED_PAYMENT_INFO = "XXXX-".repeat(3)+"XXXX"; + @ECSearchable @Type(type=ENCRYPTED_STRING) @Column(updatable=false, columnDefinition="varchar("+(100+ENC_PAD)+") NOT NULL") @Getter @Setter private String maskedPaymentInfo = DEFAULT_MASKED_PAYMENT_INFO; + @ECSearchable @Column(nullable=false) @Getter @Setter private Boolean deleted = false; public boolean deleted() { return deleted != null && deleted; } diff --git a/bubble-server/src/main/java/bubble/model/bill/AccountPlan.java b/bubble-server/src/main/java/bubble/model/bill/AccountPlan.java index 1f1884c8..49de8b45 100644 --- a/bubble-server/src/main/java/bubble/model/bill/AccountPlan.java +++ b/bubble-server/src/main/java/bubble/model/bill/AccountPlan.java @@ -10,6 +10,7 @@ import lombok.NoArgsConstructor; import lombok.Setter; import lombok.experimental.Accessors; import org.cobbzilla.wizard.model.IdentifiableBase; +import org.cobbzilla.wizard.model.entityconfig.EntityFieldType; import org.cobbzilla.wizard.model.entityconfig.annotations.*; import javax.persistence.Column; @@ -43,51 +44,63 @@ public class AccountPlan extends IdentifiableBase implements HasAccount { @Override public void beforeCreate() { if (!hasUuid()) initUuid(); } // mirrors network name + @ECSearchable(filter=true) @Size(max=100, message="err.name.length") @Column(length=100, nullable=false) @Getter @Setter private String name; + @ECSearchable @ECForeignKey(entity=Account.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String account; + @ECSearchable @ECForeignKey(entity=BubblePlan.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String plan; + @ECSearchable @ECForeignKey(entity=AccountPaymentMethod.class) @Column(updatable=false, length=UUID_MAXLEN) @Getter @Setter private String paymentMethod; + @ECSearchable @ECForeignKey(entity=BubbleDomain.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String domain; + @ECSearchable @ECForeignKey(entity=BubbleNetwork.class, index=false) @ECIndex(unique=true) @Column(length=UUID_MAXLEN) @Getter @Setter private String network; + @ECSearchable @Column(nullable=false) @Getter @Setter private Boolean enabled = false; public boolean enabled() { return enabled != null && enabled; } public boolean disabled() { return !enabled(); } + @ECSearchable(type=EntityFieldType.epoch_time) @Column(nullable=false) @ECIndex @Getter @Setter private Long nextBill; + @ECSearchable @Column(nullable=false, length=20) @Getter @Setter private String nextBillDate; public AccountPlan setNextBillDate() { return setNextBillDate(BILL_START_END_FORMAT.print(getNextBill())); } + @ECSearchable @ECIndex @Getter @Setter private Long deleted; public boolean deleted() { return deleted != null; } public boolean notDeleted() { return !deleted(); } + @ECSearchable @Column(nullable=false) @ECIndex @Getter @Setter private Boolean closed = false; public boolean closed() { return closed != null && closed; } public boolean notClosed() { return !closed(); } + @ECSearchable @ECIndex(unique=true) @Column(length=UUID_MAXLEN) @Getter @Setter private String deletedNetwork; public boolean hasDeletedNetwork() { return deletedNetwork != null; } diff --git a/bubble-server/src/main/java/bubble/model/bill/Bill.java b/bubble-server/src/main/java/bubble/model/bill/Bill.java index e7b2867e..81b58d7b 100644 --- a/bubble-server/src/main/java/bubble/model/bill/Bill.java +++ b/bubble-server/src/main/java/bubble/model/bill/Bill.java @@ -24,48 +24,60 @@ import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.*; }) public class Bill extends IdentifiableBase implements HasAccountNoName { + @ECSearchable @ECForeignKey(entity=Account.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String account; + @ECSearchable @ECForeignKey(entity=BubblePlan.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String plan; + @ECSearchable @ECForeignKey(entity=AccountPlan.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String accountPlan; + @ECSearchable @ECIndex @Enumerated(EnumType.STRING) @Column(nullable=false, length=20) @Getter @Setter private BillStatus status = BillStatus.unpaid; public boolean paid() { return status == BillStatus.paid; } public boolean unpaid() { return !paid(); } + @ECSearchable @ECIndex @Enumerated(EnumType.STRING) @Column(nullable=false, updatable=false, length=20) @Getter @Setter private BillItemType type; + @ECSearchable @Column(nullable=false, updatable=false, length=20) @ECIndex @Getter @Setter private String periodLabel; + @ECSearchable @Column(nullable=false, updatable=false, length=20) @Getter @Setter private String periodStart; + @ECSearchable @Column(nullable=false, updatable=false, length=20) @Getter @Setter private String periodEnd; public int daysInPeriod () { return BillPeriod.daysInPeriod(periodStart, periodEnd); } + @ECSearchable @Type(type=ENCRYPTED_LONG) @Column(updatable=false, columnDefinition="varchar("+(ENC_LONG)+") NOT NULL") @Getter @Setter private Long quantity = 0L; + @ECSearchable @Type(type=ENCRYPTED_LONG) @Column(updatable=false, columnDefinition="varchar("+(ENC_LONG)+") NOT NULL") @Getter @Setter private Long price = 0L; + @ECSearchable @ECIndex @Column(nullable=false, updatable=false, length=10) @Getter @Setter private String currency; + @ECSearchable @Type(type=ENCRYPTED_LONG) @Column(columnDefinition="varchar("+(ENC_LONG)+")") @Getter @Setter private Long refundedAmount = 0L; public boolean hasRefundedAmount () { return refundedAmount != null && refundedAmount > 0L; } diff --git a/bubble-server/src/main/java/bubble/model/bill/BubblePlan.java b/bubble-server/src/main/java/bubble/model/bill/BubblePlan.java index eeb47875..111ee742 100644 --- a/bubble-server/src/main/java/bubble/model/bill/BubblePlan.java +++ b/bubble-server/src/main/java/bubble/model/bill/BubblePlan.java @@ -44,14 +44,17 @@ public class BubblePlan extends IdentifiableBase implements HasAccount { public BubblePlan (BubblePlan other) { copy(this, other, CREATE_FIELDS); } + @ECSearchable @ECForeignKey(entity=Account.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String account; + @ECSearchable(filter=true) @HasValue(message="err.name.required") @ECIndex @Column(nullable=false, updatable=false, length=200) @Getter @Setter private String name; + @ECSearchable(filter=true) @HasValue(message="err.chargeName.required") @Size(message="err.chargeName.length") @Column(nullable=false, updatable=false, length=12) @@ -68,37 +71,48 @@ public class BubblePlan extends IdentifiableBase implements HasAccount { } } + @ECSearchable @ECIndex @Column(nullable=false) @Getter @Setter private Boolean enabled = true; public boolean enabled () { return enabled == null || enabled; } + @ECSearchable @ECIndex @Column(nullable=false) @Getter @Setter private Long price; + @ECSearchable @ECIndex @Column(nullable=false, length=10) @Getter @Setter private String currency = "USD"; + @ECSearchable @Enumerated(EnumType.STRING) @Column(nullable=false, updatable=false, length=20) @Getter @Setter private BillPeriod period = BillPeriod.monthly; + @ECSearchable @ECIndex @Column(nullable=false, updatable=false, length=20) @Enumerated(EnumType.STRING) @Getter @Setter private ComputeNodeSizeType computeSizeType; + @ECSearchable @Column(nullable=false, updatable=false) @Getter @Setter private Integer nodesIncluded; + @ECSearchable @Column(nullable=false, updatable=false) @Getter @Setter private Integer additionalPerNodePrice; + @ECSearchable @Column(nullable=false, updatable=false) @Getter @Setter private Integer storageGbIncluded; + @ECSearchable @Column(nullable=false, updatable=false) @Getter @Setter private Integer additionalStoragePerGbPrice; + @ECSearchable @Column(nullable=false, updatable=false) @Getter @Setter private Integer bandwidthGbIncluded; + @ECSearchable @Column(nullable=false, updatable=false) @Getter @Setter private Integer additionalBandwidthPerGbPrice; diff --git a/bubble-server/src/main/java/bubble/model/cloud/AnsibleRole.java b/bubble-server/src/main/java/bubble/model/cloud/AnsibleRole.java index b4a7f2b4..73b05845 100644 --- a/bubble-server/src/main/java/bubble/model/cloud/AnsibleRole.java +++ b/bubble-server/src/main/java/bubble/model/cloud/AnsibleRole.java @@ -43,10 +43,12 @@ public class AnsibleRole extends IdentifiableBase implements AccountTemplate, Ha public AnsibleRole(AnsibleRole role) { copy(this, role, COPY_FIELDS); } + @ECSearchable @ECForeignKey(entity=Account.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String account; + @ECSearchable(filter=true) @HasValue(message="err.name.required") @Pattern(regexp=ROLENAME_PATTERN, message="err.name.invalid") @ECIndex @Column(nullable=false, updatable=false, length=200) @@ -74,9 +76,11 @@ public class AnsibleRole extends IdentifiableBase implements AccountTemplate, Ha .orElse(null); } + @ECSearchable(filter=true) @Size(max=10000, message="err.description.length") @Getter @Setter private String description; + @ECSearchable @Enumerated(EnumType.STRING) @ECIndex @Column(nullable=false, length=20) @Getter @Setter private AnsibleInstallType install = AnsibleInstallType.standard; @@ -84,13 +88,16 @@ public class AnsibleRole extends IdentifiableBase implements AccountTemplate, Ha return install.shouldInstall(installType); } + @ECSearchable @ECIndex @Column(nullable=false) @Getter @Setter private Integer priority; + @ECSearchable @ECIndex @Column(nullable=false) @Getter @Setter private Boolean template = false; public boolean template() { return template != null && template; } + @ECSearchable @ECIndex @Column(nullable=false) @Getter @Setter private Boolean enabled = true; public boolean enabled () { return enabled == null || enabled; } diff --git a/bubble-server/src/main/java/bubble/model/cloud/BubbleBackup.java b/bubble-server/src/main/java/bubble/model/cloud/BubbleBackup.java index 27d456ed..b4be0710 100644 --- a/bubble-server/src/main/java/bubble/model/cloud/BubbleBackup.java +++ b/bubble-server/src/main/java/bubble/model/cloud/BubbleBackup.java @@ -33,18 +33,22 @@ public class BubbleBackup extends IdentifiableBase implements HasAccount { if (getUuid() == null) initUuid(); } + @ECSearchable @ECForeignKey(entity=Account.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String account; + @ECSearchable @ECForeignKey(entity=BubbleNetwork.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String network; + @ECSearchable(filter=true) @Size(max=2000, message="err.path.length") @Type(type=ENCRYPTED_STRING) @Column(updatable=false, columnDefinition="varchar("+(2000+ENC_PAD)+") NOT NULL") @Getter @Setter private String path; + @ECSearchable(filter=true) @Pattern(regexp="[A-Za-z0-9][-A-Za-z0-9\\._]{2,}", message="err.label.invalid") @Size(max=300, message="err.label.length") @Type(type=ENCRYPTED_STRING) @Column(updatable=false, columnDefinition="varchar("+(300+ENC_PAD)+")") @@ -53,12 +57,14 @@ public class BubbleBackup extends IdentifiableBase implements HasAccount { @Override @JsonIgnore @Transient public String getName() { return hasLabel() ? getLabel() : getPath(); } + @ECSearchable @Enumerated(EnumType.STRING) @ECIndex @Column(nullable=false, length=40) @Getter @Setter private BackupStatus status; public boolean success () { return status == BackupStatus.backup_completed; } - @Column(length=ERROR_MAXLEN) + @ECSearchable(filter=true) + @Type(type=ENCRYPTED_STRING) @Column(length=ERROR_MAXLEN) @Getter @Setter private String error; public boolean hasError () { return !empty(error); } diff --git a/bubble-server/src/main/java/bubble/model/cloud/BubbleDomain.java b/bubble-server/src/main/java/bubble/model/cloud/BubbleDomain.java index e630a246..5d17640e 100644 --- a/bubble-server/src/main/java/bubble/model/cloud/BubbleDomain.java +++ b/bubble-server/src/main/java/bubble/model/cloud/BubbleDomain.java @@ -53,6 +53,7 @@ public class BubbleDomain extends IdentifiableBase implements AccountTemplate { @Override public Identifiable update(Identifiable other) { copy(this, other, UPDATE_FIELDS); return this; } + @ECSearchable @ECForeignKey(entity=Account.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String account; diff --git a/bubble-server/src/main/java/bubble/model/cloud/BubbleFootprint.java b/bubble-server/src/main/java/bubble/model/cloud/BubbleFootprint.java index 8b331e8d..08f563d8 100644 --- a/bubble-server/src/main/java/bubble/model/cloud/BubbleFootprint.java +++ b/bubble-server/src/main/java/bubble/model/cloud/BubbleFootprint.java @@ -59,6 +59,7 @@ public class BubbleFootprint extends IdentifiableBase implements AccountTemplate @Override public Identifiable update(Identifiable other) { copy(this, other, UPDATE_FIELDS); return this; } + @ECSearchable @ECForeignKey(entity=Account.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String account; diff --git a/bubble-server/src/main/java/bubble/model/cloud/BubbleNetwork.java b/bubble-server/src/main/java/bubble/model/cloud/BubbleNetwork.java index 6096b23d..73e0416e 100644 --- a/bubble-server/src/main/java/bubble/model/cloud/BubbleNetwork.java +++ b/bubble-server/src/main/java/bubble/model/cloud/BubbleNetwork.java @@ -33,7 +33,7 @@ import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENCRYPTED_STRING; import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENC_PAD; @ECType(root=true) -@ECTypeURIs(listFields={"name", "domain", "description", "account", "enabled"}) +@ECTypeURIs(baseURI=EP_NETWORKS, listFields={"name", "domain", "description", "account", "enabled"}) @ECTypeFields(list={"name", "domain", "description", "account", "enabled"}) @ECTypeChildren(uriPrefix=EP_NETWORKS+"/{BubbleNetwork.name}", value={ @ECTypeChild(type=BubbleNode.class, backref="network") @@ -57,6 +57,7 @@ public class BubbleNetwork extends IdentifiableBase implements HasNetwork, HasBu @Transient @JsonIgnore public String getNetwork () { return getUuid(); } + @ECSearchable @ECForeignKey(entity=Account.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String account; diff --git a/bubble-server/src/main/java/bubble/model/cloud/BubbleNode.java b/bubble-server/src/main/java/bubble/model/cloud/BubbleNode.java index 71e4d840..e3a6d148 100644 --- a/bubble-server/src/main/java/bubble/model/cloud/BubbleNode.java +++ b/bubble-server/src/main/java/bubble/model/cloud/BubbleNode.java @@ -38,6 +38,7 @@ import static org.cobbzilla.util.network.NetworkUtil.isLocalIpv4; import static org.cobbzilla.util.reflect.ReflectionUtil.copy; import static org.cobbzilla.util.string.ValidationRegexes.IP4_MAXLEN; import static org.cobbzilla.util.string.ValidationRegexes.IP6_MAXLEN; +import static org.cobbzilla.wizard.model.entityconfig.annotations.ECForeignKeySearchDepth.shallow; @ECType(root=true) @ECTypeURIs(baseURI=EP_NODES, listFields={"name", "ip4"}) @@ -86,11 +87,12 @@ public class BubbleNode extends IdentifiableBase implements HasNetwork, HasBubbl @JsonIgnore @Transient @Override public String getName() { return getFqdn(); } + @ECSearchable @ECForeignKey(entity=Account.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String account; - @ECSearchable + @ECSearchable(fkDepth=shallow) @ECForeignKey(entity=BubbleDomain.class) @HasValue(message="err.network.required") @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @@ -99,7 +101,7 @@ public class BubbleNode extends IdentifiableBase implements HasNetwork, HasBubbl return getDomain() != null && n.getDomain() != null && getDomain().equals(n.getDomain()); } - @ECSearchable + @ECSearchable(fkDepth=shallow) @ECForeignKey(entity=BubbleNetwork.class) @HasValue(message="err.network.required") @Column(nullable=false, updatable=false, length=UUID_MAXLEN) diff --git a/bubble-server/src/main/java/bubble/model/cloud/BubbleNodeKey.java b/bubble-server/src/main/java/bubble/model/cloud/BubbleNodeKey.java index 6618087d..acb8f909 100644 --- a/bubble-server/src/main/java/bubble/model/cloud/BubbleNodeKey.java +++ b/bubble-server/src/main/java/bubble/model/cloud/BubbleNodeKey.java @@ -1,7 +1,6 @@ package bubble.model.cloud; import bubble.model.account.Account; -import bubble.model.account.HasAccount; import bubble.model.account.HasAccountNoName; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Getter; @@ -12,8 +11,10 @@ import lombok.experimental.Accessors; import org.cobbzilla.util.security.RsaKeyPair; import org.cobbzilla.util.security.RsaMessage; import org.cobbzilla.wizard.model.IdentifiableBase; +import org.cobbzilla.wizard.model.entityconfig.EntityFieldType; import org.cobbzilla.wizard.model.entityconfig.annotations.ECForeignKey; import org.cobbzilla.wizard.model.entityconfig.annotations.ECIndex; +import org.cobbzilla.wizard.model.entityconfig.annotations.ECSearchable; import org.cobbzilla.wizard.model.entityconfig.annotations.ECType; import org.hibernate.annotations.Type; @@ -81,14 +82,17 @@ public class BubbleNodeKey extends IdentifiableBase implements HasAccountNoName return keys.stream().allMatch(k -> k.expiresInLessThan(TOKEN_GENERATION_LIMIT)); } + @ECSearchable @ECForeignKey(entity=Account.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String account; + @ECSearchable @ECForeignKey(entity=BubbleNode.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String node; + @ECSearchable(filter=true) @Column(length=10000, updatable=false, nullable=false) @Getter private String publicKey; public BubbleNodeKey setPublicKey (String k) { @@ -114,10 +118,12 @@ public class BubbleNodeKey extends IdentifiableBase implements HasAccountNoName @Type(type=ENCRYPTED_STRING) @ECIndex(unique=true) @Column(updatable=false, columnDefinition="varchar("+(100+ENC_PAD)+")") @Getter @Setter private String privateKeyHash; + @ECSearchable(filter=true) @Size(max=100, message="err.remoteHost.length") @Type(type=ENCRYPTED_STRING) @Column(updatable=false, columnDefinition="varchar("+(100+ENC_PAD)+") NOT NULL") @Getter @Setter private String remoteHost; + @ECSearchable(type=EntityFieldType.epoch_time) @ECIndex @Column(nullable=false, updatable=false) @Getter @Setter private Long expiration = defaultExpiration(); diff --git a/bubble-server/src/main/java/bubble/model/cloud/CloudService.java b/bubble-server/src/main/java/bubble/model/cloud/CloudService.java index 3f4a0f1b..08e00e7d 100644 --- a/bubble-server/src/main/java/bubble/model/cloud/CloudService.java +++ b/bubble-server/src/main/java/bubble/model/cloud/CloudService.java @@ -72,6 +72,7 @@ public class CloudService extends IdentifiableBaseParentEntity implements Accoun @Override public Identifiable update(Identifiable thing) { copy(this, thing, UPDATE_FIELDS); return this; } + @ECSearchable @ECForeignKey(entity=Account.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String account; diff --git a/bubble-server/src/main/java/bubble/model/cloud/notify/NotificationBase.java b/bubble-server/src/main/java/bubble/model/cloud/notify/NotificationBase.java index 49bd5f62..416dd245 100644 --- a/bubble-server/src/main/java/bubble/model/cloud/notify/NotificationBase.java +++ b/bubble-server/src/main/java/bubble/model/cloud/notify/NotificationBase.java @@ -13,6 +13,7 @@ import org.cobbzilla.util.daemon.ZillaRuntime; import org.cobbzilla.wizard.model.IdentifiableBase; import org.cobbzilla.wizard.model.entityconfig.annotations.ECForeignKey; import org.cobbzilla.wizard.model.entityconfig.annotations.ECIndex; +import org.cobbzilla.wizard.model.entityconfig.annotations.ECSearchable; import javax.persistence.*; @@ -23,31 +24,38 @@ import static org.cobbzilla.util.json.JsonUtil.json; public class NotificationBase extends IdentifiableBase implements HasAccountNoName { // synchronous requests may include this + @ECSearchable @Column(updatable=false, length=UUID_MAXLEN) @Getter @Setter private String notificationId; public boolean hasId () { return notificationId != null; } + @ECSearchable @ECForeignKey(entity=Account.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String account; + @ECSearchable(filter=true) @ECIndex @Column(nullable=false, updatable=false, length=50) @Enumerated(EnumType.STRING) @Getter @Setter private NotificationType type; @Getter @Setter private boolean resolveNodes = false; + @ECSearchable @ECForeignKey(entity=BubbleNode.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String fromNode; public boolean hasFromNode () { return fromNode != null; } + @ECSearchable @ECForeignKey(entity=BubbleNode.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String toNode; + @ECSearchable(filter=true) @Column(nullable=false, updatable=false, length=1024) @Getter @Setter private String uri; + @ECSearchable(filter=true) @Column(updatable=false, length=100_000) @JsonIgnore @Getter @Setter private String payloadJson; public boolean hasPayload () { return payloadJson != null; } @@ -66,6 +74,7 @@ public class NotificationBase extends IdentifiableBase implements HasAccountNoNa @Transient public NotificationReceipt getReceipt () { return receiptJson == null ? null : json(receiptJson, NotificationReceipt.class); } public T setReceipt (NotificationReceipt receipt) { return (T) setReceiptJson(receipt == null ? null : json(receiptJson)); } + @ECSearchable(filter=true) @Column(length=ERROR_MAXLEN) @JsonIgnore @Getter @Setter private String error; diff --git a/bubble-server/src/main/java/bubble/model/cloud/notify/ReceivedNotification.java b/bubble-server/src/main/java/bubble/model/cloud/notify/ReceivedNotification.java index 78c65795..52562763 100644 --- a/bubble-server/src/main/java/bubble/model/cloud/notify/ReceivedNotification.java +++ b/bubble-server/src/main/java/bubble/model/cloud/notify/ReceivedNotification.java @@ -4,10 +4,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.experimental.Accessors; -import org.cobbzilla.wizard.model.entityconfig.annotations.ECIndex; -import org.cobbzilla.wizard.model.entityconfig.annotations.ECType; -import org.cobbzilla.wizard.model.entityconfig.annotations.ECTypeFields; -import org.cobbzilla.wizard.model.entityconfig.annotations.ECTypeURIs; +import org.cobbzilla.wizard.model.entityconfig.annotations.*; import javax.persistence.Column; import javax.persistence.Entity; @@ -25,6 +22,7 @@ public class ReceivedNotification extends NotificationBase { public ReceivedNotification(SentNotification notification) { copy(this, notification); setUuid(null); } + @ECSearchable(filter=true) @ECIndex @Column(nullable=false, length=20) @Enumerated(EnumType.STRING) @Getter @Setter private NotificationProcessingStatus processingStatus = NotificationProcessingStatus.received; diff --git a/bubble-server/src/main/java/bubble/model/cloud/notify/SentNotification.java b/bubble-server/src/main/java/bubble/model/cloud/notify/SentNotification.java index 33295645..a328e679 100644 --- a/bubble-server/src/main/java/bubble/model/cloud/notify/SentNotification.java +++ b/bubble-server/src/main/java/bubble/model/cloud/notify/SentNotification.java @@ -4,10 +4,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.experimental.Accessors; -import org.cobbzilla.wizard.model.entityconfig.annotations.ECIndex; -import org.cobbzilla.wizard.model.entityconfig.annotations.ECType; -import org.cobbzilla.wizard.model.entityconfig.annotations.ECTypeFields; -import org.cobbzilla.wizard.model.entityconfig.annotations.ECTypeURIs; +import org.cobbzilla.wizard.model.entityconfig.annotations.*; import javax.persistence.Column; import javax.persistence.Entity; @@ -22,6 +19,7 @@ import static bubble.ApiConstants.EP_SENT_NOTIFICATIONS; @Entity @NoArgsConstructor @Accessors(chain=true) public class SentNotification extends NotificationBase { + @ECSearchable @ECIndex @Column(nullable=false, length=20) @Enumerated(EnumType.STRING) @Getter @Setter private NotificationSendStatus status = NotificationSendStatus.created; diff --git a/bubble-server/src/main/java/bubble/resources/EntityConfigsResource.java b/bubble-server/src/main/java/bubble/resources/EntityConfigsResource.java index 08eaf751..2c3e39be 100644 --- a/bubble-server/src/main/java/bubble/resources/EntityConfigsResource.java +++ b/bubble-server/src/main/java/bubble/resources/EntityConfigsResource.java @@ -35,7 +35,7 @@ public class EntityConfigsResource extends AbstractEntityConfigsResource { @Autowired private AccountDAO accountDAO; @Getter(AccessLevel.PROTECTED) @Autowired private BubbleConfiguration configuration; - private AtomicBoolean allowPublic = new AtomicBoolean(false); + @Getter private AtomicBoolean allowPublic = new AtomicBoolean(false); @POST @Path("/set/{param}") public Response setConfig (@Context ContainerRequest ctx, diff --git a/bubble-server/src/main/java/bubble/resources/IdentityResource.java b/bubble-server/src/main/java/bubble/resources/IdentityResource.java new file mode 100644 index 00000000..66bee6bf --- /dev/null +++ b/bubble-server/src/main/java/bubble/resources/IdentityResource.java @@ -0,0 +1,74 @@ +package bubble.resources; + +import bubble.dao.account.AccountDAO; +import bubble.dao.account.AccountOwnedEntityDAO; +import bubble.model.account.Account; +import bubble.server.BubbleConfiguration; +import lombok.extern.slf4j.Slf4j; +import org.cobbzilla.wizard.dao.DAO; +import org.cobbzilla.wizard.model.Identifiable; +import org.glassfish.grizzly.http.server.Request; +import org.glassfish.jersey.server.ContainerRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.ws.rs.*; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import java.util.HashMap; +import java.util.Map; + +import static bubble.ApiConstants.ID_ENDPOINT; +import static org.cobbzilla.util.http.HttpContentTypes.APPLICATION_JSON; +import static org.cobbzilla.wizard.resources.ResourceUtil.*; + +@Consumes(APPLICATION_JSON) +@Produces(APPLICATION_JSON) +@Path(ID_ENDPOINT) +@Service @Slf4j +public class IdentityResource { + + @Autowired private BubbleConfiguration configuration; + + @GET + public Response identifyNothing(@Context Request req, + @Context ContainerRequest ctx) { return ok_empty(); } + + @GET @Path("/{id}") + public Response identify(@Context Request req, + @Context ContainerRequest ctx, + @PathParam("id") String id) { + final Account caller = userPrincipal(ctx); + final Map entities = new HashMap<>(); + for (Class type : configuration.getEntityClasses()) { + final DAO dao = configuration.getDaoForEntityClass(type); + final Identifiable found; + if (dao instanceof AccountOwnedEntityDAO) { + // find things we own with the given id + found = ((AccountOwnedEntityDAO) dao).findByAccountAndId(caller.getUuid(), id); + + } else if (dao instanceof AccountDAO) { + if (caller.admin()) { + // only admin can find any user + found = ((AccountDAO) dao).findById(id); + } else if (id.equals(caller.getUuid()) || id.equals(caller.getName())) { + // other callers can find themselves + found = caller; + } else { + found = null; + } + + } else if (caller.admin()) { + // admins can find anything anywhere, regardless of who owns it + found = dao.findByUuid(id); + + } else { + // everything else is not found + found = null; + } + if (found != null) entities.put(type.getName(), found); + } + return ok(entities); + } + +} diff --git a/bubble-server/src/main/java/bubble/resources/SearchResource.java b/bubble-server/src/main/java/bubble/resources/SearchResource.java new file mode 100644 index 00000000..f361de5e --- /dev/null +++ b/bubble-server/src/main/java/bubble/resources/SearchResource.java @@ -0,0 +1,121 @@ +package bubble.resources; + +import bubble.model.account.Account; +import bubble.server.BubbleConfiguration; +import bubble.service.cloud.GeoService; +import lombok.extern.slf4j.Slf4j; +import org.cobbzilla.util.collection.ExpirationMap; +import org.cobbzilla.wizard.dao.AbstractDAO; +import org.cobbzilla.wizard.dao.DAO; +import org.cobbzilla.wizard.dao.SearchResults; +import org.cobbzilla.wizard.model.search.SearchQuery; +import org.cobbzilla.wizard.model.search.SqlViewField; +import org.glassfish.grizzly.http.server.Request; +import org.glassfish.jersey.server.ContainerRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.ws.rs.*; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import static bubble.ApiConstants.*; +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.cobbzilla.util.daemon.ZillaRuntime.hashOf; +import static org.cobbzilla.util.http.HttpContentTypes.APPLICATION_JSON; +import static org.cobbzilla.wizard.resources.ResourceUtil.*; + +@Consumes(APPLICATION_JSON) +@Produces(APPLICATION_JSON) +@Path(SEARCH_ENDPOINT) +@Service @Slf4j +public class SearchResource { + + @Autowired private BubbleConfiguration configuration; + @Autowired private GeoService geoService; + + public static final String Q_FILTER = "query"; + public static final String Q_META = "meta"; + public static final String Q_PAGE = "page"; + public static final String Q_SIZE = "size"; + + private Map daoCache = new ConcurrentHashMap<>(); + + @GET @Path("/{type}") + public Response search(@Context Request req, + @Context ContainerRequest ctx, + @PathParam("type") String type, + @QueryParam(Q_META) Boolean meta, + @QueryParam(Q_FILTER) String filter, + @QueryParam(Q_PAGE) Integer page, + @QueryParam(Q_SIZE) Integer size) { + return search(req, ctx, type, meta, filter, page, size, null); + } + + private Map _searchCache = new ExpirationMap<>(MINUTES.toMillis(10), MINUTES.toMillis(15)); + + @POST @Path("/{type}") + public Response search(@Context Request req, + @Context ContainerRequest ctx, + @PathParam("type") String type, + @QueryParam(Q_META) Boolean meta, + @QueryParam(Q_FILTER) String filter, + @QueryParam(Q_PAGE) Integer page, + @QueryParam(Q_SIZE) Integer size, + SearchQuery searchQuery) { + + final Account caller = userPrincipal(ctx); + final String cacheKey = hashOf(caller.getUuid(), type, meta, filter, page, size, searchQuery); + return ok(_searchCache.computeIfAbsent(cacheKey, searchKey -> { + final DAO dao = daoCache.computeIfAbsent(type, k -> getDao(type)); + if (meta != null) return ((AbstractDAO) dao).getSearchFields(); + + final SearchQuery q = searchQuery != null ? searchQuery : new SearchQuery(); + if (!q.hasLocale()) { + try { + q.setLocale(geoService.getFirstLocale(caller, getRemoteHost(req), normalizeLangHeader(req))); + } catch (Exception e) { + log.warn("search: error setting locale, using default: "+configuration.getDefaultLocale()+": "+e); + q.setLocale(configuration.getDefaultLocale()); + } + } + q.setPageNumber(page != null ? page : 1); + q.setPageSize(size != null ? Integer.min(size, MAX_SEARCH_PAGE) : Integer.min(q.getPageSize(), MAX_SEARCH_PAGE)); + if (filter != null) q.setFilter(filter); + + if (!caller.admin()) { + final Class entityClass = dao.getEntityClass(); + if (entityClass.equals(Account.class)) { + // non-admins can only look up themselves + q.setBound("uuid", caller.getUuid()); + } else { + final SqlViewField accountField = ((AbstractDAO) dao).findSearchField(entityClass, "account"); + if (accountField != null) { + q.setBound("account", caller.getUuid()); + } else { + // no results, non-admin cannot search for things that do not have an account + return new SearchResults<>().setError("cannot search "+ entityClass.getName()); + } + } + } + + final SearchResults results = dao.search(q); + if (results.hasNextPage(q)) { + results.setNextPage(req.getRequestURI()+"?"+Q_PAGE+"="+(q.getPageNumber()+1)+"&"+Q_SIZE+"="+q.getPageSize()); + } + return results; + })); + } + + public DAO getDao(String type) { + for (Class c : configuration.getEntityClasses()) { + if (c.getSimpleName().equalsIgnoreCase(type)) { + return configuration.getDaoForEntityClass(c); + } + } + throw notFoundEx(type); + } + +} diff --git a/bubble-server/src/main/java/bubble/resources/account/MeResource.java b/bubble-server/src/main/java/bubble/resources/account/MeResource.java index 90e4a0e6..24dbc388 100644 --- a/bubble-server/src/main/java/bubble/resources/account/MeResource.java +++ b/bubble-server/src/main/java/bubble/resources/account/MeResource.java @@ -2,7 +2,6 @@ package bubble.resources.account; import bubble.dao.SessionDAO; import bubble.dao.account.AccountDAO; -import bubble.dao.account.AccountOwnedEntityDAO; import bubble.dao.account.AccountPolicyDAO; import bubble.model.account.Account; import bubble.model.account.AccountPolicy; @@ -20,7 +19,6 @@ import bubble.resources.notify.SentNotificationsResource; import bubble.server.BubbleConfiguration; import bubble.service.account.StandardAccountMessageService; import bubble.service.account.download.AccountDownloadService; -import bubble.service.cloud.GeoService; import bubble.service.cloud.StandardNetworkService; import com.fasterxml.jackson.databind.JsonNode; import lombok.Cleanup; @@ -31,10 +29,7 @@ import org.cobbzilla.wizard.client.script.ApiRunner; import org.cobbzilla.wizard.client.script.ApiRunnerListener; import org.cobbzilla.wizard.client.script.ApiRunnerListenerStreamLogger; import org.cobbzilla.wizard.client.script.ApiScript; -import org.cobbzilla.wizard.dao.DAO; import org.cobbzilla.wizard.model.HashedPassword; -import org.cobbzilla.wizard.model.Identifiable; -import org.cobbzilla.wizard.model.search.SearchQuery; import org.glassfish.grizzly.http.server.Request; import org.glassfish.jersey.server.ContainerRequest; import org.springframework.beans.factory.annotation.Autowired; @@ -44,9 +39,6 @@ import javax.ws.rs.*; import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import java.io.StringWriter; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import static bubble.ApiConstants.*; import static org.cobbzilla.util.daemon.ZillaRuntime.errorString; @@ -266,72 +258,4 @@ public class MeResource { return ok(networkService.listLaunchStatuses(caller.getUuid())); } - @GET @Path(EP_ID) - public Response identifyNothing(@Context Request req, - @Context ContainerRequest ctx) { return ok_empty(); } - - @GET @Path(EP_ID+"/{id}") - public Response identify(@Context Request req, - @Context ContainerRequest ctx, - @PathParam("id") String id) { - final Account caller = userPrincipal(ctx); - final Map entities = new HashMap<>(); - for (Class type : configuration.getEntityClasses()) { - final DAO dao = configuration.getDaoForEntityClass(type); - final Identifiable found; - if (dao instanceof AccountOwnedEntityDAO) { - // find things we own with the given id - found = ((AccountOwnedEntityDAO) dao).findByAccountAndId(caller.getUuid(), id); - - } else if (dao instanceof AccountDAO) { - if (caller.admin()) { - // only admin can find any user - found = ((AccountDAO) dao).findById(id); - } else if (id.equals(caller.getUuid()) || id.equals(caller.getName())) { - // other callers can find themselves - found = caller; - } else { - found = null; - } - - } else if (caller.admin()) { - // admins can find anything anywhere, regardless of who owns it - found = dao.findByUuid(id); - - } else { - // everything else is not found - found = null; - } - if (found != null) entities.put(type.getName(), found); - } - return ok(entities); - } - - @Autowired private GeoService geoService; - - private Map daoCache = new ConcurrentHashMap<>(); - - @POST @Path(EP_SEARCH+"/{type}") - public Response search(@Context Request req, - @Context ContainerRequest ctx, - @PathParam("type") String type, - SearchQuery searchQuery) { - final Account caller = userPrincipal(ctx); - final DAO dao = daoCache.computeIfAbsent(type, k -> getDao(type)); - if (searchQuery == null) searchQuery = new SearchQuery(); - if (!searchQuery.hasLocale()) { - searchQuery.setLocale(geoService.getFirstLocale(caller, getRemoteHost(req), normalizeLangHeader(req))); - } - return ok(dao.search(searchQuery)); - } - - public DAO getDao(String type) { - for (Class c : configuration.getEntityClasses()) { - if (c.getSimpleName().equalsIgnoreCase(type)) { - return configuration.getDaoForEntityClass(c); - } - } - throw notFoundEx(type); - } - } diff --git a/bubble-server/src/test/java/bubble/test/dev/DevServerTest.java b/bubble-server/src/test/java/bubble/test/dev/DevServerTest.java index 7542132a..799dd417 100644 --- a/bubble-server/src/test/java/bubble/test/dev/DevServerTest.java +++ b/bubble-server/src/test/java/bubble/test/dev/DevServerTest.java @@ -1,7 +1,10 @@ package bubble.test.dev; +import bubble.resources.EntityConfigsResource; +import bubble.server.BubbleConfiguration; import bubble.test.ActivatedBubbleModelTestBase; import lombok.extern.slf4j.Slf4j; +import org.cobbzilla.wizard.server.RestServer; import org.junit.Test; import static java.util.concurrent.TimeUnit.DAYS; @@ -20,6 +23,11 @@ public class DevServerTest extends ActivatedBubbleModelTestBase { @Override protected boolean allowPreExistingDatabase() { return true; } @Override public boolean doTruncateDb() { return false; } + @Override public void onStart(RestServer server) { + getConfiguration().getBean(EntityConfigsResource.class).getAllowPublic().set(true); + super.onStart(server); + } + @Test public void runDevServer () throws Exception { log.info("runDevServer: Bubble API server started and model initialized. You may now begin testing."); sleep(DAYS.toMillis(30), "running dev server"); diff --git a/utils/cobbzilla-utils b/utils/cobbzilla-utils index 70899628..f804680f 160000 --- a/utils/cobbzilla-utils +++ b/utils/cobbzilla-utils @@ -1 +1 @@ -Subproject commit 708996280e324bb3e5c819743a477ea988312d86 +Subproject commit f804680fd96b25582d9a269cfcd82f173c7da4d4 diff --git a/utils/cobbzilla-wizard b/utils/cobbzilla-wizard index 65c0a83a..275e0f3f 160000 --- a/utils/cobbzilla-wizard +++ b/utils/cobbzilla-wizard @@ -1 +1 @@ -Subproject commit 65c0a83a885608d917c1d1a4d85dd8945d35f8d4 +Subproject commit 275e0f3fdd4f10f5cdb04416c0d8cbb2092c065e