Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
78 changes: 59 additions & 19 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ name: CI

on:
push:
branches: [main]
branches: [main, develop]
pull_request:
branches: [main]
branches: [main, develop]

jobs:
build:
name: Build & Package
java:
name: Java — Build & Test
runs-on: ubuntu-latest

steps:
Expand All @@ -21,28 +21,68 @@ jobs:
distribution: 'temurin'
cache: maven

- name: Build all modules
run: mvn -f shopsphere/pom.xml clean package -DskipTests --no-transfer-progress
- name: Build and test all modules
run: mvn -f shopsphere/pom.xml clean verify --no-transfer-progress

dotnet:
name: .NET — Build & Test (shipping-service)
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Set up .NET 8
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'

- name: Restore
run: dotnet restore shopsphere/shipping-service/tests/ShippingService.Tests/ShippingService.Tests.csproj

- name: Build
run: dotnet build shopsphere/shipping-service/tests/ShippingService.Tests/ShippingService.Tests.csproj -c Release --no-restore

- name: Test
run: dotnet test shopsphere/shipping-service/tests/ShippingService.Tests/ShippingService.Tests.csproj -c Release --no-build --logger "trx;LogFileName=results.trx"

- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: shipping-service-test-results
path: shopsphere/shipping-service/tests/ShippingService.Tests/TestResults/

docker:
name: Docker Build (${{ matrix.service }})
runs-on: ubuntu-latest
needs: build
needs: [java, dotnet]

strategy:
fail-fast: false
matrix:
service:
- service-registry
- config-service
- api-gateway
- user-service
- product-service
- cart-service
- order-service
- inventory-service
- payment-service
- notification-service
include:
- service: service-registry
context: ./shopsphere
- service: config-service
context: ./shopsphere
- service: api-gateway
context: ./shopsphere
- service: user-service
context: ./shopsphere
- service: product-service
context: ./shopsphere
- service: cart-service
context: ./shopsphere
- service: order-service
context: ./shopsphere
- service: inventory-service
context: ./shopsphere
- service: payment-service
context: ./shopsphere
- service: notification-service
context: ./shopsphere
- service: shipping-service
context: ./shopsphere/shipping-service

steps:
- uses: actions/checkout@v4
Expand All @@ -53,7 +93,7 @@ jobs:
- name: Build Docker image
uses: docker/build-push-action@v5
with:
context: ./shopsphere
context: ${{ matrix.context }}
file: ./shopsphere/${{ matrix.service }}/Dockerfile
push: false
build-args: SERVICE_DIR=${{ matrix.service }}
Expand Down
8 changes: 8 additions & 0 deletions shopsphere/api-gateway/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ spring:
predicates:
- Path=/api/v1/notifications/**

# ---------- Shipping Service (.NET 8 + Steeltoe) ----------
- id: shipping-service
uri: ${SHIPPING_SERVICE_URL:http://localhost:8088}
predicates:
- Path=/api/v1/shipments/**

app:
jwt:
secret: ${JWT_SECRET:change-me-to-a-long-random-secret-of-at-least-256-bits-for-hs256}
Expand All @@ -74,6 +80,7 @@ app:
- /api/v1/inventory/health
- /api/v1/payments/health
- /api/v1/notifications/health
- /api/v1/shipments/health
- /actuator/health
# GET on these path prefixes is public (browsing catalog without login)
public-get-prefixes:
Expand All @@ -84,6 +91,7 @@ app:
- /api/v1/products
- /api/v1/categories
- /api/v1/inventory
- /api/v1/shipments

management:
endpoints:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.shopsphere.common.events;

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;

import java.math.BigDecimal;
import java.time.Instant;
import java.util.List;

/**
* US38 — published by order-service after an order is cancelled (committed).
* Carries enough for notification now, and for refunds/stock compensation later.
*/
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@SuperBuilder
@JsonInclude(JsonInclude.Include.NON_NULL)
public class OrderCancelledEvent extends BaseEvent {

public static final String TYPE = "order.cancelled.v1";

private Long orderId;
private Long userId;

/** Machine-readable reason code, e.g. RESERVATION_EXPIRED. */
private String reason;

/** Customer-facing explanation. */
private String reasonDescription;

private Instant cancelledAt;
private BigDecimal totalAmount;
private List<OrderItemSnapshot> items;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
import java.math.BigDecimal;
import java.util.List;

/**
* Published by order-service after an order is committed.
*
* <p>US33 added {@code shippingAddress}. The change is additive, so the type stays at v1:
* consumers built against the old class ignore the unknown field.</p>
*/
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
Expand All @@ -23,6 +29,7 @@ public class OrderCreatedEvent extends BaseEvent {
private BigDecimal totalAmount;
private Integer itemCount;
private List<Item> items;
private ShippingAddress shippingAddress;

@Data
@NoArgsConstructor
Expand All @@ -35,4 +42,22 @@ public static class Item {
private BigDecimal unitPrice;
private Integer quantity;
}

/** Snapshot of the shipping address at the time the order was placed. */
@Data
@NoArgsConstructor
@lombok.AllArgsConstructor
@lombok.Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class ShippingAddress {
private String recipientName;
private String phone;
private String line1;
private String line2;
private String city;
private String state;
private String postalCode;
/** ISO 3166-1 alpha-2, upper case (e.g. "CA", "US"). */
private String country;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,10 @@ public class PaymentSuccessfulEvent extends BaseEvent {

/** Items so inventory can deduct without a callback. */
private List<OrderItemSnapshot> items;

/**
* US35 — carried forward from order.created so shipping-service can create the
* shipment without calling order-service. Additive: type stays v1.
*/
private OrderCreatedEvent.ShippingAddress shippingAddress;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.shopsphere.common.events;

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;

import java.time.Instant;
import java.util.List;

/**
* US37 — published by inventory-service when an order's stock holds time out before
* payment settled. One event per order (keyed by orderId), listing every released line.
*/
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@SuperBuilder
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ReservationExpiredEvent extends BaseEvent {

public static final String TYPE = "inventory.reservation-expired.v1";

private Long orderId;
private Instant expiredAt;

/** productId + quantity released (unitPrice not set). */
private List<OrderItemSnapshot> items;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.shopsphere.common.events;

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;

import java.time.Instant;

/**
* Published by the .NET shipping-service (US36) when a shipment is marked DELIVERED.
* eventId is deterministic per shipment so re-publishes can be de-duplicated.
*/
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@SuperBuilder
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ShipmentDeliveredEvent extends BaseEvent {

public static final String TYPE = "shipment.delivered.v1";

private Long shipmentId;
private Long orderId;
private Long userId;
private String carrier;
private String trackingNumber;
private Instant deliveredAt;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.shopsphere.common.events;

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;

import java.time.Instant;

/**
* Published by the .NET shipping-service (US35) once a shipment has been created and
* handed to the carrier. Declared here so Java services can consume it with the same
* JsonDeserializer setup as every other event.
*
* <p>eventId is deterministic per shipment, so a re-publish after a failure carries the
* same id and consumers can de-duplicate.</p>
*/
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@SuperBuilder
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ShipmentDispatchedEvent extends BaseEvent {

public static final String TYPE = "shipment.dispatched.v1";

private Long shipmentId;
private Long orderId;
private Long userId;
private String carrier;
private String trackingNumber;
private Instant shippedAt;
private OrderCreatedEvent.ShippingAddress shippingAddress;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@
/**
* Centralized topic names. Every producer and consumer references constants here
* — never hard-code topic names in services. Keeps schema/topic evolution sane.
* The .NET shipping-service mirrors these in ShippingService.Api.Messaging.Topics.
*/
public final class Topics {

private Topics() {}

public static final String ORDER_CREATED = "order.created";
public static final String PAYMENT_SUCCESSFUL = "payment.successful";
public static final String PAYMENT_FAILED = "payment.failed";
public static final String LOW_STOCK = "inventory.low-stock";
public static final String ORDER_CREATED = "order.created";
public static final String ORDER_CANCELLED = "order.cancelled";
public static final String PAYMENT_SUCCESSFUL = "payment.successful";
public static final String PAYMENT_FAILED = "payment.failed";
public static final String LOW_STOCK = "inventory.low-stock";
public static final String RESERVATION_EXPIRED = "inventory.reservation-expired";
public static final String SHIPMENT_DISPATCHED = "shipment.dispatched";
public static final String SHIPMENT_DELIVERED = "shipment.delivered";
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ app:
- /api/v1/inventory/health
- /api/v1/payments/health
- /api/v1/notifications/health
- /api/v1/shipments/health
- /actuator/health
public-get-prefixes:
- /api/v1/products
Expand All @@ -18,3 +19,4 @@ app:
- /api/v1/products
- /api/v1/categories
- /api/v1/inventory
- /api/v1/shipments
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Served to shipping-service (.NET 8 / Steeltoe Config Server client).
# Keys map to IConfiguration (Shipping.DefaultCarrier -> Shipping:DefaultCarrier).
Logging:
LogLevel:
ShippingService: Debug

Shipping:
DefaultCarrier: ShopSphere Express
TrackingPrefix: SSX
Loading
Loading