From c45814be8551a9ec247454493d8b01a63261ac80 Mon Sep 17 00:00:00 2001 From: Mateusz Czeladka Date: Fri, 15 Sep 2023 10:26:05 +0200 Subject: [PATCH] Feature: Discord auth. --- .../voting/domain/IsVerifiedResponse.java | 2 + .../entity/DiscordUserVerification.java | 2 +- .../DiscordUserVerificationResource.java | 47 ++++++++++++++----- .../resource/UserVerificationResource.java | 33 ++++++++----- ...DefaultDiscordUserVerificationService.java | 8 ++-- .../DefaultSMSSMSUserVerificationService.java | 6 +-- .../voting/utils/CompletableFutures.java | 20 ++++++++ .../h2/V0__user_verification_service_init.sql | 6 +-- .../V0__user_verification_service_init.sql | 6 +-- .../DefaultLeaderBoardService.java | 5 +- 10 files changed, 94 insertions(+), 41 deletions(-) create mode 100644 backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/utils/CompletableFutures.java diff --git a/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/domain/IsVerifiedResponse.java b/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/domain/IsVerifiedResponse.java index fc9108612..f467f7bd3 100644 --- a/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/domain/IsVerifiedResponse.java +++ b/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/domain/IsVerifiedResponse.java @@ -3,9 +3,11 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; +import lombok.ToString; @AllArgsConstructor @Getter +@ToString public class IsVerifiedResponse { private boolean isVerified; diff --git a/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/domain/entity/DiscordUserVerification.java b/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/domain/entity/DiscordUserVerification.java index 9c75a4779..794ce2914 100644 --- a/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/domain/entity/DiscordUserVerification.java +++ b/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/domain/entity/DiscordUserVerification.java @@ -22,7 +22,7 @@ public class DiscordUserVerification extends AbstractTimestampEntity { @Id - @Column(name = "discord_id_hash", nullable = false) + @Column(name = "id", nullable = false) @Getter @Setter private String discordIdHash; diff --git a/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/resource/DiscordUserVerificationResource.java b/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/resource/DiscordUserVerificationResource.java index 3c31dad58..6e976e66c 100644 --- a/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/resource/DiscordUserVerificationResource.java +++ b/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/resource/DiscordUserVerificationResource.java @@ -4,14 +4,19 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.cardano.foundation.voting.domain.IsVerifiedRequest; import org.cardano.foundation.voting.domain.discord.DiscordCheckVerificationRequest; import org.cardano.foundation.voting.domain.discord.DiscordStartVerificationRequest; +import org.cardano.foundation.voting.service.discord.DiscordUserVerificationService; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.Objects; + import static org.springframework.web.bind.annotation.RequestMethod.GET; import static org.springframework.web.bind.annotation.RequestMethod.POST; @@ -21,14 +26,24 @@ @RequiredArgsConstructor public class DiscordUserVerificationResource { + private final DiscordUserVerificationService discordUserVerificationService; + + @Value("${discord.bot.eventId.binding}") + private String discordBotEventIdBinding; + @RequestMapping(value = "/is-verified/{discordIdHash}", method = GET, produces = "application/json") @Timed(value = "resource.discord.isVerified", histogram = true) - public ResponseEntity isDiscordUserVerified(@PathVariable("hashedDiscordId") String hashedDiscordId) { - log.info("Received isDiscordUserVerified hashedDiscordId: {}", hashedDiscordId); + public ResponseEntity isDiscordUserVerified(@PathVariable("discordIdHash") String discordIdHash) { + log.info("Received isDiscordUserVerified request discordIdHash: {}", discordIdHash); - // TODO - - return ResponseEntity.ok().build(); + return discordUserVerificationService.isVerified(new IsVerifiedRequest(discordBotEventIdBinding, discordIdHash)) + .fold(problem -> { + return ResponseEntity.status(Objects.requireNonNull(problem.getStatus()).getStatusCode()).body(problem); + }, + userVerification -> { + return ResponseEntity.ok().body(userVerification); + } + ); } @RequestMapping(value = "/start-verification", method = POST, produces = "application/json") @@ -36,9 +51,14 @@ public ResponseEntity isDiscordUserVerified(@PathVariable("hashedDiscordId") public ResponseEntity startVerification(@RequestBody @Valid DiscordStartVerificationRequest startVerificationRequest) { log.info("Received discord startVerification request: {}", startVerificationRequest); - // TODO - - return ResponseEntity.ok().build(); + return discordUserVerificationService.startVerification(startVerificationRequest) + .fold(problem -> { + return ResponseEntity.status(Objects.requireNonNull(problem.getStatus()).getStatusCode()).body(problem); + }, + userVerification -> { + return ResponseEntity.ok().body(userVerification); + } + ); } @RequestMapping(value = "/check-verification", method = POST, produces = "application/json") @@ -46,9 +66,14 @@ public ResponseEntity startVerification(@RequestBody @Valid DiscordStartVerif public ResponseEntity checkVerification(@RequestBody @Valid DiscordCheckVerificationRequest checkVerificationRequest) { log.info("Received discord checkVerification request: {}", checkVerificationRequest); - // TODO - - return ResponseEntity.ok().build(); + return discordUserVerificationService.checkVerification(checkVerificationRequest) + .fold(problem -> { + return ResponseEntity.status(Objects.requireNonNull(problem.getStatus()).getStatusCode()).body(problem); + }, + userVerification -> { + return ResponseEntity.ok().body(userVerification); + } + ); } } diff --git a/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/resource/UserVerificationResource.java b/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/resource/UserVerificationResource.java index 2ccf37ab0..45c50dbb0 100644 --- a/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/resource/UserVerificationResource.java +++ b/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/resource/UserVerificationResource.java @@ -8,12 +8,14 @@ import org.cardano.foundation.voting.domain.IsVerifiedResponse; import org.cardano.foundation.voting.service.discord.DiscordUserVerificationService; import org.cardano.foundation.voting.service.sms.SMSUserVerificationService; +import org.cardano.foundation.voting.utils.CompletableFutures; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.zalando.problem.Problem; +import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; @@ -34,7 +36,7 @@ public class UserVerificationResource { @Timed(value = "resource.isVerified", histogram = true) public ResponseEntity isVerified(@PathVariable("eventId") String eventId, @PathVariable("stakeAddress") String stakeAddress) throws ExecutionException, InterruptedException { - var isVerifiedRequest = new IsVerifiedRequest(stakeAddress, eventId); + var isVerifiedRequest = new IsVerifiedRequest(eventId, stakeAddress); log.info("Received isVerified request: {}", isVerifiedRequest); @@ -46,19 +48,26 @@ public ResponseEntity isVerified(@PathVariable("eventId") String eventId, return discordUserVerificationService.isVerified(isVerifiedRequest); }); - var isVerified = CompletableFuture.anyOf(smsVerificationFuture, discordVerificationFuture); + var allFutures = CompletableFutures.anyResultsOf(List.of(smsVerificationFuture, discordVerificationFuture)); - var isVerifiedResponseE = (Either) isVerified - .orTimeout(30, SECONDS) - .get(); + List> allResponses = allFutures.orTimeout(30, SECONDS) + .join(); - return isVerifiedResponseE.fold(problem -> { - return ResponseEntity.status(problem.getStatus().getStatusCode()).body(problem); - }, - isVerifiedResponse -> { - return ResponseEntity.ok().body(isVerifiedResponse); - } - ); + var successCount = allResponses.stream().filter(Either::isRight).count(); + + if (successCount != 2) { + var problem = allResponses.stream().filter(Either::isLeft).findFirst().orElseThrow().getLeft(); + + return ResponseEntity.status(problem.getStatus().getStatusCode()).body(problem); + } + + var successes = allResponses.stream().filter(Either::isRight).toList().stream().map(Either::get).toList(); + + var isVerified = successes.stream().reduce((a, b) -> { + return new IsVerifiedResponse(a.isVerified() || b.isVerified()); + }).orElse(new IsVerifiedResponse(false)); + + return ResponseEntity.ok().body(isVerified); } } diff --git a/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/service/discord/DefaultDiscordUserVerificationService.java b/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/service/discord/DefaultDiscordUserVerificationService.java index 37d73cec0..e5c11b51e 100644 --- a/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/service/discord/DefaultDiscordUserVerificationService.java +++ b/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/service/discord/DefaultDiscordUserVerificationService.java @@ -1,7 +1,6 @@ package org.cardano.foundation.voting.service.discord; import io.vavr.control.Either; -import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.cardano.foundation.voting.client.ChainFollowerClient; import org.cardano.foundation.voting.domain.CardanoNetwork; @@ -33,7 +32,6 @@ @Service @Slf4j -@AllArgsConstructor public class DefaultDiscordUserVerificationService implements DiscordUserVerificationService { @Autowired @@ -90,7 +88,7 @@ public Either startVerification(Disco var maybeEvent = eventDetails.get(); if (maybeEvent.isEmpty()) { - log.warn("Active event not found:{}", discordBotEventIdBinding); + log.warn("Event not found:{}", discordBotEventIdBinding); return Either.left(Problem.builder() .withTitle("EVENT_NOT_FOUND") @@ -134,7 +132,7 @@ public Either checkVerification(DiscordCheckVerific var maybeEvent = eventDetails.get(); if (maybeEvent.isEmpty()) { - log.warn("Active event not found:{}", discordBotEventIdBinding); + log.warn("Event not found:{}", discordBotEventIdBinding); return Either.left(Problem.builder() .withTitle("EVENT_NOT_FOUND") @@ -243,7 +241,7 @@ public Either checkVerification(DiscordCheckVerific ); } - DiscordUserVerification pendingUserVerification = maybePendingVerification.orElseThrow(); + var pendingUserVerification = maybePendingVerification.orElseThrow(); var now = LocalDateTime.now(clock); diff --git a/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/service/sms/DefaultSMSSMSUserVerificationService.java b/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/service/sms/DefaultSMSSMSUserVerificationService.java index 1b935b40b..2814bf06d 100644 --- a/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/service/sms/DefaultSMSSMSUserVerificationService.java +++ b/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/service/sms/DefaultSMSSMSUserVerificationService.java @@ -92,7 +92,7 @@ public Either startVerification(SMSStartV var maybeEvent = eventDetails.get(); if (maybeEvent.isEmpty()) { - log.warn("Active event not found:{}", eventId); + log.warn("Event not found:{}", eventId); return Either.left(Problem.builder() .withTitle("EVENT_NOT_FOUND") @@ -238,7 +238,7 @@ public Either checkVerification(SMSCheckVerificatio var maybeEvent = activeEventE.get(); if (maybeEvent.isEmpty()) { - log.error("Active event not found:{}", eventId); + log.error("Event not found:{}", eventId); return Either.left(Problem.builder() .withTitle("EVENT_NOT_FOUND") @@ -345,7 +345,7 @@ public Either isVerified(IsVerifiedRequest isVerifi var maybeEvent = activeEventE.get(); if (maybeEvent.isEmpty()) { - log.error("Active event not found:{}", isVerifiedRequest.getEventId()); + log.error("Event not found:{}", isVerifiedRequest.getEventId()); return Either.left(Problem.builder() .withTitle("EVENT_NOT_FOUND") diff --git a/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/utils/CompletableFutures.java b/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/utils/CompletableFutures.java new file mode 100644 index 000000000..27d2bfbf9 --- /dev/null +++ b/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/utils/CompletableFutures.java @@ -0,0 +1,20 @@ +package org.cardano.foundation.voting.utils; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public final class CompletableFutures { + + public static CompletableFuture> anyResultsOf(List> completableFutures) { + var allFutures = CompletableFuture + .anyOf(completableFutures.toArray(new CompletableFuture[completableFutures.size()])); + + return allFutures.thenApply( + future -> { + return completableFutures.stream() + .map(CompletableFuture::join) + .toList(); + }); + } + +} \ No newline at end of file diff --git a/backend-services/user-verification-service/src/main/resources/db/migration/h2/V0__user_verification_service_init.sql b/backend-services/user-verification-service/src/main/resources/db/migration/h2/V0__user_verification_service_init.sql index 3191f8bb2..6afb1b127 100644 --- a/backend-services/user-verification-service/src/main/resources/db/migration/h2/V0__user_verification_service_init.sql +++ b/backend-services/user-verification-service/src/main/resources/db/migration/h2/V0__user_verification_service_init.sql @@ -32,7 +32,7 @@ create index idx_sms_stake_address_status_phone_hash on sms_user_verification(ev create index idx_sms_stake_address_status_req_id on sms_user_verification(event_id, stake_address, status, request_id); CREATE TABLE discord_user_verification ( - discord_id_hash VARCHAR(255) NOT NULL, + id VARCHAR(255) NOT NULL, -- discord id hash event_id VARCHAR(255) NOT NULL, @@ -47,7 +47,7 @@ CREATE TABLE discord_user_verification ( created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, - CONSTRAINT pk_discord_id_hash PRIMARY KEY (discord_id_hash) + CONSTRAINT pk_discord_id_hash PRIMARY KEY (id) ); create index idx_discord_stake_event_id on sms_user_verification(event_id); @@ -56,4 +56,4 @@ create index idx_discord_event_id_status on sms_user_verification(event_id, stat create index idx_discord_stake_address_status on sms_user_verification(event_id, stake_address, status); -create index idx_discord_status_event_discord_id_hash on sms_user_verification(event_id, status, discord_id_hash); +create index idx_discord_status_event_discord_id_hash on sms_user_verification(event_id, status, id); diff --git a/backend-services/user-verification-service/src/main/resources/db/migration/postgresql/V0__user_verification_service_init.sql b/backend-services/user-verification-service/src/main/resources/db/migration/postgresql/V0__user_verification_service_init.sql index 3191f8bb2..6afb1b127 100644 --- a/backend-services/user-verification-service/src/main/resources/db/migration/postgresql/V0__user_verification_service_init.sql +++ b/backend-services/user-verification-service/src/main/resources/db/migration/postgresql/V0__user_verification_service_init.sql @@ -32,7 +32,7 @@ create index idx_sms_stake_address_status_phone_hash on sms_user_verification(ev create index idx_sms_stake_address_status_req_id on sms_user_verification(event_id, stake_address, status, request_id); CREATE TABLE discord_user_verification ( - discord_id_hash VARCHAR(255) NOT NULL, + id VARCHAR(255) NOT NULL, -- discord id hash event_id VARCHAR(255) NOT NULL, @@ -47,7 +47,7 @@ CREATE TABLE discord_user_verification ( created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, - CONSTRAINT pk_discord_id_hash PRIMARY KEY (discord_id_hash) + CONSTRAINT pk_discord_id_hash PRIMARY KEY (id) ); create index idx_discord_stake_event_id on sms_user_verification(event_id); @@ -56,4 +56,4 @@ create index idx_discord_event_id_status on sms_user_verification(event_id, stat create index idx_discord_stake_address_status on sms_user_verification(event_id, stake_address, status); -create index idx_discord_status_event_discord_id_hash on sms_user_verification(event_id, status, discord_id_hash); +create index idx_discord_status_event_discord_id_hash on sms_user_verification(event_id, status, id); diff --git a/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/service/leader_board/DefaultLeaderBoardService.java b/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/service/leader_board/DefaultLeaderBoardService.java index 4c703b587..e6d8883f7 100644 --- a/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/service/leader_board/DefaultLeaderBoardService.java +++ b/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/service/leader_board/DefaultLeaderBoardService.java @@ -41,7 +41,6 @@ private Either isHighLevelEventLeaderboardAvailable(ChainFollo return Either.right(eventDetails.proposalsReveal()); } - private Either isHighLevelCategoryLeaderboardAvailable(ChainFollowerClient.EventDetailsResponse eventDetails, boolean forceLeaderboard) { if (forceLeaderboard) { @@ -70,7 +69,8 @@ private Either isCategoryLeaderboardAvailable(ChainFollowerCli } @Override - public Either isHighLevelEventLeaderboardAvailable(String event, boolean forceLeaderboard) { + public Either isHighLevelEventLeaderboardAvailable(String event, + boolean forceLeaderboard) { var eventDetailsE = chainFollowerClient.getEventDetails(event); if (eventDetailsE.isEmpty()) { return Either.left(Problem.builder() @@ -159,7 +159,6 @@ public Either getEventLeaderboard(String even var votes = voteRepository.getHighLevelEventStats(event); if (votes.isEmpty()) { - Leaderboard.ByEventStats.ByEventStatsBuilder byEventStatsBuilder = Leaderboard.ByEventStats.builder() .event(eventDetails.id()) .totalVotesCount(0L);