Skip to content

Commit

Permalink
Merge pull request #7 from tjschutte/main
Browse files Browse the repository at this point in the history
Add examples of modification command for credit card product.
  • Loading branch information
tjschutte authored Oct 18, 2024
2 parents f7a86d2 + 735ae1f commit ee5d5f6
Show file tree
Hide file tree
Showing 24 changed files with 675 additions and 66 deletions.
43 changes: 42 additions & 1 deletion application/frontend/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ const endpoints = {
"refresh-token": domains.security + "/session/refresh-token",
"sign-in": domains.security + "/session/sign-in",
"sign-out": domains.security + "/session/sign-out",
"list-credit-card-products": domains.card + "/product/list-items"
"list-credit-card-products": domains.card + "/product/list-items",
"activate-product": domains.card + "/product/activate",
"deactivate-product": domains.card + "/product/deactivate"
}

// Accept JSON bodies
Expand Down Expand Up @@ -103,6 +105,7 @@ app.post('/sign-in', unauthenticated, routeSignIn);
app.post('/sign-up', unauthenticated, routeSignUp);
app.get('/verification-emails', routeVerificationEmails);
app.get('/card/products', authenticated, routeCardProducts);
app.post('/card/products', authenticated, cardToggle);

async function userDetails(token) {
const response = await fetch(endpoints["user-details"], {
Expand Down Expand Up @@ -385,6 +388,44 @@ async function routeCardProducts(req, res) {
});
}

async function cardToggle(req, res) {
const productId = req.body.productId;
const active = req.body.active;

if (!productId) {
return res.status(400).json({ error: 'Product ID is required' });
}

try {
// Make the appropriate fetch request based on the product's current status
const endpoint = active === "true"
? endpoints["deactivate-product"] + "/" + productId
: endpoints["activate-product"] + "/" + productId;

const response = await fetch(endpoint, {
method: "POST",
body: '{}',
headers: {
'Content-Type': 'application/json',
}
});

// Bit of a hack, the request -> event -> projection will take some time.
// Realistically you would update the interface locally, and refresh state async
await sleep(2000);

// After successfully toggling the product status, render the updated product list
return await routeCardProducts(req, res);
} catch (error) {
console.error('Error in cardToggle:', error);
return await routeCardProducts(req, res);
}
}

function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

app.get("*", authenticated, render("404", { title: "Not Found" }))

app.listen(port, () => {
Expand Down
83 changes: 51 additions & 32 deletions application/frontend/src/views/card/products.handlebars
Original file line number Diff line number Diff line change
Expand Up @@ -22,36 +22,55 @@



<ul class="max-w-md divide-y divide-gray-200">
{{#each locals.products}}
<li class="p-3 hover:bg-slate-100 focus:bg-slate-100 active:bg-slate-100">
<div class="flex items-center space-x-4 rtl:space-x-reverse">
<div class="flex-shrink-0">
<div class="w-8 h-8 rounded-full" style="background-color: grey; opacity: .3"></div>
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-gray-900 truncate">
{{this.name}}
</p>
<p class="text-sm text-gray-500 truncate">
{{#if this.isActive}}
Active
{{else}}
Inactive
{{/if}}
</p>
</div>
<div class="text-base font-semibold text-gray-900 "style="
width: 10em;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
display: inline-block;
">
{{this.id}}
</div>
</div>
</li>
{{/each}}
<ul class="max-w-4xl divide-y divide-gray-200">
{{#each locals.products}}
<li class="p-3 hover:bg-slate-100 focus:bg-slate-100 active:bg-slate-100"
style="background-color: {{#if this.isActive}}{{this.backgroundColorHex}}{{else}}#f0f0f0{{/if}};">
<div class="flex items-center space-x-4 rtl:space-x-reverse">
<div class="flex-shrink-0">
<div class="w-8 h-8 rounded-full"
style="background-color: {{#if this.isActive}}{{this.hexColor}}{{else}}grey{{/if}}; opacity: .3">
</div>
</div>
<div class="flex-1 min-w-0">
{{#if this.isActive}}
<p class="text-sm font-medium text-gray-900 truncate">
{{this.name}}
</p>
<p class="text-sm text-gray-700 truncate">
Active
</p>
<p class="text-sm font-medium text-gray-700">
Reward Program: {{this.reward}}
</p>
<p class="text-sm font-medium text-gray-700">
Annual Fee: ${{this.annualFee}}.00
</p>
{{else}}
<p class="text-sm font-medium text-gray-400 truncate">
{{this.name}}
</p>
<p class="text-sm text-gray-400 truncate">
Inactive
</p>
{{/if}}
</div>
<div class="text-base font-semibold text-gray-900"
style="width: 10em; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; display: inline-block;">
{{this.id}}
</div>
<div class="flex items-center">
<!-- Activate/Deactivate Button -->
<form method="POST" action="/card/products">
<input type="hidden" name="productId" value="{{this.id}}">
<input type="hidden" name="active" value="{{this.isActive}}">
<button type="submit"
class="px-4 py-2 text-sm font-medium text-white {{#if this.isActive}}bg-red-500 hover:bg-red-600{{else}}bg-green-500 hover:bg-green-600{{/if}} rounded">
{{#if this.isActive}}Deactivate{{else}}Activate{{/if}}
</button>
</form>
</div>
</div>
</li>
{{/each}}
</ul>

Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,19 @@ productId=""
curl -X POST "${endpoint}/api/v1/credit_card_product/product/activate/${productId}"

# Deactivate a product
curl -X POST "${endpoint}/api/v1/credit_card_product/product/deactivate/${productId}"
curl -X POST "${endpoint}/api/v1/credit_card_product/product/deactivate/${productId}"

# To modify a card product
curl -X PATCH "${endpoint}/api/v1/credit_card_product/product" \
-H "Content-Type: application/json" \
-d '{
"id": "806ea870-56aa-4289-ac8f-76861b27a702",
"annualFeeInCents": 50000,
"creditLimitInCents": 500000,
"paymentCycle": "monthly",
"cardBackgroundHex": "#E5E4E2"
}'

# For connecting to the postgres container (To See the events)
# It will prompt for the password: my_es_password
psql -h 172.30.0.102 -p 5432 -U my_es_username -d my_es_database
52 changes: 51 additions & 1 deletion application/java-credit-card-product-service/src/commands.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,48 @@ curl -X POST "${endpoint}/api/v1/credit_card_product/product" \
"cardBackgroundHex": "#7fffd4"
}'

curl -X POST "${endpoint}/api/v1/credit_card_product/product" \
-H "Content-Type: application/json" \
-d '{
"productIdentifierForAggregateIdHash": "BASIC_CREDIT_CARD",
"name": "Basic",
"interestInBasisPoints": 1500,
"annualFeeInCents": 7500,
"paymentCycle": "monthly",
"creditLimitInCents": 500000,
"maxBalanceTransferAllowedInCents": 100000,
"reward": "none",
"cardBackgroundHex": "#34eb37"
}'

curl -X POST "${endpoint}/api/v1/credit_card_product/product" \
-H "Content-Type: application/json" \
-d '{
"productIdentifierForAggregateIdHash": "BASIC_CASH_BACK_CREDIT_CARD",
"name": "Cash Back - Basic",
"interestInBasisPoints": 2000,
"annualFeeInCents": 8500,
"paymentCycle": "monthly",
"creditLimitInCents": 500000,
"maxBalanceTransferAllowedInCents": 100000,
"reward": "CASHBACK",
"cardBackgroundHex": "#e396ff"
}'

curl -X POST "${endpoint}/api/v1/credit_card_product/product" \
-H "Content-Type: application/json" \
-d '{
"productIdentifierForAggregateIdHash": "BASIC_POINTS_CREDIT_CARD",
"name": "Travel Points - Basic",
"interestInBasisPoints": 2000,
"annualFeeInCents": 8500,
"paymentCycle": "monthly",
"creditLimitInCents": 500000,
"maxBalanceTransferAllowedInCents": 100000,
"reward": "POINTS",
"cardBackgroundHex": "#3a34eb"
}'

# To create the Platinum card
curl -X POST "${endpoint}/api/v1/credit_card_product/product" \
-H "Content-Type: application/json" \
Expand All @@ -38,4 +80,12 @@ productId=""
curl -X POST "${endpoint}/api/v1/credit_card_product/product/activate/${productId}"

# Deactivate a product
curl -X POST "${endpoint}/api/v1/credit_card_product/product/deactivate/${productId}"
curl -X POST "${endpoint}/api/v1/credit_card_product/product/deactivate/${productId}"

# To modify a card product
curl -X PATCH "${endpoint}/api/v1/credit_card_product/product" \
-H "Content-Type: application/json" \
-d '{
"id": "806ea870-56aa-4289-ac8f-76861b27a702",
"annualFeeInCents": 2500
}'
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

import cloud.ambar.creditCardProduct.events.Event;
import cloud.ambar.creditCardProduct.events.ProductActivatedEventData;
import cloud.ambar.creditCardProduct.events.ProductAnnualFeeChangedEventData;
import cloud.ambar.creditCardProduct.events.ProductBackgroundChangedEventData;
import cloud.ambar.creditCardProduct.events.ProductCreditLimitChangedEventData;
import cloud.ambar.creditCardProduct.events.ProductDeactivatedEventData;
import cloud.ambar.creditCardProduct.events.ProductDefinedEventData;
import cloud.ambar.creditCardProduct.events.ProductPaymentCycleChangedEventData;
import cloud.ambar.creditCardProduct.exceptions.InvalidEventException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
Expand Down Expand Up @@ -34,10 +38,9 @@ public CreditCardProductAggregate(String aggregateId) {

@Override
public void transform(Event event) {
ObjectMapper om = new ObjectMapper();
switch(event.getEventName()) {
case ProductDefinedEventData.EVENT_NAME -> {
log.info("Transforming aggregate for ProductDefinedEvent");
ObjectMapper om = new ObjectMapper();
try {
ProductDefinedEventData definition = om.readValue(event.getData(), ProductDefinedEventData.class);
this.setAggregateId(event.getAggregateId());
Expand All @@ -57,13 +60,43 @@ public void transform(Event event) {
}
}
case ProductActivatedEventData.EVENT_NAME -> {
log.info("Transforming aggregate for ProductActivatedEvent");
this.active = true;
}
case ProductDeactivatedEventData.EVENT_NAME -> {
log.info("Transforming aggregate for ProductDeactivatedEvent");
this.active = false;
}
case ProductAnnualFeeChangedEventData.EVENT_NAME -> {
try {
ProductAnnualFeeChangedEventData modification = om.readValue(event.getData(), ProductAnnualFeeChangedEventData.class);
this.setAnnualFeeInCents(modification.getAnnualFeeInCents());
} catch (JsonProcessingException e) {
throw new RuntimeException(e.getMessage());
}
}
case ProductPaymentCycleChangedEventData.EVENT_NAME -> {
try {
ProductPaymentCycleChangedEventData modification = om.readValue(event.getData(), ProductPaymentCycleChangedEventData.class);
this.setPaymentCycle(modification.getPaymentCycle());
} catch (JsonProcessingException e) {
throw new RuntimeException(e.getMessage());
}
}
case ProductCreditLimitChangedEventData.EVENT_NAME -> {
try {
ProductCreditLimitChangedEventData modification = om.readValue(event.getData(), ProductCreditLimitChangedEventData.class);
this.setCreditLimitInCents(modification.getCreditLimitInCents());
} catch (JsonProcessingException e) {
throw new RuntimeException(e.getMessage());
}
}
case ProductBackgroundChangedEventData.EVENT_NAME -> {
try {
ProductBackgroundChangedEventData modification = om.readValue(event.getData(), ProductBackgroundChangedEventData.class);
this.setCardBackgroundHex(modification.getCardBackgroundHex());
} catch (JsonProcessingException e) {
throw new RuntimeException(e.getMessage());
}
}
}
}
}
Loading

0 comments on commit ee5d5f6

Please sign in to comment.