@@ -141,6 +141,7 @@ public class ApiConstants { | |||||
public static final String EP_MODEL = "/model"; | public static final String EP_MODEL = "/model"; | ||||
public static final String EP_VPN = "/vpn"; | public static final String EP_VPN = "/vpn"; | ||||
public static final String EP_IPS = "/ips"; | public static final String EP_IPS = "/ips"; | ||||
public static final String EP_PLAN = "/plan"; | |||||
public static final String EP_PAYMENT_METHOD = "/paymentMethod"; | public static final String EP_PAYMENT_METHOD = "/paymentMethod"; | ||||
public static final String EP_PAYMENT_METHODS = PAYMENT_METHODS_ENDPOINT; | public static final String EP_PAYMENT_METHODS = PAYMENT_METHODS_ENDPOINT; | ||||
public static final String EP_PAYMENT = "/payment"; | public static final String EP_PAYMENT = "/payment"; | ||||
@@ -1,7 +1,10 @@ | |||||
package bubble.dao.bill; | package bubble.dao.bill; | ||||
import bubble.dao.account.AccountOwnedEntityDAO; | import bubble.dao.account.AccountOwnedEntityDAO; | ||||
import bubble.dao.app.BubbleAppDAO; | |||||
import bubble.model.app.BubbleApp; | |||||
import bubble.model.bill.BubblePlanApp; | import bubble.model.bill.BubblePlanApp; | ||||
import org.springframework.beans.factory.annotation.Autowired; | |||||
import org.springframework.stereotype.Repository; | import org.springframework.stereotype.Repository; | ||||
import java.util.List; | import java.util.List; | ||||
@@ -9,8 +12,18 @@ import java.util.List; | |||||
@Repository | @Repository | ||||
public class BubblePlanAppDAO extends AccountOwnedEntityDAO<BubblePlanApp> { | public class BubblePlanAppDAO extends AccountOwnedEntityDAO<BubblePlanApp> { | ||||
public List<BubblePlanApp> findByAccountAndPlan(String account, String bubblePlan) { | |||||
return findByFields("account", account, "plan", bubblePlan); | |||||
@Autowired private BubbleAppDAO appDAO; | |||||
public List<BubblePlanApp> findByPlan(String bubblePlan) { | |||||
return findByField("plan", bubblePlan); | |||||
} | |||||
public BubblePlanApp findByAccountAndPlanAndId(String account, String bubblePlan, String id) { | |||||
final BubblePlanApp planApp = findByUniqueFields("plan", bubblePlan, "app", id); | |||||
if (planApp != null) return planApp; | |||||
final BubbleApp app = appDAO.findByAccountAndId(account, id); | |||||
return app == null ? null : findByUniqueFields("plan", bubblePlan, "app", app.getUuid()); | |||||
} | } | ||||
} | } |
@@ -9,8 +9,8 @@ import lombok.NoArgsConstructor; | |||||
import lombok.Setter; | import lombok.Setter; | ||||
import lombok.experimental.Accessors; | import lombok.experimental.Accessors; | ||||
import org.cobbzilla.util.collection.HasPriority; | import org.cobbzilla.util.collection.HasPriority; | ||||
import org.cobbzilla.wizard.model.IdentifiableBase; | |||||
import org.cobbzilla.wizard.model.entityconfig.EntityFieldType; | import org.cobbzilla.wizard.model.entityconfig.EntityFieldType; | ||||
import org.cobbzilla.wizard.model.entityconfig.IdentifiableBaseParentEntity; | |||||
import org.cobbzilla.wizard.model.entityconfig.annotations.*; | import org.cobbzilla.wizard.model.entityconfig.annotations.*; | ||||
import org.cobbzilla.wizard.validation.HasValue; | import org.cobbzilla.wizard.validation.HasValue; | ||||
import org.joda.time.format.DateTimeFormat; | import org.joda.time.format.DateTimeFormat; | ||||
@@ -35,7 +35,7 @@ import static org.cobbzilla.util.reflect.ReflectionUtil.copy; | |||||
}) | }) | ||||
@Entity @NoArgsConstructor @Accessors(chain=true) | @Entity @NoArgsConstructor @Accessors(chain=true) | ||||
@ECIndexes({ @ECIndex(unique=true, of={"account", "name"}) }) | @ECIndexes({ @ECIndex(unique=true, of={"account", "name"}) }) | ||||
public class BubblePlan extends IdentifiableBase implements HasAccount, HasPriority { | |||||
public class BubblePlan extends IdentifiableBaseParentEntity implements HasAccount, HasPriority { | |||||
public static final int MAX_CHARGENAME_LEN = 12; | public static final int MAX_CHARGENAME_LEN = 12; | ||||
@@ -20,7 +20,9 @@ import static org.cobbzilla.util.reflect.ReflectionUtil.copy; | |||||
@ECType(root=true) | @ECType(root=true) | ||||
@ECTypeURIs(baseURI=EP_APPS, listFields={"plan", "app"}) | @ECTypeURIs(baseURI=EP_APPS, listFields={"plan", "app"}) | ||||
@Entity @NoArgsConstructor @Accessors(chain=true) | @Entity @NoArgsConstructor @Accessors(chain=true) | ||||
@ECIndexes({ @ECIndex(unique=true, of={"account", "plan", "app"}) }) | |||||
@ECIndexes({ | |||||
@ECIndex(unique=true, of={"plan", "app"}) | |||||
}) | |||||
public class BubblePlanApp extends IdentifiableBase implements HasAccountNoName { | public class BubblePlanApp extends IdentifiableBase implements HasAccountNoName { | ||||
public static final String[] CREATE_FIELDS = {"plan", "app"}; | public static final String[] CREATE_FIELDS = {"plan", "app"}; | ||||
@@ -256,4 +256,24 @@ public class AccountPlansResource extends AccountOwnedResource<AccountPlan, Acco | |||||
return paymentMethod == null ? notFound() : ok(paymentMethod); | return paymentMethod == null ? notFound() : ok(paymentMethod); | ||||
} | } | ||||
@GET @Path("/{id}"+EP_PLAN) | |||||
public Response getBubblePlan(@Context ContainerRequest ctx, | |||||
@PathParam("id") String id) { | |||||
final AccountPlan accountPlan = find(ctx, id); | |||||
if (accountPlan == null) return notFound(id); | |||||
final BubblePlan plan = planDAO.findByUuid(accountPlan.getPlan()); | |||||
return plan == null ? notFound() : ok(plan); | |||||
} | |||||
@Path("/{id}"+EP_APPS) | |||||
public BubblePlanAppsResource getApps(@Context ContainerRequest ctx, | |||||
@PathParam("id") String id) { | |||||
final AccountPlan accountPlan = find(ctx, id); | |||||
if (accountPlan == null) throw notFoundEx(id); | |||||
final Account account = accountDAO.findByUuid(getAccountUuid(ctx)); | |||||
final BubblePlan plan = planDAO.findByUuid(accountPlan.getPlan()); | |||||
return configuration.subResource(BubblePlanAppsResource.class, account, plan); | |||||
} | |||||
} | } |
@@ -8,9 +8,12 @@ import bubble.model.bill.BubblePlan; | |||||
import bubble.model.bill.BubblePlanApp; | import bubble.model.bill.BubblePlanApp; | ||||
import bubble.resources.account.AccountOwnedResource; | import bubble.resources.account.AccountOwnedResource; | ||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
import org.glassfish.grizzly.http.server.Request; | |||||
import org.glassfish.jersey.server.ContainerRequest; | import org.glassfish.jersey.server.ContainerRequest; | ||||
import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||
import java.util.List; | |||||
import static org.cobbzilla.wizard.resources.ResourceUtil.notFoundEx; | import static org.cobbzilla.wizard.resources.ResourceUtil.notFoundEx; | ||||
@Slf4j | @Slf4j | ||||
@@ -25,6 +28,26 @@ public class BubblePlanAppsResource extends AccountOwnedResource<BubblePlanApp, | |||||
@Autowired private BubbleAppDAO appDAO; | @Autowired private BubbleAppDAO appDAO; | ||||
@Override protected List<BubblePlanApp> list(ContainerRequest ctx) { | |||||
return getDao().findByPlan(plan.getUuid()); | |||||
} | |||||
@Override protected BubblePlanApp find(ContainerRequest ctx, String id) { | |||||
return getDao().findByAccountAndPlanAndId(account.getUuid(), plan.getUuid(), id); | |||||
} | |||||
@Override protected boolean canCreate(Request req, ContainerRequest ctx, Account caller, BubblePlanApp request) { | |||||
return caller.admin(); | |||||
} | |||||
@Override protected boolean canUpdate(ContainerRequest ctx, Account caller, BubblePlanApp found, BubblePlanApp request) { | |||||
return false; | |||||
} | |||||
@Override protected boolean canDelete(ContainerRequest ctx, Account caller, BubblePlanApp found) { | |||||
return caller.admin(); | |||||
} | |||||
@Override protected BubblePlanApp setReferences(ContainerRequest ctx, Account caller, BubblePlanApp request) { | @Override protected BubblePlanApp setReferences(ContainerRequest ctx, Account caller, BubblePlanApp request) { | ||||
final BubbleApp app = appDAO.findByAccountAndId(getAccountUuid(ctx), request.getApp()); | final BubbleApp app = appDAO.findByAccountAndId(getAccountUuid(ctx), request.getApp()); | ||||
if (app == null) throw notFoundEx(request.getApp()); | if (app == null) throw notFoundEx(request.getApp()); | ||||
@@ -16,8 +16,7 @@ import static bubble.ApiConstants.EP_APPS; | |||||
import static bubble.ApiConstants.PLANS_ENDPOINT; | import static bubble.ApiConstants.PLANS_ENDPOINT; | ||||
import static bubble.model.bill.BubblePlan.MAX_CHARGENAME_LEN; | import static bubble.model.bill.BubblePlan.MAX_CHARGENAME_LEN; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | ||||
import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx; | |||||
import static org.cobbzilla.wizard.resources.ResourceUtil.notFoundEx; | |||||
import static org.cobbzilla.wizard.resources.ResourceUtil.*; | |||||
@Path(PLANS_ENDPOINT) | @Path(PLANS_ENDPOINT) | ||||
@Service @Slf4j | @Service @Slf4j | ||||
@@ -36,7 +35,8 @@ public class BubblePlansResource extends AccountOwnedResource<BubblePlan, Bubble | |||||
@PathParam("id") String id) { | @PathParam("id") String id) { | ||||
final BubblePlan plan = find(ctx, id); | final BubblePlan plan = find(ctx, id); | ||||
if (plan == null) throw notFoundEx(id); | if (plan == null) throw notFoundEx(id); | ||||
return configuration.subResource(BubblePlanAppsResource.class, account, plan); | |||||
final Account caller = userPrincipal(ctx); | |||||
return configuration.subResource(BubblePlanAppsResource.class, caller, plan); | |||||
} | } | ||||
} | } |
@@ -90,7 +90,7 @@ public class AnsiblePrepService { | |||||
if (configuration.paymentsEnabled()) { | if (configuration.paymentsEnabled()) { | ||||
final AccountPlan accountPlan = accountPlanDAO.findByAccountAndNetwork(account.getUuid(), network.getUuid()); | final AccountPlan accountPlan = accountPlanDAO.findByAccountAndNetwork(account.getUuid(), network.getUuid()); | ||||
if (accountPlan == null) return die("prepAnsible: no AccountPlan found for network: "+network.getUuid()); | if (accountPlan == null) return die("prepAnsible: no AccountPlan found for network: "+network.getUuid()); | ||||
planApps = planAppDAO.findByAccountAndPlan(account.getUuid(), accountPlan.getPlan()); | |||||
planApps = planAppDAO.findByPlan(accountPlan.getPlan()); | |||||
} else { | } else { | ||||
planApps = null; | planApps = null; | ||||
} | } | ||||
@@ -14,4 +14,6 @@ public class PaymentTest extends PaymentTestBase { | |||||
modelTest("payment/pay_credit_refund_and_restart"); | modelTest("payment/pay_credit_refund_and_restart"); | ||||
} | } | ||||
@Test public void testAppsForPlan () throws Exception { modelTest("payment/plan_apps"); } | |||||
} | } |
@@ -2,11 +2,11 @@ | |||||
"system/cloudService", | "system/cloudService", | ||||
"system/cloudService_test", | "system/cloudService_test", | ||||
"system/bubbleDomain", | "system/bubbleDomain", | ||||
"system/bubblePlan", | |||||
"system/bubbleFootprint", | "system/bubbleFootprint", | ||||
"system/ruleDriver", | "system/ruleDriver", | ||||
"system/account_testDevice", | "system/account_testDevice", | ||||
"manifest-app-analytics", | "manifest-app-analytics", | ||||
"manifest-app-user-block-hn", | "manifest-app-user-block-hn", | ||||
"manifest-app-user-block-localhost" | |||||
"manifest-app-user-block-localhost", | |||||
"system/bubblePlan_withApps" | |||||
] | ] |
@@ -1,23 +1,26 @@ | |||||
[{ | |||||
"name": "bubble", | |||||
"chargeName": "BubbleVPNP", | |||||
"computeSizeType": "small", | |||||
"nodesIncluded": 1, | |||||
"additionalPerNodePrice": 1200, | |||||
"price": 1200, | |||||
"storageGbIncluded": 15, | |||||
"additionalStoragePerGbPrice": 2, | |||||
"bandwidthGbIncluded": 500, | |||||
"additionalBandwidthPerGbPrice": 2 | |||||
}, { | |||||
"name": "bubble_plus", | |||||
"chargeName": "BubblePlus", | |||||
"computeSizeType": "medium", | |||||
"nodesIncluded": 1, | |||||
"additionalPerNodePrice": 1900, | |||||
"price": 1900, | |||||
"storageGbIncluded": 40, | |||||
"additionalStoragePerGbPrice": 2, | |||||
"bandwidthGbIncluded": 1000, | |||||
"additionalBandwidthPerGbPrice": 2 | |||||
}] | |||||
[ | |||||
{ | |||||
"name": "bubble", | |||||
"chargeName": "BubbleVPNP", | |||||
"computeSizeType": "small", | |||||
"nodesIncluded": 1, | |||||
"additionalPerNodePrice": 1200, | |||||
"price": 1200, | |||||
"storageGbIncluded": 15, | |||||
"additionalStoragePerGbPrice": 2, | |||||
"bandwidthGbIncluded": 500, | |||||
"additionalBandwidthPerGbPrice": 2 | |||||
}, | |||||
{ | |||||
"name": "bubble_plus", | |||||
"chargeName": "BubblePlus", | |||||
"computeSizeType": "medium", | |||||
"nodesIncluded": 1, | |||||
"additionalPerNodePrice": 1900, | |||||
"price": 1900, | |||||
"storageGbIncluded": 40, | |||||
"additionalStoragePerGbPrice": 2, | |||||
"bandwidthGbIncluded": 1000, | |||||
"additionalBandwidthPerGbPrice": 2 | |||||
} | |||||
] |
@@ -0,0 +1,37 @@ | |||||
[ | |||||
{ | |||||
"name": "bubble", | |||||
"chargeName": "BubbleVPNP", | |||||
"computeSizeType": "small", | |||||
"nodesIncluded": 1, | |||||
"additionalPerNodePrice": 1200, | |||||
"price": 1200, | |||||
"storageGbIncluded": 15, | |||||
"additionalStoragePerGbPrice": 2, | |||||
"bandwidthGbIncluded": 500, | |||||
"additionalBandwidthPerGbPrice": 2, | |||||
"children": { | |||||
"BubblePlanApp": [ | |||||
{"app": "UserBlocker"} | |||||
] | |||||
} | |||||
}, | |||||
{ | |||||
"name": "bubble_plus", | |||||
"chargeName": "BubblePlus", | |||||
"computeSizeType": "medium", | |||||
"nodesIncluded": 1, | |||||
"additionalPerNodePrice": 1900, | |||||
"price": 1900, | |||||
"storageGbIncluded": 40, | |||||
"additionalStoragePerGbPrice": 2, | |||||
"bandwidthGbIncluded": 1000, | |||||
"additionalBandwidthPerGbPrice": 2, | |||||
"children": { | |||||
"BubblePlanApp": [ | |||||
{"app": "TrafficAnalytics"}, | |||||
{"app": "UserBlocker"} | |||||
] | |||||
} | |||||
} | |||||
] |
@@ -0,0 +1,104 @@ | |||||
[ | |||||
{ | |||||
"comment": "create a user account", | |||||
"request": { | |||||
"uri": "users", | |||||
"method": "put", | |||||
"entity": { | |||||
"name": "test_user_free", | |||||
"password": "password", | |||||
"contact": {"type": "email", "info": "test-user@example.com"} | |||||
} | |||||
} | |||||
}, | |||||
{ | |||||
"before": "sleep 22s", // wait for account objects to be created | |||||
"comment": "login as new user", | |||||
"request": { | |||||
"session": "new", | |||||
"uri": "auth/login", | |||||
"entity": { | |||||
"name": "test_user_free", | |||||
"password": "password" | |||||
} | |||||
}, | |||||
"response": { | |||||
"store": "testAccount", | |||||
"sessionName": "userSession", | |||||
"session": "token" | |||||
} | |||||
}, | |||||
{ | |||||
"comment": "get plans", | |||||
"request": { "uri": "plans" }, | |||||
"response": { | |||||
"store": "plans", | |||||
"check": [{"condition": "json.length >= 1"}] | |||||
} | |||||
}, | |||||
{ | |||||
"comment": "add basic plan, using 'free' payment method", | |||||
"request": { | |||||
"uri": "me/plans", | |||||
"method": "put", | |||||
"entity": { | |||||
"name": "test-net-{{rand 5}}", | |||||
"domain": "{{defaultDomain}}", | |||||
"locale": "en_US", | |||||
"timezone": "EST", | |||||
"plan": "{{plans.[0].name}}", | |||||
"footprint": "US", | |||||
"paymentMethodObject": { | |||||
"paymentMethodType": "free", | |||||
"paymentInfo": "free" | |||||
} | |||||
} | |||||
}, | |||||
"response": { | |||||
"store": "accountPlan" | |||||
} | |||||
}, | |||||
{ | |||||
"comment": "get plan apps, should be 1", | |||||
"request": { "uri": "me/plans/{{accountPlan.uuid}}/apps" }, | |||||
"response": { | |||||
"check": [{"condition": "json.length == 1"}] | |||||
} | |||||
}, | |||||
{ | |||||
"comment": "add plus plan, using 'free' payment method", | |||||
"request": { | |||||
"uri": "me/plans", | |||||
"method": "put", | |||||
"entity": { | |||||
"name": "test-net-{{rand 5}}", | |||||
"domain": "{{defaultDomain}}", | |||||
"locale": "en_US", | |||||
"timezone": "EST", | |||||
"plan": "{{plans.[1].name}}", | |||||
"footprint": "US", | |||||
"paymentMethodObject": { | |||||
"paymentMethodType": "free", | |||||
"paymentInfo": "free" | |||||
} | |||||
} | |||||
}, | |||||
"response": { | |||||
"store": "accountPlan2" | |||||
} | |||||
}, | |||||
{ | |||||
"comment": "get plan apps, should be 2", | |||||
"request": { "uri": "me/plans/{{accountPlan2.uuid}}/apps" }, | |||||
"response": { | |||||
"check": [{"condition": "json.length == 2"}] | |||||
} | |||||
} | |||||
] |