From 7232b3e9fdeeebf56dc0786fa3e496203f2a955f Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed Date: Sat, 1 Aug 2026 22:34:37 -0400 Subject: [PATCH 1/4] Add leaderboardId field to Achievement table (with backfill) --- ...derboard_id_field_to_Achievement_table.SQL | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 db/migration/V0077__Add_leaderboard_id_field_to_Achievement_table.SQL diff --git a/db/migration/V0077__Add_leaderboard_id_field_to_Achievement_table.SQL b/db/migration/V0077__Add_leaderboard_id_field_to_Achievement_table.SQL new file mode 100644 index 000000000..e84863078 --- /dev/null +++ b/db/migration/V0077__Add_leaderboard_id_field_to_Achievement_table.SQL @@ -0,0 +1,23 @@ +ALTER TABLE "Achievement" +ADD COLUMN "leaderboardId" UUID, +ADD CONSTRAINT "fk_leaderboard" FOREIGN KEY ("leaderboardId") REFERENCES "Leaderboard"(id); + +-- backfill +-- all current `Achievement.name` is either +-- " - - Place" (tag leaderboards) +-- " - Place" (global leaderboard) +DO $$ +BEGIN + CASE current_database() + WHEN 'codebloom-prod' THEN + UPDATE "Achievement" a + SET "leaderboardId" = l.id + FROM "Leaderboard" l + WHERE l.name = split_part(a.title, ' - ', 1); + ELSE + RAISE NOTICE 'Skipping prod only backfill: Current database is %', current_database(); + END CASE; +END $$; + +ALTER TABLE "Achievement" +ALTER COLUMN "leaderboardId" SET NOT NULL; From bd236caf6527de324c5c2cc9514ad5ed97b56089 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed Date: Sat, 1 Aug 2026 23:23:53 -0400 Subject: [PATCH 2/4] fix mock data --- ...t_mock_achievements_for_achievement_table.SQL | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/db/repeated/R__Mock_V0023_Insert_mock_achievements_for_achievement_table.SQL b/db/repeated/R__Mock_V0023_Insert_mock_achievements_for_achievement_table.SQL index 493cc9e5b..428494eb9 100644 --- a/db/repeated/R__Mock_V0023_Insert_mock_achievements_for_achievement_table.SQL +++ b/db/repeated/R__Mock_V0023_Insert_mock_achievements_for_achievement_table.SQL @@ -1,31 +1,33 @@ DO $$ BEGIN - INSERT INTO "Achievement" - (id, "userId", title, description, "isActive", place, leaderboard, "createdAt") + INSERT INTO "Achievement" + (id, "userId", title, description, "isActive", place, leaderboard, "leaderboardId", "createdAt") VALUES ( '265dd000-c310-11f0-8d3a-461b1b1abee9', '1717a2dc-ce77-4853-a620-82177799ad26', - 'October Overflow', + 'Not expired leaderboard', 'Achieved first place on the Patina Network leaderboard', true, 'ONE', 'Patina', + '39bc2def-669f-4383-8ea3-7202efd613f2', '2025-11-11 00:00:00+00' ); - INSERT INTO "Achievement" - (id, "userId", title, description, "isActive", place, leaderboard, "createdAt") + INSERT INTO "Achievement" + (id, "userId", title, description, "isActive", place, leaderboard, "leaderboardId", "createdAt") VALUES ( '365dd000-c310-11f0-8d3a-461b1b1abeea', '1717a2dc-ce77-4853-a620-82177799ad26', - 'auto november = nullptr;', + 'Expired leaderboard', 'Secured first place on the global leaderboard', true, 'ONE', NULL, + '8fc363b2-b5be-4f1a-9e0e-ea4844fc919c', '2025-12-06 00:00:00+00' ); -END $$; \ No newline at end of file +END $$; From 05b42726a089c998938cbfe7a02d6b4dec2765b8 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed Date: Sat, 1 Aug 2026 23:08:41 -0400 Subject: [PATCH 3/4] Update Achievement & AchievementDto to include leaderboardId --- .../db/models/achievements/Achievement.java | 3 +++ .../achievements/AchievementRepository.java | 2 ++ .../achievements/AchievementSqlRepository.java | 18 +++++++++++++----- .../common/dto/achievement/AchievementDto.java | 4 ++++ 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/models/achievements/Achievement.java b/src/main/java/org/patinanetwork/codebloom/common/db/models/achievements/Achievement.java index 950504952..70a09d814 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/models/achievements/Achievement.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/models/achievements/Achievement.java @@ -23,6 +23,9 @@ public class Achievement { @NotNullColumn private String userId; + @NotNullColumn + private String leaderboardId; + @NotNullColumn private AchievementPlaceEnum place; diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/repos/achievements/AchievementRepository.java b/src/main/java/org/patinanetwork/codebloom/common/db/repos/achievements/AchievementRepository.java index dbf7e7d23..9075c47cb 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/repos/achievements/AchievementRepository.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/repos/achievements/AchievementRepository.java @@ -15,6 +15,7 @@ public interface AchievementRepository { *
  • title *
  • description *
  • isActive + *
  • leaderboardId * */ void createAchievement(Achievement achievement); @@ -29,6 +30,7 @@ public interface AchievementRepository { *
  • description *
  • isActive *
  • deletedAt + *
  • leaderboardId * * * @return updated achievement if successful diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/repos/achievements/AchievementSqlRepository.java b/src/main/java/org/patinanetwork/codebloom/common/db/repos/achievements/AchievementSqlRepository.java index 09bffe052..554b8fd04 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/repos/achievements/AchievementSqlRepository.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/repos/achievements/AchievementSqlRepository.java @@ -26,6 +26,7 @@ public class AchievementSqlRepository implements AchievementRepository { var title = rs.getString("title"); var description = rs.getString("description"); var isActive = rs.getBoolean("isActive"); + var leaderboardId = rs.getString("leaderboardId"); var createdAt = StandardizedOffsetDateTime.normalize(rs.getObject("createdAt", OffsetDateTime.class)); OffsetDateTime deletedAt = StandardizedOffsetDateTime.normalize(rs.getObject("deletedAt", OffsetDateTime.class)); @@ -34,6 +35,7 @@ public class AchievementSqlRepository implements AchievementRepository { .userId(userId) .place(place) .leaderboard(leaderboard) + .leaderboardId(leaderboardId) .title(title) .description(description) .isActive(isActive) @@ -53,9 +55,9 @@ public void createAchievement(final Achievement achievement) { achievement.setId(UUID.randomUUID().toString()); String sql = """ INSERT INTO "Achievement" - (id, "userId", place, leaderboard, title, description, "isActive", "deletedAt") + (id, "userId", place, leaderboard, title, description, "isActive", "deletedAt", "leaderboardId") VALUES - (:id, :userId, :place, :leaderboard, :title, :description, :isActive, :deletedAt) + (:id, :userId, :place, :leaderboard, :title, :description, :isActive, :deletedAt, :leaderboardId) RETURNING "createdAt" """; @@ -71,6 +73,7 @@ public void createAchievement(final Achievement achievement) { .map(Enum::name) .orElse(null), Types.OTHER) + .param("leaderboardId", UUID.fromString(achievement.getLeaderboardId())) .param("title", achievement.getTitle()) .param("description", achievement.getDescription()) .param("isActive", achievement.isActive()) @@ -94,7 +97,8 @@ public Achievement updateAchievement(final Achievement achievement) { title = :title, description = :description, "isActive" = :isActive, - "deletedAt" = :deletedAt + "deletedAt" = :deletedAt, + "leaderboardId" = :leaderboardId WHERE id = :id """; @@ -108,10 +112,12 @@ public Achievement updateAchievement(final Achievement achievement) { .map(Enum::name) .orElse(null), Types.OTHER) + .param("leaderboardId", achievement.getLeaderboardId()) .param("title", achievement.getTitle()) .param("description", achievement.getDescription()) .param("isActive", achievement.isActive()) .param("deletedAt", achievement.getDeletedAt()) + .param("leaderboardId", UUID.fromString(achievement.getLeaderboardId())) .param("id", UUID.fromString(achievement.getId())) .update(); @@ -150,7 +156,8 @@ public Achievement getAchievementById(final String id) { description, "isActive", "createdAt", - "deletedAt" + "deletedAt", + "leaderboardId" FROM "Achievement" WHERE @@ -178,7 +185,8 @@ public List getAchievementsByUserId(final String userId) { description, "isActive", "createdAt", - "deletedAt" + "deletedAt", + "leaderboardId" FROM "Achievement" WHERE diff --git a/src/main/java/org/patinanetwork/codebloom/common/dto/achievement/AchievementDto.java b/src/main/java/org/patinanetwork/codebloom/common/dto/achievement/AchievementDto.java index 3d12f01fc..73f15a959 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/dto/achievement/AchievementDto.java +++ b/src/main/java/org/patinanetwork/codebloom/common/dto/achievement/AchievementDto.java @@ -30,6 +30,9 @@ public class AchievementDto { @Schema(requiredMode = Schema.RequiredMode.REQUIRED, nullable = true) private Tag leaderboard; + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + private String leaderboardId; + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) private String title; @@ -52,6 +55,7 @@ public static AchievementDto fromAchievement(final Achievement achievement) { .leaderboard(achievement.getLeaderboard()) .place(achievement.getPlace()) .title(achievement.getTitle()) + .leaderboardId(achievement.getLeaderboardId()) .description(achievement.getDescription()) .isActive(achievement.isActive()) .createdAt(achievement.getCreatedAt()) From f97db717b4cb61d79d743fbf01994006c4ca7c8d Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed Date: Sun, 2 Aug 2026 00:11:32 -0400 Subject: [PATCH 4/4] fix tests --- .../db/repos/achievements/AchievementRepositoryTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/test/java/org/patinanetwork/codebloom/common/db/repos/achievements/AchievementRepositoryTest.java b/src/test/java/org/patinanetwork/codebloom/common/db/repos/achievements/AchievementRepositoryTest.java index 690d76d0e..5c7935add 100644 --- a/src/test/java/org/patinanetwork/codebloom/common/db/repos/achievements/AchievementRepositoryTest.java +++ b/src/test/java/org/patinanetwork/codebloom/common/db/repos/achievements/AchievementRepositoryTest.java @@ -22,6 +22,8 @@ public class AchievementRepositoryTest extends BaseRepositoryTest { private Achievement testAchievement; private Achievement deletableAchievement; private String mockUserId = "ed3bfe18-e42a-467f-b4fa-07e8da4d2555"; + // Not expired leaderboard + private String mockLeaderboardId = "39bc2def-669f-4383-8ea3-7202efd613f2"; @Autowired public AchievementRepositoryTest(final AchievementRepository repo) { @@ -34,6 +36,7 @@ void createAchievement() { .userId(mockUserId) .place(AchievementPlaceEnum.ONE) .leaderboard(null) + .leaderboardId(mockLeaderboardId) .title("Test Achievement") .description("Integration test achievement") .isActive(true) @@ -78,6 +81,7 @@ void testUpdateAchievement() { .userId(testAchievement.getUserId()) .place(AchievementPlaceEnum.THREE) .leaderboard(Tag.Patina) + .leaderboardId(mockLeaderboardId) .title("Updated Title") .description("Updated Description") .isActive(false) @@ -101,6 +105,7 @@ void testDeleteAchievementById() { .userId(mockUserId) .place(AchievementPlaceEnum.ONE) .leaderboard(null) + .leaderboardId(mockLeaderboardId) .title("Deletable Achievement") .description("Should be deleted") .isActive(true)