Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

@Getter
public enum AchievementType {
// Activation achievements
FIRST_TRIP("First Steps", "Create your first trip", 1),
PROFILE_COMPLETED("All Set Up", "Fill in your display name and bio", 1),

// Distance achievements (in kilometers)
DISTANCE_100KM("First Century", "Walk 100km in a single trip", 100),
DISTANCE_200KM("4daagse", "Walk 200km in a single trip", 200),
Expand All @@ -14,6 +18,7 @@ public enum AchievementType {
DISTANCE_2200KM("The Hobbit", "Walk from the Shire to Mordor (2200km)", 2200),

// Update achievements
UPDATES_1("Breaking the Ice", "Post your first update on a trip", 1),
UPDATES_10("Getting Started", "Post 10 updates on a single trip", 10),
UPDATES_50("Regular Reporter", "Post 50 updates on a single trip", 50),
UPDATES_100("Century Poster", "Post 100 updates on a single trip", 100),
Expand All @@ -25,11 +30,13 @@ public enum AchievementType {
DURATION_60_DAYS("Two Month Journey", "Trip lasting 60 days", 60),

// Social achievements - Followers
FOLLOWERS_1("First Fan", "Gain your first follower", 1),
FOLLOWERS_10("Popular Walker", "Reach 10 followers", 10),
FOLLOWERS_50("Influencer", "Reach 50 followers", 50),
FOLLOWERS_100("Community Leader", "Reach 100 followers", 100),

// Social achievements - Friends
FRIENDS_1("First Friend", "Make your first friend", 1),
FRIENDS_5("Making Friends", "Make 5 friends", 5),
FRIENDS_20("Social Butterfly", "Make 20 friends", 20),
FRIENDS_50("Friend Collector", "Make 50 friends", 50);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.tomassirio.wanderer.command.event.TripCreatedEvent;
import com.tomassirio.wanderer.command.repository.TripRepository;
import com.tomassirio.wanderer.command.service.AchievementService;
import com.tomassirio.wanderer.command.service.helper.TripEmbeddedObjectsInitializer;
import com.tomassirio.wanderer.commons.domain.Trip;
import com.tomassirio.wanderer.commons.domain.TripVisibility;
Expand All @@ -25,6 +26,7 @@ public class TripCreatedEventHandler implements EventHandler<TripCreatedEvent> {

private final TripEmbeddedObjectsInitializer embeddedObjectsInitializer;
private final TripRepository tripRepository;
private final AchievementService achievementCalculationService;

@Override
@EventListener
Expand Down Expand Up @@ -65,6 +67,7 @@ public void handle(TripCreatedEvent event) {
}

tripRepository.save(trip);
achievementCalculationService.checkAndUnlockSocialAchievements(event.getOwnerId());
log.info("Trip created and persisted: {}", event.getTripId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.tomassirio.wanderer.command.event.UserDetailsUpdatedEvent;
import com.tomassirio.wanderer.command.repository.UserRepository;
import com.tomassirio.wanderer.command.service.AchievementService;
import com.tomassirio.wanderer.commons.domain.UserDetails;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -16,6 +17,7 @@
public class UserDetailsUpdatedEventHandler implements EventHandler<UserDetailsUpdatedEvent> {

private final UserRepository userRepository;
private final AchievementService achievementCalculationService;

@Override
@EventListener
Expand All @@ -42,6 +44,9 @@ public void handle(UserDetailsUpdatedEvent event) {

user.setUserDetails(details);
log.info("User details updated for user: {}", event.getUserId());

achievementCalculationService.checkAndUnlockSocialAchievements(
event.getUserId());
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public class FollowerAchievementChecker implements SocialAchievementChecker {
@Override
public List<AchievementType> getApplicableTypes() {
return List.of(
AchievementType.FOLLOWERS_1,
AchievementType.FOLLOWERS_10,
AchievementType.FOLLOWERS_50,
AchievementType.FOLLOWERS_100);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ public class FriendAchievementChecker implements SocialAchievementChecker {
@Override
public List<AchievementType> getApplicableTypes() {
return List.of(
AchievementType.FRIENDS_5, AchievementType.FRIENDS_20, AchievementType.FRIENDS_50);
AchievementType.FRIENDS_1,
AchievementType.FRIENDS_5,
AchievementType.FRIENDS_20,
AchievementType.FRIENDS_50);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.tomassirio.wanderer.command.service.impl.checker;

import com.tomassirio.wanderer.command.repository.UserRepository;
import com.tomassirio.wanderer.commons.domain.AchievementType;
import com.tomassirio.wanderer.commons.domain.User;
import com.tomassirio.wanderer.commons.domain.UserDetails;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

/** Checks the profile-completed achievement (display name and bio both filled in). */
@Component
@RequiredArgsConstructor
public class ProfileCompletionAchievementChecker implements SocialAchievementChecker {

private final UserRepository userRepository;

@Override
public List<AchievementType> getApplicableTypes() {
return List.of(AchievementType.PROFILE_COMPLETED);
}

@Override
public double computeMetric(UUID userId) {
User user = userRepository.findById(userId).orElse(null);
if (user == null) {
return 0.0;
}

UserDetails details = user.getUserDetails();
boolean complete =
details != null
&& StringUtils.hasText(details.getDisplayName())
&& StringUtils.hasText(details.getBio());

return complete ? 1.0 : 0.0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.tomassirio.wanderer.command.service.impl.checker;

import com.tomassirio.wanderer.command.repository.TripRepository;
import com.tomassirio.wanderer.commons.domain.AchievementType;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

/** Checks achievements based on the number of trips a user has created. */
@Component
@RequiredArgsConstructor
public class TripCountAchievementChecker implements SocialAchievementChecker {

private final TripRepository tripRepository;

@Override
public List<AchievementType> getApplicableTypes() {
return List.of(AchievementType.FIRST_TRIP);
}

@Override
public double computeMetric(UUID userId) {
return tripRepository.findAllByUserId(userId).size();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public class UpdateCountAchievementChecker implements TripAchievementChecker {
@Override
public List<AchievementType> getApplicableTypes() {
return List.of(
AchievementType.UPDATES_1,
AchievementType.UPDATES_10,
AchievementType.UPDATES_50,
AchievementType.UPDATES_100);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
databaseChangeLog:
- changeSet:
id: 042-seed-activation-achievements-data
author: tomassirio
comment: "Seed achievements table with new activation AchievementType values"
preConditions:
- onFail: MARK_RAN
- tableExists:
tableName: achievements
changes:
- insert:
tableName: achievements
columns:
- column:
name: id
valueComputed: gen_random_uuid()
- column:
name: type
value: FIRST_TRIP
- column:
name: name
value: "First Steps"
- column:
name: description
value: "Create your first trip"
- column:
name: threshold_value
valueNumeric: 1
- column:
name: enabled
valueBoolean: true
- insert:
tableName: achievements
columns:
- column:
name: id
valueComputed: gen_random_uuid()
- column:
name: type
value: UPDATES_1
- column:
name: name
value: "Breaking the Ice"
- column:
name: description
value: "Post your first update on a trip"
- column:
name: threshold_value
valueNumeric: 1
- column:
name: enabled
valueBoolean: true
- insert:
tableName: achievements
columns:
- column:
name: id
valueComputed: gen_random_uuid()
- column:
name: type
value: FOLLOWERS_1
- column:
name: name
value: "First Fan"
- column:
name: description
value: "Gain your first follower"
- column:
name: threshold_value
valueNumeric: 1
- column:
name: enabled
valueBoolean: true
- insert:
tableName: achievements
columns:
- column:
name: id
valueComputed: gen_random_uuid()
- column:
name: type
value: FRIENDS_1
- column:
name: name
value: "First Friend"
- column:
name: description
value: "Make your first friend"
- column:
name: threshold_value
valueNumeric: 1
- column:
name: enabled
valueBoolean: true
- insert:
tableName: achievements
columns:
- column:
name: id
valueComputed: gen_random_uuid()
- column:
name: type
value: PROFILE_COMPLETED
- column:
name: name
value: "All Set Up"
- column:
name: description
value: "Fill in your display name and bio"
- column:
name: threshold_value
valueNumeric: 1
- column:
name: enabled
valueBoolean: true
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,5 @@ databaseChangeLog:
file: db/changelog/040-add-performance-indexes.yaml
- include:
file: db/changelog/041-add-update-count-to-trips.yaml
- include:
file: db/changelog/042-seed-activation-achievements-data.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import com.tomassirio.wanderer.command.event.TripCreatedEvent;
import com.tomassirio.wanderer.command.repository.TripRepository;
import com.tomassirio.wanderer.command.service.AchievementService;
import com.tomassirio.wanderer.command.service.helper.TripEmbeddedObjectsInitializer;
import com.tomassirio.wanderer.commons.domain.GeoLocation;
import com.tomassirio.wanderer.commons.domain.Trip;
Expand All @@ -29,6 +30,7 @@ class TripCreatedEventHandlerTest {

@Mock private TripEmbeddedObjectsInitializer embeddedObjectsInitializer;
@Mock private TripRepository tripRepository;
@Mock private AchievementService achievementCalculationService;

@InjectMocks private TripCreatedEventHandler handler;

Expand Down Expand Up @@ -73,6 +75,7 @@ void handle_whenEventWithMinimalData_shouldPersistTrip() {
assertThat(savedTrip.getTripSettings()).isEqualTo(tripSettings);
assertThat(savedTrip.getTripDetails()).isEqualTo(tripDetails);
assertThat(savedTrip.getEnabled()).isTrue();
verify(achievementCalculationService).checkAndUnlockSocialAchievements(ownerId);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package com.tomassirio.wanderer.command.handler;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import com.tomassirio.wanderer.command.event.UserDetailsUpdatedEvent;
import com.tomassirio.wanderer.command.repository.UserRepository;
import com.tomassirio.wanderer.command.service.AchievementService;
import com.tomassirio.wanderer.commons.domain.User;
import com.tomassirio.wanderer.commons.domain.UserDetails;
import java.util.Optional;
Expand All @@ -20,6 +23,7 @@
class UserDetailsUpdatedEventHandlerTest {

@Mock private UserRepository userRepository;
@Mock private AchievementService achievementCalculationService;

@InjectMocks private UserDetailsUpdatedEventHandler handler;

Expand Down Expand Up @@ -47,6 +51,7 @@ void handle_whenUserExists_shouldUpdateAllFields() {
// Then
assertThat(user.getUserDetails().getDisplayName()).isEqualTo("John Doe");
assertThat(user.getUserDetails().getBio()).isEqualTo("Walking the Camino");
verify(achievementCalculationService).checkAndUnlockSocialAchievements(userId);
}

@Test
Expand All @@ -73,6 +78,7 @@ void handle_whenPartialUpdate_shouldOnlyUpdateProvidedFields() {
// Then
assertThat(user.getUserDetails().getDisplayName()).isEqualTo("New Name");
assertThat(user.getUserDetails().getBio()).isEqualTo("Keep this bio");
verify(achievementCalculationService).checkAndUnlockSocialAchievements(userId);
}

@Test
Expand All @@ -98,6 +104,7 @@ void handle_whenUserDetailsIsNull_shouldCreateNewDetails() {
assertThat(user.getUserDetails()).isNotNull();
assertThat(user.getUserDetails().getDisplayName()).isEqualTo("John");
assertThat(user.getUserDetails().getBio()).isEqualTo("Bio text");
verify(achievementCalculationService).checkAndUnlockSocialAchievements(userId);
}

@Test
Expand All @@ -114,5 +121,6 @@ void handle_whenUserNotFound_shouldDoNothing() {

// Then
verify(userRepository).findById(userId);
verify(achievementCalculationService, never()).checkAndUnlockSocialAchievements(any());
}
}
Loading
Loading