Skip to content

Commit

Permalink
add subscription rest apis
Browse files Browse the repository at this point in the history
  • Loading branch information
gabrmsouza committed Aug 29, 2024
1 parent c21fea7 commit 08b5698
Show file tree
Hide file tree
Showing 9 changed files with 406 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.fasterxml.jackson.databind.ObjectMapper;
import io.github.gabrmsouza.subscription.infrastructure.configuration.annontations.Keycloak;
import io.github.gabrmsouza.subscription.infrastructure.configuration.annontations.KeycloakAdmin;
import io.github.gabrmsouza.subscription.infrastructure.configuration.properties.RestClientProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
Expand Down Expand Up @@ -29,6 +30,19 @@ RestClient keycloakHttpClient(@Keycloak final RestClientProperties properties, f
return restClient(properties, mapper);
}

@Bean
@KeycloakAdmin
@ConfigurationProperties(prefix = "rest-client.keycloak-admin")
public RestClientProperties keycloakAdminRestClientProperties() {
return new RestClientProperties();
}

@Bean
@KeycloakAdmin
public RestClient keycloakAdminHttpClient(@KeycloakAdmin final RestClientProperties properties, final ObjectMapper objectMapper) {
return restClient(properties, objectMapper);
}

private RestClient restClient(final RestClientProperties properties, final ObjectMapper mapper) {
final var factory = new JdkClientHttpRequestFactory();
factory.setReadTimeout(properties.readTimeout());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package io.github.gabrmsouza.subscription.infrastructure.rest;

import io.github.gabrmsouza.subscription.infrastructure.authentication.principal.CodeflixUser;
import io.github.gabrmsouza.subscription.infrastructure.rest.models.request.ChargeSubscriptionRequest;
import io.github.gabrmsouza.subscription.infrastructure.rest.models.request.CreateSubscriptionRequest;
import io.github.gabrmsouza.subscription.infrastructure.rest.models.response.CancelSubscriptionResponse;
import io.github.gabrmsouza.subscription.infrastructure.rest.models.response.ChargeSubscriptionResponse;
import io.github.gabrmsouza.subscription.infrastructure.rest.models.response.CreateSubscriptionResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

@RequestMapping(value = "subscriptions")
@Tag(name = "Subscription")
public interface SubscriptionRestApi {

@PostMapping(
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE
)
@Operation(summary = "Create a new subscription")
@ApiResponses(value = {
@ApiResponse(responseCode = "201", description = "Created successfully"),
@ApiResponse(responseCode = "422", description = "A validation error was observed"),
@ApiResponse(responseCode = "500", description = "An unpredictable error was observed"),
})
ResponseEntity<CreateSubscriptionResponse> createSubscription(
@RequestBody @Valid CreateSubscriptionRequest req,
@AuthenticationPrincipal final CodeflixUser principal
);

@PutMapping(
value = "active/cancel",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE
)
@Operation(summary = "Cancel an active subscription")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Canceled successfully"),
@ApiResponse(responseCode = "422", description = "A validation error was observed"),
@ApiResponse(responseCode = "500", description = "An unpredictable error was observed"),
})
ResponseEntity<CancelSubscriptionResponse> cancelSubscription(@AuthenticationPrincipal final CodeflixUser principal);

@PutMapping(
value = "active/charge",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE
)
@Operation(summary = "Charge an active subscription")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Charged successfully"),
@ApiResponse(responseCode = "422", description = "A validation error was observed"),
@ApiResponse(responseCode = "500", description = "An unpredictable error was observed"),
})
ResponseEntity<ChargeSubscriptionResponse> chargeActiveSubscription(
@RequestBody @Valid ChargeSubscriptionRequest req,
@AuthenticationPrincipal final CodeflixUser principal
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package io.github.gabrmsouza.subscription.infrastructure.rest.controllers;

import io.github.gabrmsouza.subscription.application.subscription.CancelSubscription;
import io.github.gabrmsouza.subscription.application.subscription.ChargeSubscription;
import io.github.gabrmsouza.subscription.application.subscription.CreateSubscription;
import io.github.gabrmsouza.subscription.infrastructure.authentication.principal.CodeflixUser;
import io.github.gabrmsouza.subscription.infrastructure.rest.SubscriptionRestApi;
import io.github.gabrmsouza.subscription.infrastructure.rest.models.request.ChargeSubscriptionRequest;
import io.github.gabrmsouza.subscription.infrastructure.rest.models.request.CreateSubscriptionRequest;
import io.github.gabrmsouza.subscription.infrastructure.rest.models.response.CancelSubscriptionResponse;
import io.github.gabrmsouza.subscription.infrastructure.rest.models.response.ChargeSubscriptionResponse;
import io.github.gabrmsouza.subscription.infrastructure.rest.models.response.CreateSubscriptionResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;

import java.net.URI;
import java.util.Objects;

@RestController
public class SubscriptionRestController implements SubscriptionRestApi {
private final CreateSubscription createSubscription;
private final CancelSubscription cancelSubscription;
private final ChargeSubscription chargeSubscription;

public SubscriptionRestController(
final CreateSubscription createSubscription,
final CancelSubscription cancelSubscription,
final ChargeSubscription chargeSubscription
) {
this.createSubscription = Objects.requireNonNull(createSubscription);
this.cancelSubscription = Objects.requireNonNull(cancelSubscription);
this.chargeSubscription = Objects.requireNonNull(chargeSubscription);
}

@Override
public ResponseEntity<CreateSubscriptionResponse> createSubscription(final CreateSubscriptionRequest req, final CodeflixUser principal) {
record CreateSubscriptionInput(Long planId, String accountId) implements CreateSubscription.Input {}
final var res = this.createSubscription.execute(new CreateSubscriptionInput(req.planId(), principal.accountId()), CreateSubscriptionResponse::new);
return ResponseEntity.created(URI.create("/subscriptions/" + res.subscriptionId())).body(res);
}

@Override
public ResponseEntity<CancelSubscriptionResponse> cancelSubscription(final CodeflixUser principal) {
record CancelSubscriptionInput(String accountId) implements CancelSubscription.Input {}
final var res = this.cancelSubscription.execute(new CancelSubscriptionInput(principal.accountId()), CancelSubscriptionResponse::new);
return ResponseEntity.ok(res);
}

@Override
public ResponseEntity<ChargeSubscriptionResponse> chargeActiveSubscription(final ChargeSubscriptionRequest req, final CodeflixUser principal) {
record ChargeSubscriptionInput(String accountId, String paymentType, String creditCardToken)
implements ChargeSubscription.Input {}

final var res = this.chargeSubscription.execute(new ChargeSubscriptionInput(principal.accountId(), req.paymentType(), req.creditCardToken()), ChargeSubscriptionResponse::new);
return ResponseEntity.ok(res);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.github.gabrmsouza.subscription.infrastructure.rest.models.request;

import jakarta.validation.constraints.NotBlank;

public record ChargeSubscriptionRequest(
@NotBlank String paymentType,
String creditCardToken
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.github.gabrmsouza.subscription.infrastructure.rest.models.request;

import jakarta.validation.constraints.NotNull;

public record CreateSubscriptionRequest(@NotNull Long planId) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.github.gabrmsouza.subscription.infrastructure.rest.models.response;

import io.github.gabrmsouza.subscription.application.subscription.CancelSubscription;
import io.github.gabrmsouza.subscription.domain.AssertionConcern;

public record CancelSubscriptionResponse(String subscriptionId, String subscriptionStatus) implements AssertionConcern {
public CancelSubscriptionResponse {
this.assertArgumentNotEmpty(subscriptionId, "CancelSubscriptionResponse 'subscriptionId' should not be empty");
this.assertArgumentNotEmpty(subscriptionStatus, "CancelSubscriptionResponse 'subscriptionStatus' should not be empty");
}

public CancelSubscriptionResponse(CancelSubscription.Output out) {
this(out.subscriptionId().value(), out.subscriptionStatus());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.github.gabrmsouza.subscription.infrastructure.rest.models.response;

import io.github.gabrmsouza.subscription.application.subscription.ChargeSubscription;

public record ChargeSubscriptionResponse(
String subscriptionId,
String subscriptionStatus,
String subscriptionDueDate,
String paymentTransactionId,
String paymentTransactionError
) {

public ChargeSubscriptionResponse(ChargeSubscription.Output out) {
this(
out.subscriptionId().value(),
out.subscriptionStatus(),
out.subscriptionDueDate().toString(),
out.paymentTransaction() == null ? null : out.paymentTransaction().transactionId(),
out.paymentTransaction() == null ? null : out.paymentTransaction().errorMessage()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.github.gabrmsouza.subscription.infrastructure.rest.models.response;

import io.github.gabrmsouza.subscription.application.subscription.CreateSubscription;
import io.github.gabrmsouza.subscription.domain.AssertionConcern;

public record CreateSubscriptionResponse(String subscriptionId) implements AssertionConcern {
public CreateSubscriptionResponse {
this.assertArgumentNotEmpty(subscriptionId, "CreateSubscriptionResponse 'subscriptionId' should not be empty");
}

public CreateSubscriptionResponse(CreateSubscription.Output out) {
this(out.subscriptionId().value());
}
}
Loading

0 comments on commit 08b5698

Please sign in to comment.