@@ -1,4 +1,4 @@ | |||||
# Copyright (C) 2019 Bubble, Inc. | |||||
# Copyright (C) 2020 Bubble, Inc. | |||||
## For ANY commercial use of this software | ## For ANY commercial use of this software | ||||
All rights are reserved. Please contact licensing@bubblev.com for more information. | All rights are reserved. Please contact licensing@bubblev.com for more information. | ||||
@@ -12,6 +12,7 @@ import bubble.dao.device.DeviceDAO; | |||||
import bubble.model.account.*; | import bubble.model.account.*; | ||||
import bubble.model.app.*; | import bubble.model.app.*; | ||||
import bubble.model.bill.Bill; | import bubble.model.bill.Bill; | ||||
import bubble.model.bill.BubblePlan; | |||||
import bubble.model.cloud.*; | import bubble.model.cloud.*; | ||||
import bubble.server.BubbleConfiguration; | import bubble.server.BubbleConfiguration; | ||||
import bubble.service.SearchService; | import bubble.service.SearchService; | ||||
@@ -83,6 +84,15 @@ public class AccountDAO extends AbstractCRUDDAO<Account> implements SqlViewSearc | |||||
@Override public Object preCreate(Account account) { | @Override public Object preCreate(Account account) { | ||||
if (!account.hasLocale()) account.setLocale(getDEFAULT_LOCALE()); | if (!account.hasLocale()) account.setLocale(getDEFAULT_LOCALE()); | ||||
// check account limit for plan, if there is a plan | |||||
final BubblePlan plan = selfNodeService.getThisPlan(); | |||||
if (plan != null && plan.hasMaxAccounts()) { | |||||
final int numAccounts = countNotDeleted(); | |||||
if (numAccounts > plan.getMaxAccounts()) { | |||||
throw invalidEx("err.name.planMaxAccountLimit", "Account limit for plan reached (max "+plan.getMaxAccounts()+" accounts)"); | |||||
} | |||||
} | |||||
final ValidationResult result = account.validateName(); | final ValidationResult result = account.validateName(); | ||||
if (result.isInvalid()) throw invalidEx(result); | if (result.isInvalid()) throw invalidEx(result); | ||||
@@ -94,6 +104,8 @@ public class AccountDAO extends AbstractCRUDDAO<Account> implements SqlViewSearc | |||||
return super.preCreate(account); | return super.preCreate(account); | ||||
} | } | ||||
public int countNotDeleted() { return findByField("deleted", null).size(); } | |||||
private void ensureThisNodeExists() { | private void ensureThisNodeExists() { | ||||
if (activated()) { | if (activated()) { | ||||
BubbleNode thisNode = selfNodeService.getThisNode(); | BubbleNode thisNode = selfNodeService.getThisNode(); | ||||
@@ -46,6 +46,8 @@ public class AccountPlanDAO extends AccountOwnedEntityDAO<AccountPlan> { | |||||
return findByUniqueFields("account", accountUuid, "network", networkUuid); | return findByUniqueFields("account", accountUuid, "network", networkUuid); | ||||
} | } | ||||
public AccountPlan findByNetwork(String networkUuid) { return findByUniqueField("network", networkUuid); } | |||||
public List<AccountPlan> findByAccountAndNotDeleted(String account) { | public List<AccountPlan> findByAccountAndNotDeleted(String account) { | ||||
return findByFields("account", account, "deleted", null); | return findByFields("account", account, "deleted", null); | ||||
} | } | ||||
@@ -91,6 +91,7 @@ public class Account extends IdentifiableBaseParentEntity implements TokenPrinci | |||||
public static Account sageMask(Account sage) { | public static Account sageMask(Account sage) { | ||||
final Account masked = new Account(sage) | final Account masked = new Account(sage) | ||||
.setAdmin(false) | .setAdmin(false) | ||||
.setDeleted(now()) | |||||
.setHashedPassword(HashedPassword.DISABLED); | .setHashedPassword(HashedPassword.DISABLED); | ||||
masked.setUuid(sage.getUuid()); | masked.setUuid(sage.getUuid()); | ||||
return masked; | return masked; | ||||
@@ -40,12 +40,13 @@ public class BubblePlan extends IdentifiableBaseParentEntity implements HasAccou | |||||
public static final int MAX_CHARGENAME_LEN = 12; | public static final int MAX_CHARGENAME_LEN = 12; | ||||
public static final String[] UPDATE_FIELDS = { | public static final String[] UPDATE_FIELDS = { | ||||
"enabled", "price", "additionalPerNodePrice", "additionalStoragePerGbPrice", "additionalBandwidthPerGbPrice" | |||||
"enabled", "chargeName", "priority", "price", "maxAccounts", | |||||
"nodesIncluded", "storageGbIncluded", "bandwidthGbIncluded", | |||||
"additionalPerNodePrice", "additionalStoragePerGbPrice", "additionalBandwidthPerGbPrice" | |||||
}; | }; | ||||
public static final String[] CREATE_FIELDS = ArrayUtil.append(UPDATE_FIELDS, | public static final String[] CREATE_FIELDS = ArrayUtil.append(UPDATE_FIELDS, | ||||
"name", "chargeName", "priority", "period", "computeSizeType", | |||||
"nodesIncluded", "storageGbIncluded", "bandwidthGbIncluded" | |||||
"name", "period", "computeSizeType" | |||||
); | ); | ||||
public BubblePlan (BubblePlan other) { copy(this, other, CREATE_FIELDS); } | public BubblePlan (BubblePlan other) { copy(this, other, CREATE_FIELDS); } | ||||
@@ -104,27 +105,32 @@ public class BubblePlan extends IdentifiableBaseParentEntity implements HasAccou | |||||
@Enumerated(EnumType.STRING) @Getter @Setter private ComputeNodeSizeType computeSizeType; | @Enumerated(EnumType.STRING) @Getter @Setter private ComputeNodeSizeType computeSizeType; | ||||
@ECSearchable @ECField(index=100) | @ECSearchable @ECField(index=100) | ||||
@Column(nullable=false, updatable=false) | |||||
@Getter @Setter private Integer nodesIncluded; | |||||
@Getter @Setter private Integer maxAccounts; | |||||
public boolean hasMaxAccounts () { return maxAccounts != null; } | |||||
public int maxAccounts () { return hasMaxAccounts() ? getMaxAccounts() : Integer.MAX_VALUE; } | |||||
@ECSearchable @ECField(index=110) | @ECSearchable @ECField(index=110) | ||||
@Column(nullable=false, updatable=false) | |||||
@Getter @Setter private Integer additionalPerNodePrice; | |||||
@Column(nullable=false) | |||||
@Getter @Setter private Integer nodesIncluded; | |||||
@ECSearchable @ECField(index=120) | @ECSearchable @ECField(index=120) | ||||
@Column(nullable=false, updatable=false) | |||||
@Getter @Setter private Integer storageGbIncluded; | |||||
@Column(nullable=false) | |||||
@Getter @Setter private Integer additionalPerNodePrice; | |||||
@ECSearchable @ECField(index=130) | @ECSearchable @ECField(index=130) | ||||
@Column(nullable=false, updatable=false) | |||||
@Getter @Setter private Integer additionalStoragePerGbPrice; | |||||
@Column(nullable=false) | |||||
@Getter @Setter private Integer storageGbIncluded; | |||||
@ECSearchable @ECField(index=140) | @ECSearchable @ECField(index=140) | ||||
@Column(nullable=false, updatable=false) | |||||
@Getter @Setter private Integer bandwidthGbIncluded; | |||||
@Column(nullable=false) | |||||
@Getter @Setter private Integer additionalStoragePerGbPrice; | |||||
@ECSearchable @ECField(index=150) | @ECSearchable @ECField(index=150) | ||||
@Column(nullable=false, updatable=false) | |||||
@Column(nullable=false) | |||||
@Getter @Setter private Integer bandwidthGbIncluded; | |||||
@ECSearchable @ECField(index=160) | |||||
@Column(nullable=false) | |||||
@Getter @Setter private Integer additionalBandwidthPerGbPrice; | @Getter @Setter private Integer additionalBandwidthPerGbPrice; | ||||
@Transient @Getter @Setter private transient List<BubbleApp> apps; | @Transient @Getter @Setter private transient List<BubbleApp> apps; | ||||
@@ -1,5 +1,6 @@ | |||||
package bubble.service.boot; | package bubble.service.boot; | ||||
import bubble.model.bill.BubblePlan; | |||||
import bubble.model.cloud.BubbleNetwork; | import bubble.model.cloud.BubbleNetwork; | ||||
import bubble.model.cloud.BubbleNode; | import bubble.model.cloud.BubbleNode; | ||||
@@ -16,4 +17,6 @@ public interface SelfNodeService { | |||||
BubbleNode getSoleNode(); | BubbleNode getSoleNode(); | ||||
void setActivated(BubbleNode thisNode); | void setActivated(BubbleNode thisNode); | ||||
BubblePlan getThisPlan(); | |||||
} | } |
@@ -3,14 +3,15 @@ package bubble.service.boot; | |||||
import bubble.cloud.CloudServiceType; | import bubble.cloud.CloudServiceType; | ||||
import bubble.cloud.storage.local.LocalStorageDriver; | import bubble.cloud.storage.local.LocalStorageDriver; | ||||
import bubble.dao.account.AccountSshKeyDAO; | import bubble.dao.account.AccountSshKeyDAO; | ||||
import bubble.dao.bill.AccountPlanDAO; | |||||
import bubble.dao.bill.BubblePlanDAO; | |||||
import bubble.dao.cloud.BubbleNetworkDAO; | import bubble.dao.cloud.BubbleNetworkDAO; | ||||
import bubble.dao.cloud.BubbleNodeDAO; | import bubble.dao.cloud.BubbleNodeDAO; | ||||
import bubble.dao.cloud.BubbleNodeKeyDAO; | import bubble.dao.cloud.BubbleNodeKeyDAO; | ||||
import bubble.dao.cloud.CloudServiceDAO; | import bubble.dao.cloud.CloudServiceDAO; | ||||
import bubble.model.cloud.BubbleNetwork; | |||||
import bubble.model.cloud.BubbleNode; | |||||
import bubble.model.cloud.BubbleNodeKey; | |||||
import bubble.model.cloud.BubbleNodeState; | |||||
import bubble.model.bill.AccountPlan; | |||||
import bubble.model.bill.BubblePlan; | |||||
import bubble.model.cloud.*; | |||||
import bubble.model.cloud.notify.NotificationReceipt; | import bubble.model.cloud.notify.NotificationReceipt; | ||||
import bubble.model.cloud.notify.NotificationType; | import bubble.model.cloud.notify.NotificationType; | ||||
import bubble.server.BubbleConfiguration; | import bubble.server.BubbleConfiguration; | ||||
@@ -60,6 +61,8 @@ public class StandardSelfNodeService implements SelfNodeService { | |||||
@Autowired private BubbleNodeKeyDAO nodeKeyDAO; | @Autowired private BubbleNodeKeyDAO nodeKeyDAO; | ||||
@Autowired private BubbleNetworkDAO networkDAO; | @Autowired private BubbleNetworkDAO networkDAO; | ||||
@Autowired private CloudServiceDAO cloudDAO; | @Autowired private CloudServiceDAO cloudDAO; | ||||
@Autowired private AccountPlanDAO accountPlanDAO; | |||||
@Autowired private BubblePlanDAO planDAO; | |||||
@Autowired private NotificationService notificationService; | @Autowired private NotificationService notificationService; | ||||
@Autowired private BubbleConfiguration configuration; | @Autowired private BubbleConfiguration configuration; | ||||
@@ -383,4 +386,14 @@ public class StandardSelfNodeService implements SelfNodeService { | |||||
return existingByUuid; | return existingByUuid; | ||||
} | } | ||||
} | } | ||||
@Override public BubblePlan getThisPlan() { | |||||
final BubbleNetwork network = safeGetThisNetwork(); | |||||
if (network == null) return null; | |||||
if (network.getInstallType() != AnsibleInstallType.node) return null; | |||||
final AccountPlan accountPlan = accountPlanDAO.findByNetwork(network.getUuid()); | |||||
if (accountPlan == null) return null; | |||||
return planDAO.findByUuid(accountPlan.getPlan()); | |||||
} | |||||
} | } |
@@ -1,5 +1,6 @@ | |||||
package bubble.service_dbfilter; | package bubble.service_dbfilter; | ||||
import bubble.model.bill.BubblePlan; | |||||
import bubble.model.cloud.BubbleNetwork; | import bubble.model.cloud.BubbleNetwork; | ||||
import bubble.model.cloud.BubbleNode; | import bubble.model.cloud.BubbleNode; | ||||
import bubble.service.boot.SelfNodeService; | import bubble.service.boot.SelfNodeService; | ||||
@@ -22,4 +23,6 @@ public class DbFilterSelfNodeService implements SelfNodeService { | |||||
@Override public void setActivated(BubbleNode thisNode) { notSupported("setActivated"); } | @Override public void setActivated(BubbleNode thisNode) { notSupported("setActivated"); } | ||||
@Override public BubblePlan getThisPlan() { return notSupported("getThisPlan"); } | |||||
} | } |
@@ -151,6 +151,7 @@ err.app.notFound=App not found | |||||
err.name.required=Name is required | err.name.required=Name is required | ||||
err.name.tooShort=Name must be at least 4 characters | err.name.tooShort=Name must be at least 4 characters | ||||
err.name.tooLong=Name cannot be longer than 100 characters | err.name.tooLong=Name cannot be longer than 100 characters | ||||
err.name.planMaxAccountLimit=No more accounts can be created on this plan. Upgrade your plan or delete some accounts. | |||||
err.password.required=Password is required | err.password.required=Password is required | ||||
err.password.tooShort=Password must be at least 8 characters | err.password.tooShort=Password must be at least 8 characters | ||||
err.password.invalid=Password must contain at least one letter, one number, and one special character | err.password.invalid=Password must contain at least one letter, one number, and one special character | ||||
@@ -13,6 +13,7 @@ | |||||
"additionalBandwidthPerGbPrice": 0, | "additionalBandwidthPerGbPrice": 0, | ||||
"children": { | "children": { | ||||
"BubblePlanApp": [ | "BubblePlanApp": [ | ||||
{"app": "TrafficAnalytics"}, | |||||
{"app": "BubbleBlock"}, | {"app": "BubbleBlock"}, | ||||
{"app": "UserBlocker"} | {"app": "UserBlocker"} | ||||
] | ] | ||||