diff --git a/.gradle/8.8/checksums/checksums.lock b/.gradle/8.8/checksums/checksums.lock deleted file mode 100644 index 89e73855e..000000000 Binary files a/.gradle/8.8/checksums/checksums.lock and /dev/null differ diff --git a/.gradle/8.8/checksums/md5-checksums.bin b/.gradle/8.8/checksums/md5-checksums.bin deleted file mode 100644 index c9cc52752..000000000 Binary files a/.gradle/8.8/checksums/md5-checksums.bin and /dev/null differ diff --git a/.gradle/8.8/checksums/sha1-checksums.bin b/.gradle/8.8/checksums/sha1-checksums.bin deleted file mode 100644 index 59a046bdf..000000000 Binary files a/.gradle/8.8/checksums/sha1-checksums.bin and /dev/null differ diff --git a/.gradle/8.8/dependencies-accessors/gc.properties b/.gradle/8.8/dependencies-accessors/gc.properties deleted file mode 100644 index e69de29bb..000000000 diff --git a/.gradle/8.8/fileChanges/last-build.bin b/.gradle/8.8/fileChanges/last-build.bin deleted file mode 100644 index f76dd238a..000000000 Binary files a/.gradle/8.8/fileChanges/last-build.bin and /dev/null differ diff --git a/.gradle/8.8/fileHashes/fileHashes.bin b/.gradle/8.8/fileHashes/fileHashes.bin deleted file mode 100644 index bbeba673b..000000000 Binary files a/.gradle/8.8/fileHashes/fileHashes.bin and /dev/null differ diff --git a/.gradle/8.8/fileHashes/fileHashes.lock b/.gradle/8.8/fileHashes/fileHashes.lock deleted file mode 100644 index cbccc31c3..000000000 Binary files a/.gradle/8.8/fileHashes/fileHashes.lock and /dev/null differ diff --git a/.gradle/8.8/gc.properties b/.gradle/8.8/gc.properties deleted file mode 100644 index e69de29bb..000000000 diff --git a/.gradle/8.9/checksums/checksums.lock b/.gradle/8.9/checksums/checksums.lock deleted file mode 100644 index ac203378a..000000000 Binary files a/.gradle/8.9/checksums/checksums.lock and /dev/null differ diff --git a/.gradle/8.9/checksums/sha1-checksums.bin b/.gradle/8.9/checksums/sha1-checksums.bin deleted file mode 100644 index afbdcad73..000000000 Binary files a/.gradle/8.9/checksums/sha1-checksums.bin and /dev/null differ diff --git a/.gradle/8.9/dependencies-accessors/gc.properties b/.gradle/8.9/dependencies-accessors/gc.properties deleted file mode 100644 index e69de29bb..000000000 diff --git a/.gradle/8.9/executionHistory/executionHistory.lock b/.gradle/8.9/executionHistory/executionHistory.lock deleted file mode 100644 index 3f7fb2033..000000000 Binary files a/.gradle/8.9/executionHistory/executionHistory.lock and /dev/null differ diff --git a/.gradle/8.9/fileChanges/last-build.bin b/.gradle/8.9/fileChanges/last-build.bin deleted file mode 100644 index f76dd238a..000000000 Binary files a/.gradle/8.9/fileChanges/last-build.bin and /dev/null differ diff --git a/.gradle/8.9/fileHashes/fileHashes.lock b/.gradle/8.9/fileHashes/fileHashes.lock deleted file mode 100644 index 1643d5627..000000000 Binary files a/.gradle/8.9/fileHashes/fileHashes.lock and /dev/null differ diff --git a/.gradle/8.9/gc.properties b/.gradle/8.9/gc.properties deleted file mode 100644 index e69de29bb..000000000 diff --git a/.gradle/buildOutputCleanup/cache.properties b/.gradle/buildOutputCleanup/cache.properties deleted file mode 100644 index 9c6b7dba1..000000000 --- a/.gradle/buildOutputCleanup/cache.properties +++ /dev/null @@ -1,2 +0,0 @@ -#Tue Mar 25 22:21:14 EDT 2025 -gradle.version=8.12 diff --git a/.gradle/vcs-1/gc.properties b/.gradle/vcs-1/gc.properties deleted file mode 100644 index e69de29bb..000000000 diff --git a/Microservices/event-service/src/main/java/app/sportahub/eventservice/config/kafka/KafkaProducerConfig.java b/Microservices/event-service/src/main/java/app/sportahub/eventservice/config/kafka/KafkaProducerConfig.java index 62b84ea52..70f1bdb6d 100644 --- a/Microservices/event-service/src/main/java/app/sportahub/eventservice/config/kafka/KafkaProducerConfig.java +++ b/Microservices/event-service/src/main/java/app/sportahub/eventservice/config/kafka/KafkaProducerConfig.java @@ -1,18 +1,25 @@ package app.sportahub.eventservice.config.kafka; +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; + import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.common.serialization.StringSerializer; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.kafka.annotation.EnableKafka; +import org.springframework.kafka.core.ConsumerFactory; import org.springframework.kafka.core.DefaultKafkaProducerFactory; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.kafka.core.ProducerFactory; +import org.springframework.kafka.listener.ContainerProperties; +import org.springframework.kafka.listener.KafkaMessageListenerContainer; +import org.springframework.kafka.requestreply.ReplyingKafkaTemplate; import org.springframework.kafka.support.serializer.JsonSerializer; -import java.util.HashMap; -import java.util.Map; +import app.sportahub.kafka.events.user.UserEvent; @EnableKafka @Configuration @@ -31,7 +38,25 @@ public ProducerFactory producerFactory() { } @Bean - public KafkaTemplate kafkaTemplate(ProducerFactory producerFactory) { - return new KafkaTemplate<>(producerFactory); + public KafkaTemplate kafkaTemplate() { + return new KafkaTemplate<>(producerFactory()); + } + + @Bean + public ReplyingKafkaTemplate replyingKafkaTemplate( + ProducerFactory producerFactory, + KafkaMessageListenerContainer replyContainer) { + ReplyingKafkaTemplate template = new ReplyingKafkaTemplate<>(producerFactory, + replyContainer); + template.setDefaultReplyTimeout(Duration.ofSeconds(6)); + return template; + } + + @Bean + public KafkaMessageListenerContainer replyContainer( + ConsumerFactory consumerFactory) { + ContainerProperties containerProperties = new ContainerProperties(UserEvent.RESPONSE_TOPIC); + containerProperties.setGroupId("OrchestrationServiceConsumer"); + return new KafkaMessageListenerContainer<>(consumerFactory, containerProperties); } -} +} \ No newline at end of file diff --git a/Microservices/event-service/src/main/java/app/sportahub/eventservice/controller/event/EventController.java b/Microservices/event-service/src/main/java/app/sportahub/eventservice/controller/event/EventController.java index 7cf7c5aaf..d270ef906 100644 --- a/Microservices/event-service/src/main/java/app/sportahub/eventservice/controller/event/EventController.java +++ b/Microservices/event-service/src/main/java/app/sportahub/eventservice/controller/event/EventController.java @@ -1,32 +1,42 @@ package app.sportahub.eventservice.controller.event; -import app.sportahub.eventservice.dto.request.event.EventRequest; +import java.util.List; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +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; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + import app.sportahub.eventservice.dto.request.event.WhitelistRequest; import app.sportahub.eventservice.dto.request.event.EventCancellationRequest; +import app.sportahub.eventservice.dto.request.event.EventRequest; import app.sportahub.eventservice.dto.response.EventResponse; import app.sportahub.eventservice.dto.response.ParticipantResponse; +import app.sportahub.eventservice.dto.response.ReactionResponse; import app.sportahub.eventservice.enums.EventSortingField; -import app.sportahub.eventservice.enums.SortDirection; -import app.sportahub.eventservice.model.event.Location; import app.sportahub.eventservice.enums.SkillLevelEnum; -import app.sportahub.eventservice.dto.response.ReactionResponse; +import app.sportahub.eventservice.enums.SortDirection; import app.sportahub.eventservice.model.event.reactor.ReactionType; import app.sportahub.eventservice.service.event.EventService; +import app.sportahub.eventservice.service.recommendation.RecommendationService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import java.util.List; - -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.*; - @RestController @RequestMapping("/event") @@ -35,6 +45,7 @@ public class EventController { private final EventService eventService; + private final RecommendationService recommendationService; @GetMapping("/{id}") @ResponseStatus(HttpStatus.OK) @@ -201,4 +212,20 @@ public EventResponse whitelistUsers(@PathVariable String id, @RequestBody WhitelistRequest whitelistRequest) { return eventService.whitelistUsers(id, whitelistRequest); } -} + + + @GetMapping("/recommendation") + @PreAuthorize("authentication.name == #userId || hasRole('ROLE_ADMIN')") + @ResponseStatus(HttpStatus.OK) + @Operation(summary = "Retrieve events by location", + description = "Fetches events based on the provided location.") + public Page getEventRecommendations( + @RequestParam String userId, + @RequestParam double longitude, + @RequestParam double latitude, + @RequestParam(defaultValue = "30.0") double radius, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size) { + return recommendationService.getRecommendations(userId, longitude, latitude, radius, page, size); + } +} \ No newline at end of file diff --git a/Microservices/event-service/src/main/java/app/sportahub/eventservice/exception/GlobalExceptionHandler.java b/Microservices/event-service/src/main/java/app/sportahub/eventservice/exception/GlobalExceptionHandler.java index 8403a1522..608947407 100644 --- a/Microservices/event-service/src/main/java/app/sportahub/eventservice/exception/GlobalExceptionHandler.java +++ b/Microservices/event-service/src/main/java/app/sportahub/eventservice/exception/GlobalExceptionHandler.java @@ -1,14 +1,20 @@ package app.sportahub.eventservice.exception; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.server.ResponseStatusException; -import java.util.HashMap; -import java.util.Map; +import lombok.extern.slf4j.Slf4j; @ControllerAdvice +@Slf4j public class GlobalExceptionHandler { @ExceptionHandler(ResponseStatusException.class) @@ -18,4 +24,13 @@ public class GlobalExceptionHandler { response.put("message", ex.getStatusCode().value()); return ResponseEntity.status(ex.getStatusCode()).body(response); } + + @ExceptionHandler(Exception.class) + public ResponseEntity> handleException(Exception ex) { + Map response = new HashMap<>(); + response.put("error", "Internal Server Error"); + response.put("message", ex.getMessage()); + log.error(ex.getMessage(), ex); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response); + } } \ No newline at end of file diff --git a/Microservices/event-service/src/main/java/app/sportahub/eventservice/factory/ScoringFactory.java b/Microservices/event-service/src/main/java/app/sportahub/eventservice/factory/ScoringFactory.java new file mode 100644 index 000000000..a2936be14 --- /dev/null +++ b/Microservices/event-service/src/main/java/app/sportahub/eventservice/factory/ScoringFactory.java @@ -0,0 +1,35 @@ +package app.sportahub.eventservice.factory; + +import java.util.List; + +import app.sportahub.eventservice.mapper.user.UserProfileMapper; +import app.sportahub.eventservice.model.event.Event; +import app.sportahub.eventservice.model.user.UserProfile; +import app.sportahub.eventservice.service.kafka.producer.OrchestrationServiceProducer; +import app.sportahub.eventservice.service.recommendation.strategies.HistoryScoreStrategy; +import app.sportahub.eventservice.service.recommendation.strategies.ProfileScoreStrategy; +import app.sportahub.eventservice.service.recommendation.strategies.ScoreStrategy; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class ScoringFactory { + + private final UserProfileMapper userProfileMapper; + private final OrchestrationServiceProducer orchestrationServiceProducer; + + public ScoringFactory(OrchestrationServiceProducer orchestrationServiceProducer) { + this.orchestrationServiceProducer = orchestrationServiceProducer; + this.userProfileMapper = new UserProfileMapper(); + } + + public ScoreStrategy getScoringStrategy(List userEventHistory, String userId) { + if (userEventHistory.isEmpty()) { + String userString = orchestrationServiceProducer.getUserById(userId); + UserProfile userProfile = userProfileMapper.userStringToUserProfile(userString); + log.info("ScoringFactory::getScoringStrategy: user retrieved {}", userProfile); + return new ProfileScoreStrategy(userProfile); + } else { + return new HistoryScoreStrategy(userEventHistory); + } + } +} \ No newline at end of file diff --git a/Microservices/event-service/src/main/java/app/sportahub/eventservice/mapper/user/UserProfileMapper.java b/Microservices/event-service/src/main/java/app/sportahub/eventservice/mapper/user/UserProfileMapper.java new file mode 100644 index 000000000..fb44fc42b --- /dev/null +++ b/Microservices/event-service/src/main/java/app/sportahub/eventservice/mapper/user/UserProfileMapper.java @@ -0,0 +1,19 @@ +package app.sportahub.eventservice.mapper.user; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import app.sportahub.eventservice.model.user.UserProfile; + +public class UserProfileMapper { + + public UserProfile userStringToUserProfile(String user) { + ObjectMapper objectMapper = new ObjectMapper(); + UserProfile userProfile; + try { + userProfile = objectMapper.readValue(user, UserProfile.class); + } catch (Exception e) { + throw new RuntimeException("Error processing JSON for user data"); + } + return userProfile; + } +} \ No newline at end of file diff --git a/Microservices/event-service/src/main/java/app/sportahub/eventservice/model/user/UserProfile.java b/Microservices/event-service/src/main/java/app/sportahub/eventservice/model/user/UserProfile.java new file mode 100644 index 000000000..026a1c7ee --- /dev/null +++ b/Microservices/event-service/src/main/java/app/sportahub/eventservice/model/user/UserProfile.java @@ -0,0 +1,42 @@ +package app.sportahub.eventservice.model.user; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import app.sportahub.eventservice.enums.SkillLevelEnum; +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class UserProfile { + + @JsonProperty("profile") + private Profile profile; + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Profile { + @JsonProperty("sportsOfPreference") + private List sportsOfPreference; + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class SportPreferences { + @JsonProperty("name") + private String sport; + + @JsonProperty("ranking") + private SkillLevelEnum ranking; + + public void setRanking(String ranking) { + try { + this.ranking = SkillLevelEnum.valueOf(ranking.toUpperCase()); + } catch (IllegalArgumentException | NullPointerException e) { + this.ranking = SkillLevelEnum.BEGINNER; + } + } + } +} \ No newline at end of file diff --git a/Microservices/event-service/src/main/java/app/sportahub/eventservice/repository/event/EventRepository.java b/Microservices/event-service/src/main/java/app/sportahub/eventservice/repository/event/EventRepository.java index 754aa8adf..aa858f8e8 100644 --- a/Microservices/event-service/src/main/java/app/sportahub/eventservice/repository/event/EventRepository.java +++ b/Microservices/event-service/src/main/java/app/sportahub/eventservice/repository/event/EventRepository.java @@ -1,7 +1,8 @@ package app.sportahub.eventservice.repository.event; -import app.sportahub.eventservice.model.event.Event; -import app.sportahub.eventservice.repository.SearchingEventRepository; +import java.util.List; +import java.util.Optional; + import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.geo.Distance; @@ -10,8 +11,8 @@ import org.springframework.data.mongodb.repository.Query; import org.springframework.stereotype.Repository; -import java.util.List; -import java.util.Optional; +import app.sportahub.eventservice.model.event.Event; +import app.sportahub.eventservice.repository.SearchingEventRepository; @Repository public interface EventRepository extends MongoRepository, SearchingEventRepository { @@ -23,9 +24,9 @@ public interface EventRepository extends MongoRepository, Searchi Optional findEventByEventName(String eventName); Page findByParticipantsUserId(String userId, Pageable pageable); + + List findByParticipantsUserId(String userId); Page findByCreatedBy(String userId, Pageable pageable); - @Query("{ 'participants.userId' : ?0 }") - List findAllByParticipantUserId(String userId); -} +} \ No newline at end of file diff --git a/Microservices/event-service/src/main/java/app/sportahub/eventservice/service/kafka/consumer/OrchestrationServiceConsumerImpl.java b/Microservices/event-service/src/main/java/app/sportahub/eventservice/service/kafka/consumer/OrchestrationServiceConsumerImpl.java index 5b9b3b9c1..35615307f 100644 --- a/Microservices/event-service/src/main/java/app/sportahub/eventservice/service/kafka/consumer/OrchestrationServiceConsumerImpl.java +++ b/Microservices/event-service/src/main/java/app/sportahub/eventservice/service/kafka/consumer/OrchestrationServiceConsumerImpl.java @@ -38,7 +38,7 @@ public void handleEventsByUserRequest( String userId = fetchEvent.getUserId(); - List eventIds = eventRepository.findAllByParticipantUserId(userId) + List eventIds = eventRepository.findByParticipantsUserId(userId) .stream() .map(Event::getId) .collect(Collectors.toList()); diff --git a/Microservices/event-service/src/main/java/app/sportahub/eventservice/service/kafka/producer/OrchestrationServiceProducer.java b/Microservices/event-service/src/main/java/app/sportahub/eventservice/service/kafka/producer/OrchestrationServiceProducer.java index d8773b45b..420051a36 100644 --- a/Microservices/event-service/src/main/java/app/sportahub/eventservice/service/kafka/producer/OrchestrationServiceProducer.java +++ b/Microservices/event-service/src/main/java/app/sportahub/eventservice/service/kafka/producer/OrchestrationServiceProducer.java @@ -2,9 +2,8 @@ import app.sportahub.kafka.events.notification.NotificationEvent; -import java.util.List; -import java.util.Map; public interface OrchestrationServiceProducer { void sendNotificationEvent(NotificationEvent event); -} + String getUserById(String userId); +} \ No newline at end of file diff --git a/Microservices/event-service/src/main/java/app/sportahub/eventservice/service/kafka/producer/OrchestrationServiceProducerImpl.java b/Microservices/event-service/src/main/java/app/sportahub/eventservice/service/kafka/producer/OrchestrationServiceProducerImpl.java index 6da51fc6e..c73503ec6 100644 --- a/Microservices/event-service/src/main/java/app/sportahub/eventservice/service/kafka/producer/OrchestrationServiceProducerImpl.java +++ b/Microservices/event-service/src/main/java/app/sportahub/eventservice/service/kafka/producer/OrchestrationServiceProducerImpl.java @@ -1,11 +1,27 @@ package app.sportahub.eventservice.service.kafka.producer; import app.sportahub.kafka.events.notification.NotificationEvent; +import app.sportahub.kafka.events.BaseEvent; import app.sportahub.kafka.events.SportaKafkaEvents; +import app.sportahub.kafka.events.user.UserEvent; +import app.sportahub.kafka.events.user.UserRequestEvent; +import app.sportahub.kafka.events.user.UserResponseEvent; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; + +import java.time.Instant; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import org.springframework.kafka.support.SendResult; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.clients.producer.ProducerRecord; import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.requestreply.ReplyingKafkaTemplate; +import org.springframework.kafka.requestreply.RequestReplyFuture; +import org.springframework.kafka.support.KafkaHeaders; import org.springframework.stereotype.Service; @Service @@ -14,11 +30,59 @@ public class OrchestrationServiceProducerImpl implements OrchestrationServiceProducer { private final KafkaTemplate kafkaTemplate; + private final ReplyingKafkaTemplate replyingKafkaTemplate; @SneakyThrows @Override public void sendNotificationEvent(NotificationEvent event) { kafkaTemplate.send(SportaKafkaEvents.NOTIFICATION_REQUEST_TOPIC, event); - log.info("OrchestrationServiceProducerImpl::sendNotificationEvent: NotificationEvent sent to topic for user {}", event.getUserId()); + log.info("OrchestrationServiceProducerImpl::sendNotificationEvent: NotificationEvent sent to topic for user {}", + event.getUserId()); + } + + @SneakyThrows + @Override + public String getUserById(String userId) { + BaseEvent baseEvent = new BaseEvent( + UUID.randomUUID().toString(), + "request", + "event-service", + Instant.now(), + UUID.randomUUID().toString()); + + UserRequestEvent userRequestEvent = new UserRequestEvent(baseEvent, userId); + + ProducerRecord record = new ProducerRecord<>( + UserEvent.REQUEST_TOPIC, + userRequestEvent); + record.headers().add(KafkaHeaders.REPLY_TOPIC, UserEvent.RESPONSE_TOPIC.getBytes()); + + RequestReplyFuture future = replyingKafkaTemplate.sendAndReceive(record); + log.info("OrchestrationServiceProducerImpl::getUserById: sent request for user with id: {}", + userRequestEvent.getUserId()); + + SendResult sendResult = future.getSendFuture().get(); + sendResult.getProducerRecord().headers() + .forEach(header -> log.info( + "OrchestrationServiceProducerImpl::getUserById: header key: {}, header value: {}", + header.key(), header.value())); + + ConsumerRecord response = future.get(15, TimeUnit.SECONDS); + log.info("OrchestrationServiceProducerImpl::getUserById: received response for user with id: {}", + userRequestEvent.getUserId()); + + if (response.value() instanceof UserResponseEvent userResponseEvent + && Objects.equals(userResponseEvent.getBaseEvent().getCorrelationId(), + userRequestEvent.getBaseEvent().getCorrelationId())) { + log.info("OrchestrationServiceImpl::getUserById: response is of type UserResponseEvent: {}", + response.value()); + + return userResponseEvent.getUser(); + } else { + log.info("OrchestrationServiceImpl::getUserById: response is not of type UserResponseEvent: {}", + response.value()); + return null; + } + } -} +} \ No newline at end of file diff --git a/Microservices/event-service/src/main/java/app/sportahub/eventservice/service/recommendation/RecommendationService.java b/Microservices/event-service/src/main/java/app/sportahub/eventservice/service/recommendation/RecommendationService.java new file mode 100644 index 000000000..d0d1e1a89 --- /dev/null +++ b/Microservices/event-service/src/main/java/app/sportahub/eventservice/service/recommendation/RecommendationService.java @@ -0,0 +1,11 @@ +package app.sportahub.eventservice.service.recommendation; + +import org.springframework.data.domain.Page; + +import app.sportahub.eventservice.dto.response.EventResponse; + +public interface RecommendationService { + + public Page getRecommendations(String userId, double longitude, double latitude, double radius, int page, int size); + +} \ No newline at end of file diff --git a/Microservices/event-service/src/main/java/app/sportahub/eventservice/service/recommendation/RecommendationServiceImpl.java b/Microservices/event-service/src/main/java/app/sportahub/eventservice/service/recommendation/RecommendationServiceImpl.java new file mode 100644 index 000000000..2af8578e3 --- /dev/null +++ b/Microservices/event-service/src/main/java/app/sportahub/eventservice/service/recommendation/RecommendationServiceImpl.java @@ -0,0 +1,107 @@ +package app.sportahub.eventservice.service.recommendation; + +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.geo.Distance; +import org.springframework.data.geo.Metrics; +import org.springframework.data.mongodb.core.geo.GeoJsonPoint; +import org.springframework.stereotype.Service; + +import app.sportahub.eventservice.dto.response.EventResponse; +import app.sportahub.eventservice.factory.ScoringFactory; +import app.sportahub.eventservice.mapper.event.EventMapper; +import app.sportahub.eventservice.model.event.Event; +import app.sportahub.eventservice.repository.event.EventRepository; +import app.sportahub.eventservice.service.kafka.producer.OrchestrationServiceProducer; +import app.sportahub.eventservice.service.recommendation.strategies.ScoreStrategy; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service("recommendationService") +@RequiredArgsConstructor +public class RecommendationServiceImpl implements RecommendationService { + + private final EventRepository eventRepository; + private final OrchestrationServiceProducer orchestrationServiceProducer; + private final EventMapper eventMapper; + + @Override + public Page getRecommendations(String userId, double longitude, double latitude, double radius, + int page, int size) { + GeoJsonPoint point = new GeoJsonPoint(longitude, latitude); + Distance distance = new Distance(radius, Metrics.KILOMETERS); + Pageable pageable = PageRequest.of(page, size); + List eventsList = eventRepository.findByLocationCoordinatesNear(point, distance, pageable).getContent() + .stream() + .collect(Collectors.toList()); + + eventsList.removeIf(event -> !(event.getIsPrivate() && event.getWhitelistedUsers().contains(userId)) + || isEventCutOffTimePassed(event.getCutOffTime())); + + if (eventsList.isEmpty()) { + log.info("RecommendationServiceImpl::getRecommendations: no valid events found for user {}", userId); + return new PageImpl<>(new ArrayList<>(), pageable, 0); + } + log.info("RecommendationServiceImpl::getRecommendations: found {} events for user {}", eventsList.size(), + userId); + + List userEventHistoryList = eventRepository.findByParticipantsUserId(userId); + + ScoreStrategy scoreService = new ScoringFactory(orchestrationServiceProducer) + .getScoringStrategy(userEventHistoryList, userId); + log.info( + "RecommendationServiceImpl::getRecommendations: Strategy used for calculating recommendation scores: {}", + scoreService.getClass().getSimpleName()); + Map eventScores = scoreService.computeScores(eventsList); + + List eventResponse = eventScores.entrySet().parallelStream() + .sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue())) + .map(Map.Entry::getKey) + .map(eventMapper::eventToEventResponse) + .collect(Collectors.toList()); + + log.info("RecommendationServiceImpl::getRecommendations: returning {} events for user {}", eventResponse.size(), + userId); + int fromIndex = (int) pageable.getOffset() > eventResponse.size() ? eventResponse.size() + : (int) pageable.getOffset(); + int toIndex = Math.min(fromIndex + size, eventResponse.size()); + if (fromIndex > toIndex) { + fromIndex = toIndex; + } + return new PageImpl<>(eventResponse.subList(fromIndex, toIndex), pageable, eventResponse.size()); + } + + private boolean isEventCutOffTimePassed(String cutOffTime) { + try { + LocalDateTime currentTime = LocalDateTime.now(); + // handle for offset time + if (cutOffTime.contains("+") || cutOffTime.charAt(cutOffTime.length() - 6) == '-' + || cutOffTime.contains("Z")) { + ZoneId localZoneId = ZoneId.systemDefault(); + ZoneOffset localZoneOffset = localZoneId.getRules().getOffset(currentTime); + OffsetDateTime currentOffsetDateTime = currentTime.atOffset(localZoneOffset); + return OffsetDateTime.parse(cutOffTime).isBefore(currentOffsetDateTime); + } + // handle for local time + else { + return LocalDateTime.parse(cutOffTime).isBefore(currentTime); + } + + } catch (DateTimeParseException e) { + return false; + } + } +} \ No newline at end of file diff --git a/Microservices/event-service/src/main/java/app/sportahub/eventservice/service/recommendation/strategies/HistoryScoreStrategy.java b/Microservices/event-service/src/main/java/app/sportahub/eventservice/service/recommendation/strategies/HistoryScoreStrategy.java new file mode 100644 index 000000000..e5d131b7d --- /dev/null +++ b/Microservices/event-service/src/main/java/app/sportahub/eventservice/service/recommendation/strategies/HistoryScoreStrategy.java @@ -0,0 +1,111 @@ +package app.sportahub.eventservice.service.recommendation.strategies; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import app.sportahub.eventservice.enums.SkillLevelEnum; +import app.sportahub.eventservice.model.event.Event; +import app.sportahub.eventservice.model.event.participant.Participant; +import app.sportahub.eventservice.utils.Haversine; + +public class HistoryScoreStrategy implements ScoreStrategy { + + private List userEventHistory; + + // score weights + private static final double DISTANCE_WEIGHT = 2 / 5.0; + private static final double CATEGORICAL_WEIGHT = 2 / 5.0; + private static final double TIME_WEIGHT = 1 / 5.0; + + public HistoryScoreStrategy(List userEventHistory) { + this.userEventHistory = userEventHistory; + } + + @Override + public Map computeScores(List events) { + Map eventScores = events.parallelStream().collect(Collectors.toConcurrentMap( + event -> event, + event -> generateEventScore(event, userEventHistory))); + + return eventScores; + } + + private double generateEventScore(Event event, List userEventHistoryList) { + List eventScores = new ArrayList<>(); + userEventHistoryList.forEach(historyEvent -> { + + if (historyEvent.getId().equals(event.getId())) { + return; + } + + double distanceScore = haversineDistance(historyEvent, event) / 100; + double timeScore = timeSimilarity(historyEvent, event); + double categoricalScore = jaccardSimilarity(historyEvent, event); + + double score = DISTANCE_WEIGHT * distanceScore + CATEGORICAL_WEIGHT * categoricalScore + + timeScore * TIME_WEIGHT; + + eventScores.add(score); + }); + + double averageScore = eventScores.stream().mapToDouble(Double::doubleValue).average().orElse(0.0); + + return averageScore; + } + + private double jaccardSimilarity(Event event1, Event event2) { + Set participants1 = event1.getParticipants().stream() + .map(Participant::getUserId) + .collect(Collectors.toSet()); + + Set participants2 = event2.getParticipants().stream() + .map(Participant::getUserId) + .collect(Collectors.toSet()); + + Set intersection = new HashSet<>(participants1); + intersection.retainAll(participants2); + Set union = new HashSet<>(participants1); + union.addAll(participants2); + + double participantScore = (double) intersection.size() / union.size(); + + Set skills1 = event1.getRequiredSkillLevel().stream() + .map(SkillLevelEnum::toString) + .collect(Collectors.toSet()); + + Set skills2 = event2.getRequiredSkillLevel().stream() + .map(SkillLevelEnum::toString) + .collect(Collectors.toSet()); + + Set intersectionSkills = new HashSet<>(skills1); + intersectionSkills.retainAll(skills2); + Set unionSkills = new HashSet<>(skills1); + unionSkills.addAll(skills2); + + double skillScore = (double) intersectionSkills.size() / unionSkills.size(); + + double sportTypeScore = event1.getSportType().equalsIgnoreCase(event2.getSportType()) ? 1.0 : 0.0; + double similarityScore = (participantScore + skillScore + sportTypeScore) / 3.0; + + return similarityScore; + } + + private double timeSimilarity(Event event1, Event event2) { + double startTimeScore = 1 + - Math.abs(event1.getStartTime().toSecondOfDay() - event2.getStartTime().toSecondOfDay()) / 86400.0; + double duration1 = event1.getDuration().isEmpty() ? 0.0 : Double.parseDouble(event1.getDuration()); + double duration2 = event2.getDuration().isEmpty() ? 0.0 : Double.parseDouble(event2.getDuration()); + double durationScore = 1 - Math.abs(duration1 - duration2) / 24.0; + double similarityScore = (startTimeScore + durationScore) / 2.0; + + return similarityScore; + } + + private double haversineDistance(Event event1, Event event2) { + return Haversine.EventHaversineDistance(event1, event2); + } +} \ No newline at end of file diff --git a/Microservices/event-service/src/main/java/app/sportahub/eventservice/service/recommendation/strategies/ProfileScoreStrategy.java b/Microservices/event-service/src/main/java/app/sportahub/eventservice/service/recommendation/strategies/ProfileScoreStrategy.java new file mode 100644 index 000000000..04af26866 --- /dev/null +++ b/Microservices/event-service/src/main/java/app/sportahub/eventservice/service/recommendation/strategies/ProfileScoreStrategy.java @@ -0,0 +1,55 @@ +package app.sportahub.eventservice.service.recommendation.strategies; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import app.sportahub.eventservice.enums.SkillLevelEnum; +import app.sportahub.eventservice.model.event.Event; +import app.sportahub.eventservice.model.user.UserProfile; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class ProfileScoreStrategy implements ScoreStrategy { + + private UserProfile user; + + public ProfileScoreStrategy(UserProfile user) { + this.user = user; + } + + @Override + public Map computeScores(List events) { + log.info("ProfileScoreStrategy::computeScores: computing scores for {} events with user profile {}", + events.size(), user); + Map eventScores = events.parallelStream().collect(Collectors.toConcurrentMap( + event -> event, + event -> generateEventScore(event))); + return eventScores; + } + + private double generateEventScore(Event event) { + if (user.getProfile() == null || user.getProfile().getSportsOfPreference().isEmpty()) { + log.info("ProfileScoreStrategy::generateEventScore: user has no sports of preference, returning 0.0"); + return 0.0; + } + + log.info( + "ProfileScoreStrategy::generateEventScore: user has sports of preference, calculating score for event {}", + event); + Map sports = user.getProfile().getSportsOfPreference().stream() + .collect(Collectors.toMap( + sport -> sport.getSport().toLowerCase().trim(), + sport -> sport.getRanking())); + + Integer score = 0; + if (sports.containsKey(event.getSportType().toLowerCase().trim())) { + score += 1; + if (event.getRequiredSkillLevel().contains(sports.get(event.getSportType()))) { + score += 1; + } + } + + return score == 0 ? 0.0 : score / 2.0; + } +} \ No newline at end of file diff --git a/Microservices/event-service/src/main/java/app/sportahub/eventservice/service/recommendation/strategies/ScoreStrategy.java b/Microservices/event-service/src/main/java/app/sportahub/eventservice/service/recommendation/strategies/ScoreStrategy.java new file mode 100644 index 000000000..81ba62cab --- /dev/null +++ b/Microservices/event-service/src/main/java/app/sportahub/eventservice/service/recommendation/strategies/ScoreStrategy.java @@ -0,0 +1,10 @@ +package app.sportahub.eventservice.service.recommendation.strategies; + +import java.util.List; +import java.util.Map; + +import app.sportahub.eventservice.model.event.Event; + +public interface ScoreStrategy { + public Map computeScores(List events); +} \ No newline at end of file diff --git a/Microservices/event-service/src/main/java/app/sportahub/eventservice/utils/Haversine.java b/Microservices/event-service/src/main/java/app/sportahub/eventservice/utils/Haversine.java new file mode 100644 index 000000000..b8bf4d175 --- /dev/null +++ b/Microservices/event-service/src/main/java/app/sportahub/eventservice/utils/Haversine.java @@ -0,0 +1,27 @@ +package app.sportahub.eventservice.utils; + +import app.sportahub.eventservice.model.event.Event; + +public class Haversine { + + private static final double EARTH_RADIUS = 6371; + private static final double DEG_TO_RAD = Math.PI / 180.0; + + public static double EventHaversineDistance(Event event1, Event event2) { + double longitude1 = event1.getLocation().getCoordinates().getX() * DEG_TO_RAD; + double latitude1 = event1.getLocation().getCoordinates().getY() * DEG_TO_RAD; + double longitude2 = event2.getLocation().getCoordinates().getX() * DEG_TO_RAD; + double latitude2 = event2.getLocation().getCoordinates().getY() * DEG_TO_RAD; + + double dlat = latitude2 - latitude1; + double dlon = longitude2 - longitude1; + + double a = Math.pow(Math.sin(dlat / 2), 2) + + Math.pow(Math.sin(dlon / 2), 2) * + Math.cos(latitude1) * + Math.cos(latitude2); + + double distance = EARTH_RADIUS * 2 * Math.asin(Math.sqrt(a)); + return distance; + } +} diff --git a/Microservices/orchestration-service/src/main/java/app/sportahub/orchestrationservice/config/KafkaProducerConfig.java b/Microservices/orchestration-service/src/main/java/app/sportahub/orchestrationservice/config/KafkaProducerConfig.java index edf6f4eb5..0d1006139 100644 --- a/Microservices/orchestration-service/src/main/java/app/sportahub/orchestrationservice/config/KafkaProducerConfig.java +++ b/Microservices/orchestration-service/src/main/java/app/sportahub/orchestrationservice/config/KafkaProducerConfig.java @@ -1,6 +1,7 @@ package app.sportahub.orchestrationservice.config; import app.sportahub.kafka.events.SportaKafkaEvents; +import app.sportahub.kafka.events.user.UserEvent; import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.common.serialization.StringSerializer; import org.springframework.beans.factory.annotation.Value; diff --git a/Microservices/orchestration-service/src/main/java/app/sportahub/orchestrationservice/service/consumer/UserServiceConsumerImpl.java b/Microservices/orchestration-service/src/main/java/app/sportahub/orchestrationservice/service/consumer/UserServiceConsumerImpl.java index b76a1dc18..47315bd3a 100644 --- a/Microservices/orchestration-service/src/main/java/app/sportahub/orchestrationservice/service/consumer/UserServiceConsumerImpl.java +++ b/Microservices/orchestration-service/src/main/java/app/sportahub/orchestrationservice/service/consumer/UserServiceConsumerImpl.java @@ -4,6 +4,11 @@ import app.sportahub.kafka.events.SportaKafkaEvents; import app.sportahub.kafka.events.forgotpassword.ForgotPasswordRequestedEvent; import app.sportahub.kafka.events.forgotpassword.ForgotPasswordSendEmailEvent; +import app.sportahub.kafka.events.user.UserEvent; +import app.sportahub.kafka.events.user.UserFetchEvent; +import app.sportahub.kafka.events.user.UserFetchedEvent; +import app.sportahub.kafka.events.user.UserRequestEvent; +import app.sportahub.kafka.events.user.UserResponseEvent; import app.sportahub.kafka.events.joinsporteventevent.*; import app.sportahub.orchestrationservice.service.producer.EmailServiceProducer; import lombok.RequiredArgsConstructor; @@ -132,4 +137,55 @@ public void listenForJoinedEventsByUserRequestEvent( replyingKafkaTemplate.stop(); } } -} + + @SneakyThrows + @KafkaListener(topics = UserEvent.REQUEST_TOPIC, groupId = "UserServiceKafkaConsumer") + public void listenForUserRequests( + @Payload UserRequestEvent requestEvent, + @Header(KafkaHeaders.CORRELATION_ID) byte[] correlationId + ) { + log.info("EventServiceConsumer::listenForUserRequests: received user request event with id: {}", requestEvent.getUserId()); + + String userId = requestEvent.getUserId(); + BaseEvent fetchBaseEvent = new BaseEvent( + UUID.randomUUID().toString(), + "request", + "orchestration-service", + Instant.now(), + requestEvent.getBaseEvent().getCorrelationId() + ); + + UserFetchEvent fetchEvent = new UserFetchEvent(fetchBaseEvent, userId); + ProducerRecord record = new ProducerRecord<>(UserEvent.FETCH_TOPIC, fetchEvent); + record.headers().add(new RecordHeader(KafkaHeaders.REPLY_TOPIC, UserEvent.FETCHED_TOPIC.getBytes())); + record.headers().add(new RecordHeader(KafkaHeaders.CORRELATION_ID, correlationId)); + + replyingKafkaTemplate.send(record); + log.info("EventServiceConsumer::listenForUserRequests: sent fetch request for user with id: {}", userId); + } + + @SneakyThrows + @KafkaListener(topics = UserEvent.FETCHED_TOPIC, groupId = "UserServiceKafkaConsumer") + public void listenForFetchedUsers( + @Payload UserFetchedEvent fetchedEvent, + @Header(KafkaHeaders.CORRELATION_ID) byte[] correlationId + ) { + + log.info("EventServiceConsumer::listenForFetchedUsers: received fetched user event with id: {}", fetchedEvent.getUser()); + + BaseEvent fetchedBaseEvent = new BaseEvent( + UUID.randomUUID().toString(), + "response", + "orchestration-service", + Instant.now(), + fetchedEvent.getBaseEvent().getCorrelationId() + ); + + UserResponseEvent responseEvent = new UserResponseEvent(fetchedBaseEvent, fetchedEvent.getUser()); + ProducerRecord responseRecord = new ProducerRecord<>(UserEvent.RESPONSE_TOPIC, responseEvent); + responseRecord.headers().add(new RecordHeader(KafkaHeaders.CORRELATION_ID, correlationId)); + responseRecord.headers().forEach(header -> log.info("Header key: {}, value: {}", header.key(),header.value().toString())); + kafkaTemplate.send(responseRecord); + log.info("EventServiceConsumer::listenForFetchedUsers: sent response to event-service for user with id: {}", fetchedEvent.getUser()); + } +} \ No newline at end of file diff --git a/Microservices/user-service/src/main/java/app/sportahub/userservice/service/kafka/consumer/OrchestrationServiceConsumer.java b/Microservices/user-service/src/main/java/app/sportahub/userservice/service/kafka/consumer/OrchestrationServiceConsumer.java new file mode 100644 index 000000000..b53d29023 --- /dev/null +++ b/Microservices/user-service/src/main/java/app/sportahub/userservice/service/kafka/consumer/OrchestrationServiceConsumer.java @@ -0,0 +1,5 @@ +package app.sportahub.userservice.service.kafka.consumer; + +public interface OrchestrationServiceConsumer { + +} diff --git a/Microservices/user-service/src/main/java/app/sportahub/userservice/service/kafka/consumer/OrchestrationServiceConsumerImpl.java b/Microservices/user-service/src/main/java/app/sportahub/userservice/service/kafka/consumer/OrchestrationServiceConsumerImpl.java new file mode 100644 index 000000000..45db594ef --- /dev/null +++ b/Microservices/user-service/src/main/java/app/sportahub/userservice/service/kafka/consumer/OrchestrationServiceConsumerImpl.java @@ -0,0 +1,71 @@ +package app.sportahub.userservice.service.kafka.consumer; + +import java.time.Instant; +import java.util.UUID; + +import org.apache.kafka.clients.producer.ProducerRecord; +import org.apache.kafka.common.header.internals.RecordHeader; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.support.KafkaHeaders; +import org.springframework.messaging.handler.annotation.Header; +import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.stereotype.Service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import app.sportahub.kafka.events.BaseEvent; +import app.sportahub.kafka.events.user.UserEvent; +import app.sportahub.kafka.events.user.UserFetchEvent; +import app.sportahub.kafka.events.user.UserFetchedEvent; +import app.sportahub.userservice.model.user.User; +import app.sportahub.userservice.repository.user.UserRepository; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Service +@Slf4j +@AllArgsConstructor +public class OrchestrationServiceConsumerImpl { + + private final KafkaTemplate kafkaTemplate; + private final UserRepository userRepository; + + @KafkaListener(topics = UserEvent.FETCH_TOPIC, groupId = "OrchestrationServiceKafkaConsumer") + public void handleEventsByUserRequest( + @Payload UserFetchEvent fetchEvent, + @Header(KafkaHeaders.CORRELATION_ID) byte[] correlationId) { + + log.info("OrchestrationServiceConsumerImpl::handleEventsByUserRequest: received fetch request for user with id: {}", fetchEvent.getUserId()); + + String userId = fetchEvent.getUserId(); + User user = userRepository.findById(userId).orElse(null); + log.info("OrchestrationServiceConsumerImpl::handleEventsByUserRequest: fetched user {}", user); + + ObjectMapper objectMapper = new ObjectMapper(); + String userJsonString; + try { + userJsonString = objectMapper.writeValueAsString(user); + } catch (JsonProcessingException e) { + log.error("Error serializing user object to JSON", e); + userJsonString = "{}"; // Default to empty JSON object + } + + log.info("OrchestrationServiceConsumerImpl::handleEventsByUserRequest: response: {}", userJsonString); + + BaseEvent responseBaseEvent = new BaseEvent( + UUID.randomUUID().toString(), + "response", + "user-service", + Instant.now(), + fetchEvent.getBaseEvent().getCorrelationId() + ); + + UserFetchedEvent fetchedEvent = new UserFetchedEvent(responseBaseEvent, userJsonString); + ProducerRecord responseRecord = new ProducerRecord<>(UserEvent.FETCHED_TOPIC, fetchedEvent); + responseRecord.headers().add(new RecordHeader(KafkaHeaders.CORRELATION_ID, correlationId)); + kafkaTemplate.send(responseRecord); + log.info("OrchestrationServiceConsumerImpl::handleEventsByUserRequest: sent user with id: {}", fetchEvent.getUserId()); + } +} diff --git a/libs/kafka-events/build.gradle b/libs/kafka-events/build.gradle index d0955090d..dff47bd84 100644 --- a/libs/kafka-events/build.gradle +++ b/libs/kafka-events/build.gradle @@ -4,7 +4,7 @@ plugins { } group = 'app.sportahub' -version = '1.0.8' +version = '1.0.9' publishing { repositories { diff --git a/libs/kafka-events/gradle/wrapper/gradle-wrapper.jar b/libs/kafka-events/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..e6441136f Binary files /dev/null and b/libs/kafka-events/gradle/wrapper/gradle-wrapper.jar differ diff --git a/libs/kafka-events/gradle/wrapper/gradle-wrapper.properties b/libs/kafka-events/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..a4413138c --- /dev/null +++ b/libs/kafka-events/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/libs/kafka-events/gradlew b/libs/kafka-events/gradlew index f3b75f3b0..b740cf133 100755 --- a/libs/kafka-events/gradlew +++ b/libs/kafka-events/gradlew @@ -15,8 +15,6 @@ # See the License for the specific language governing permissions and # limitations under the License. # -# SPDX-License-Identifier: Apache-2.0 -# ############################################################################## # @@ -86,7 +84,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/libs/kafka-events/gradlew.bat b/libs/kafka-events/gradlew.bat index 9d21a2183..25da30dbd 100644 --- a/libs/kafka-events/gradlew.bat +++ b/libs/kafka-events/gradlew.bat @@ -13,8 +13,6 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## diff --git a/libs/kafka-events/src/main/java/app/sportahub/kafka/events/user/UserEvent.java b/libs/kafka-events/src/main/java/app/sportahub/kafka/events/user/UserEvent.java new file mode 100644 index 000000000..08fad101f --- /dev/null +++ b/libs/kafka-events/src/main/java/app/sportahub/kafka/events/user/UserEvent.java @@ -0,0 +1,8 @@ +package app.sportahub.kafka.events.user; + +public class UserEvent { + public static final String REQUEST_TOPIC = "user.request"; + public static final String RESPONSE_TOPIC = "user.response"; + public static final String FETCH_TOPIC = "user.fetch"; + public static final String FETCHED_TOPIC = "user.fetched"; +} diff --git a/libs/kafka-events/src/main/java/app/sportahub/kafka/events/user/UserFetchEvent.java b/libs/kafka-events/src/main/java/app/sportahub/kafka/events/user/UserFetchEvent.java new file mode 100644 index 000000000..9f40e11aa --- /dev/null +++ b/libs/kafka-events/src/main/java/app/sportahub/kafka/events/user/UserFetchEvent.java @@ -0,0 +1,24 @@ +package app.sportahub.kafka.events.user; + +import app.sportahub.kafka.events.BaseEvent; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.lang.String; + + +public class UserFetchEvent { + private final BaseEvent baseEvent; + private final String userId; + + @JsonCreator + public UserFetchEvent( + @JsonProperty("baseEvent") BaseEvent baseEvent, + @JsonProperty("userId") String userId) + { + this.baseEvent = baseEvent; + this.userId = userId; + } + public String getUserId() {return this.userId;} + public BaseEvent getBaseEvent() {return this.baseEvent;} +} diff --git a/libs/kafka-events/src/main/java/app/sportahub/kafka/events/user/UserFetchedEvent.java b/libs/kafka-events/src/main/java/app/sportahub/kafka/events/user/UserFetchedEvent.java new file mode 100644 index 000000000..2292f0ed4 --- /dev/null +++ b/libs/kafka-events/src/main/java/app/sportahub/kafka/events/user/UserFetchedEvent.java @@ -0,0 +1,24 @@ +package app.sportahub.kafka.events.user; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import app.sportahub.kafka.events.BaseEvent; + + +public class UserFetchedEvent { + private final BaseEvent baseEvent; + private final String user; + + @JsonCreator + public UserFetchedEvent( + @JsonProperty("baseEvent") BaseEvent baseEvent, + @JsonProperty("user") String user) + { + this.baseEvent = baseEvent; + this.user = user; + } + + public BaseEvent getBaseEvent() {return this.baseEvent;} + public String getUser() {return this.user;} +} diff --git a/libs/kafka-events/src/main/java/app/sportahub/kafka/events/user/UserRequestEvent.java b/libs/kafka-events/src/main/java/app/sportahub/kafka/events/user/UserRequestEvent.java new file mode 100644 index 000000000..307cfa2ff --- /dev/null +++ b/libs/kafka-events/src/main/java/app/sportahub/kafka/events/user/UserRequestEvent.java @@ -0,0 +1,23 @@ +package app.sportahub.kafka.events.user; + +import app.sportahub.kafka.events.BaseEvent; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.lang.String; + +public class UserRequestEvent { + private final BaseEvent baseEvent; + private final String userId; + + @JsonCreator + public UserRequestEvent( + @JsonProperty("baseEvent") BaseEvent baseEvent, + @JsonProperty("userId") String userId) + { + this.baseEvent = baseEvent; + this.userId = userId; + } + public String getUserId() {return this.userId;} + public BaseEvent getBaseEvent() {return this.baseEvent;} +} diff --git a/libs/kafka-events/src/main/java/app/sportahub/kafka/events/user/UserResponseEvent.java b/libs/kafka-events/src/main/java/app/sportahub/kafka/events/user/UserResponseEvent.java new file mode 100644 index 000000000..64aab76f7 --- /dev/null +++ b/libs/kafka-events/src/main/java/app/sportahub/kafka/events/user/UserResponseEvent.java @@ -0,0 +1,23 @@ +package app.sportahub.kafka.events.user; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import app.sportahub.kafka.events.BaseEvent; + + +public class UserResponseEvent { + private final BaseEvent baseEvent; + private final String user; + + @JsonCreator + public UserResponseEvent( + @JsonProperty("baseEvent") BaseEvent baseEvent, + @JsonProperty("user") String user) + { + this.baseEvent = baseEvent; + this.user = user; + } + public String getUser() {return this.user;} + public BaseEvent getBaseEvent() {return this.baseEvent;} +}