Skip to content

853: create /leaderboard/current endpoint#863

Open
luoh00 wants to merge 2 commits intomainfrom
853
Open

853: create /leaderboard/current endpoint#863
luoh00 wants to merge 2 commits intomainfrom
853

Conversation

@luoh00
Copy link
Collaborator

@luoh00 luoh00 commented Mar 17, 2026

853

Description of changes

Added /leaderboard/current endpoint in admin route to update current leaderboard

Checklist before review

  • I have done a thorough self-review of the PR
  • Copilot has reviewed my latest changes, and all comments have been fixed and/or closed.
  • If I have made database changes, I have made sure I followed all the db repo rules listed in the wiki here. (check if no db changes)
  • All tests have passed
  • I have successfully deployed this PR to staging
  • I have done manual QA in both dev (and staging if possible) and attached screenshots below.

Screenshots

Dev

Screen.Recording.2026-03-17.at.7.45.33.PM.mov

Staging

@github-actions
Copy link
Contributor

Available PR Commands

  • /ai - Triggers all AI review commands at once
  • /review - AI review of the PR changes
  • /describe - AI-powered description of the PR
  • /improve - AI-powered suggestions
  • /deploy - Deploy to staging

See: https://github.com/tahminator/codebloom/wiki/CI-Commands

@github-actions
Copy link
Contributor

Title

853: create /leaderboard/current endpoint


PR Type

Enhancement


Description

  • Adds admin endpoint to update current leaderboard.

  • Introduces DTO for leaderboard update request validation.

  • Updates leaderboard name, expiration, and syntax language.

  • Modifies repository to persist leaderboard expiration.


Diagram Walkthrough

flowchart LR
  AdminController["AdminController"] -- "Handles PUT /leaderboard/current" --> EditLeaderboardBody["EditLeaderboardBody (Validation)"];
  EditLeaderboardBody -- "Validated data" --> AdminController;
  AdminController -- "Updates Leaderboard via" --> LeaderboardSqlRepository["LeaderboardSqlRepository"];
  LeaderboardSqlRepository -- "Persists changes" --> Database["Database"];
Loading

File Walkthrough

Relevant files
Enhancement
AdminController.java
Add endpoint for current leaderboard updates                         

src/main/java/org/patinanetwork/codebloom/api/admin/AdminController.java

  • Added a new PUT /leaderboard/current endpoint for admin users.
  • Implemented validation for admin session and the EditLeaderboardBody
    request.
  • Retrieves the current leaderboard, constructs an updated Leaderboard
    object, and persists changes.
  • Consolidated import statements for body classes and Spring
    annotations.
+28/-10 
EditLeaderboardBody.java
Introduce DTO for leaderboard update requests                       

src/main/java/org/patinanetwork/codebloom/api/admin/body/EditLeaderboardBody.java

  • Created a new DTO EditLeaderboardBody for updating leaderboard
    details.
  • Includes fields for name, shouldExpireBy, and
    syntaxHighlightingLanguage.
  • Implemented a validate() method to ensure name length, future
    shouldExpireBy date, and non-empty syntaxHighlightingLanguage.
+51/-0   
LeaderboardSqlRepository.java
Update SQL to include leaderboard expiration                         

src/main/java/org/patinanetwork/codebloom/common/db/repos/leaderboard/LeaderboardSqlRepository.java

  • Modified the updateLeaderboard SQL query to include the shouldExpireBy
    field.
  • Updated the NamedPreparedStatement to bind the shouldExpireBy
    parameter.
+2/-0     

@github-actions
Copy link
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

**🎫 Ticket compliance analysis **

⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Missing Annotation

The EditLeaderboardBody class is missing the @Jacksonized annotation. According to the "Database Repository Best Practices" section, this annotation is required for classes that are deserialized from JSON into Java objects, which applies to request bodies.

@Getter
@Builder
@AllArgsConstructor
@ToString
public class EditLeaderboardBody {
Error Handling

The editLeaderboardBody.validate() method throws a ValidationException. If there isn't a global exception handler configured to map ValidationException to an appropriate HTTP status code (e.g., 400 Bad Request), this could result in a generic 500 Internal Server Error for invalid input.

editLeaderboardBody.validate();
Edge Case Handling

If no current leaderboard is found (i.e., leaderboardRepository.getRecentLeaderboardMetadata() returns an empty Optional), the endpoint still returns a 200 OK success response, even though no update occurred. It might be more appropriate to return a 404 Not Found or a more specific message indicating that no leaderboard was available to update.

    Optional<Leaderboard> currentLeaderboard = leaderboardRepository.getRecentLeaderboardMetadata();
    currentLeaderboard.ifPresent(lb -> {
        OffsetDateTime shouldExpireBy =
                StandardizedOffsetDateTime.normalize(editLeaderboardBody.getShouldExpireBy());

        Leaderboard updated = Leaderboard.builder()
                .name(editLeaderboardBody.getName())
                .deletedAt(lb.getDeletedAt())
                .createdAt(lb.getCreatedAt())
                .shouldExpireBy(Optional.of(shouldExpireBy).map(d -> d.toLocalDateTime()))
                .syntaxHighlightingLanguage(Optional.of(editLeaderboardBody.getSyntaxHighlightingLanguage()))
                .id(lb.getId())
                .build();

        leaderboardRepository.updateLeaderboard(updated);
    });

    return ResponseEntity.ok().body(ApiResponder.success("Leaderboard updated successfully", Empty.of()));
}

@github-actions
Copy link
Contributor

Title

853: create /leaderboard/current endpoint


PR Type

Enhancement


Description

  • Add admin endpoint to update current leaderboard

  • Introduce request body for leaderboard updates

  • Update leaderboard expiration and syntax language

  • Include unit tests for new functionality


Diagram Walkthrough

flowchart LR
  AdminController -- "PUT /leaderboard/current" --> EditLeaderboardBody["Validate Request Body"]
  EditLeaderboardBody --> LeaderboardRepository["Get & Update Leaderboard"]
  LeaderboardRepository --> AdminController["Return Success"]
Loading

File Walkthrough

Relevant files
Api endpoint
AdminController.java
Add PUT endpoint for current leaderboard                                 

src/main/java/org/patinanetwork/codebloom/api/admin/AdminController.java

  • Added @PutMapping annotation and wildcard import for admin body
    classes.
  • Implemented editCurrentLeaderboard endpoint to update the current
    leaderboard.
  • Validates admin session and EditLeaderboardBody content.
  • Updates leaderboard's name, expiration, and syntax highlighting
    language.
+28/-10 
New feature
EditLeaderboardBody.java
Define request body for leaderboard updates                           

src/main/java/org/patinanetwork/codebloom/api/admin/body/EditLeaderboardBody.java

  • Created new class EditLeaderboardBody for leaderboard update requests.
  • Defines fields for name, shouldExpireBy, and
    syntaxHighlightingLanguage.
  • Includes a validate() method to enforce constraints on name length and
    future expiration date.
+51/-0   
Database
LeaderboardSqlRepository.java
Update leaderboard with expiration date                                   

src/main/java/org/patinanetwork/codebloom/common/db/repos/leaderboard/LeaderboardSqlRepository.java

  • Modified the updateLeaderboard SQL query.
  • Added shouldExpireBy field to the UPDATE statement.
  • Included shouldExpireBy in the NamedPreparedStatement parameters.
+2/-0     
Tests
AdminControllerTest.java
Add test for current leaderboard update                                   

src/test/java/org/patinanetwork/codebloom/api/admin/AdminControllerTest.java

  • Added EditLeaderboardBody and StandardizedOffsetDateTime imports.
  • Implemented testEditCurrentLeaderboardSuccess unit test.
  • Mocks repository calls and verifies the editCurrentLeaderboard
    endpoint's success.
+21/-0   

@github-actions
Copy link
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

**🎫 Ticket compliance analysis **

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Missing Documentation

The new /leaderboard/current endpoint lacks Swagger annotations such as @Operation and @ApiResponse. These are crucial for auto-generating API documentation and ensuring clarity for API consumers.

@PutMapping("/leaderboard/current")
public ResponseEntity<ApiResponder<Empty>> editCurrentLeaderboard(
        @RequestBody final EditLeaderboardBody editLeaderboardBody, final HttpServletRequest request) {
    protector.validateAdminSession(request);
    editLeaderboardBody.validate();

    Optional<Leaderboard> currentLeaderboard = leaderboardRepository.getRecentLeaderboardMetadata();
    currentLeaderboard.ifPresent(lb -> {
        OffsetDateTime shouldExpireBy =
                StandardizedOffsetDateTime.normalize(editLeaderboardBody.getShouldExpireBy());

        Leaderboard updated = Leaderboard.builder()
                .name(editLeaderboardBody.getName())
                .deletedAt(lb.getDeletedAt())
                .createdAt(lb.getCreatedAt())
                .shouldExpireBy(Optional.ofNullable(shouldExpireBy).map(d -> d.toLocalDateTime()))
                .syntaxHighlightingLanguage(Optional.of(editLeaderboardBody.getSyntaxHighlightingLanguage()))
                .id(lb.getId())
                .build();

        leaderboardRepository.updateLeaderboard(updated);
    });

    return ResponseEntity.ok().body(ApiResponder.success("Leaderboard updated successfully", Empty.of()));
}
Incomplete Test

The testEditCurrentLeaderboardSuccess test does not mock the interactions with leaderboardRepository.getRecentLeaderboardMetadata() and leaderboardRepository.updateLeaderboard(). This means the test currently only verifies the admin session validation and the ApiResponder structure, but not the actual logic of fetching and updating the leaderboard within the controller.

@Test
void testEditCurrentLeaderboardSuccess() {
    OffsetDateTime date = StandardizedOffsetDateTime.normalize(OffsetDateTime.parse("4096-01-01T00:00:00Z"));

    EditLeaderboardBody body = EditLeaderboardBody.builder()
            .name("std::string name = new lb")
            .syntaxHighlightingLanguage("cpp")
            .shouldExpireBy(date)
            .build();

    ResponseEntity<ApiResponder<Empty>> response = adminController.editCurrentLeaderboard(body, request);
    assertEquals(HttpStatus.OK, response.getStatusCode());
    assertNotNull(response.getBody());
    assertTrue(response.getBody().isSuccess());
    assertEquals("Leaderboard updated successfully", response.getBody().getMessage());

    verify(protector).validateAdminSession(request);
}
Wildcard Imports

The PR introduces wildcard imports (import org.patinanetwork.codebloom.api.admin.body.*; and import org.springframework.web.bind.annotation.*;). While functional, it's generally recommended to use specific imports for better code readability and to prevent potential naming conflicts, aligning with the existing pattern in the file.

import org.patinanetwork.codebloom.api.admin.body.*;

Comment on lines +330 to +334
leaderboardRepository.updateLeaderboard(updated);
});

return ResponseEntity.ok().body(ApiResponder.success("Leaderboard updated successfully", Empty.of()));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The editCurrentLeaderboard endpoint currently returns a success response even if no current leaderboard is found to update. This misrepresents the operation's outcome and is an edge case that needs proper error handling. Add a check for currentLeaderboard.isEmpty() and return an appropriate NOT_FOUND response if no leaderboard exists. [possible issue, importance: 9]

Suggested change
leaderboardRepository.updateLeaderboard(updated);
});
return ResponseEntity.ok().body(ApiResponder.success("Leaderboard updated successfully", Empty.of()));
}
leaderboardRepository.updateLeaderboard(updated);
});
if (currentLeaderboard.isEmpty()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "No current leaderboard found to update.");
}
return ResponseEntity.ok().body(ApiResponder.success("Leaderboard updated successfully", Empty.of()));
}

Comment on lines +323 to +328
.deletedAt(lb.getDeletedAt())
.createdAt(lb.getCreatedAt())
.shouldExpireBy(Optional.ofNullable(shouldExpireBy).map(d -> d.toLocalDateTime()))
.syntaxHighlightingLanguage(Optional.of(editLeaderboardBody.getSyntaxHighlightingLanguage()))
.id(lb.getId())
.build();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Converting OffsetDateTime to LocalDateTime for shouldExpireBy might lead to loss of timezone information if the database or Leaderboard model expects OffsetDateTime. Ensure consistency by passing OffsetDateTime directly to the builder, or confirm that LocalDateTime is the intended and correctly handled type throughout the persistence layer. Additionally, use Optional.ofNullable() for syntaxHighlightingLanguage to safely handle potentially null values. [possible issue, importance: 8]

Suggested change
.deletedAt(lb.getDeletedAt())
.createdAt(lb.getCreatedAt())
.shouldExpireBy(Optional.ofNullable(shouldExpireBy).map(d -> d.toLocalDateTime()))
.syntaxHighlightingLanguage(Optional.of(editLeaderboardBody.getSyntaxHighlightingLanguage()))
.id(lb.getId())
.build();
.deletedAt(lb.getDeletedAt())
.createdAt(lb.getCreatedAt())
.shouldExpireBy(Optional.ofNullable(shouldExpireBy)) // Pass OffsetDateTime directly
.syntaxHighlightingLanguage(Optional.ofNullable(editLeaderboardBody.getSyntaxHighlightingLanguage())) // Use ofNullable for safety
.id(lb.getId())
.build();

Comment on lines +26 to +39
var name = getName();
var expire = getShouldExpireBy();

if (Strings.isNullOrEmpty(name)) {
throw new ValidationException("Leaderboard name cannot be null or empty");
}

if (name.length() == 1) {
throw new ValidationException("Leaderboard name cannot have only 1 character");
}

if (name.length() > 512) {
throw new ValidationException("Leaderboard name cannot have more than 512 characters");
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The manual Strings.isNullOrEmpty(name) check is redundant as the @NotBlank annotation already handles null, empty, and whitespace-only strings. Remove this redundant check to streamline the validation logic. Consider adding validation for syntaxHighlightingLanguage if it has specific constraints or is a required field. [general, importance: 6]

Suggested change
var name = getName();
var expire = getShouldExpireBy();
if (Strings.isNullOrEmpty(name)) {
throw new ValidationException("Leaderboard name cannot be null or empty");
}
if (name.length() == 1) {
throw new ValidationException("Leaderboard name cannot have only 1 character");
}
if (name.length() > 512) {
throw new ValidationException("Leaderboard name cannot have more than 512 characters");
}
var name = getName();
var expire = getShouldExpireBy();
// @NotBlank already handles null/empty/whitespace for 'name'
if (name.length() == 1) {
throw new ValidationException("Leaderboard name cannot have only 1 character");
}
if (name.length() > 512) {
throw new ValidationException("Leaderboard name cannot have more than 512 characters");
}
// Add validation for syntaxHighlightingLanguage if required, e.g., using @NotBlank or a custom check.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant