Skip to content
Open
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 @@ -12,10 +12,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.patinanetwork.codebloom.api.admin.body.CreateAnnouncementBody;
import org.patinanetwork.codebloom.api.admin.body.DeleteAnnouncementBody;
import org.patinanetwork.codebloom.api.admin.body.NewLeaderboardBody;
import org.patinanetwork.codebloom.api.admin.body.UpdateAdminBody;
import org.patinanetwork.codebloom.api.admin.body.*;
import org.patinanetwork.codebloom.api.admin.body.jda.DeleteMessageBody;
import org.patinanetwork.codebloom.common.components.DiscordClubManager;
import org.patinanetwork.codebloom.common.components.LeaderboardManager;
Expand All @@ -38,12 +35,7 @@
import org.patinanetwork.codebloom.common.time.StandardizedOffsetDateTime;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;

@RestController
Expand Down Expand Up @@ -314,4 +306,30 @@ public ResponseEntity<ApiResponder<Empty>> deleteDiscordMessage(

return ResponseEntity.ok(ApiResponder.success("Discord Message successfully deleted", Empty.of()));
}

@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();
Comment on lines +323 to +328
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();


leaderboardRepository.updateLeaderboard(updated);
});

return ResponseEntity.ok().body(ApiResponder.success("Leaderboard updated successfully", Empty.of()));
}
Comment on lines +330 to +334
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()));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.patinanetwork.codebloom.api.admin.body;

import com.google.common.base.Strings;
import jakarta.validation.constraints.NotBlank;
import java.time.OffsetDateTime;
import lombok.*;
import lombok.extern.jackson.Jacksonized;
import org.patinanetwork.codebloom.common.time.StandardizedOffsetDateTime;
import org.patinanetwork.codebloom.utilities.exception.ValidationException;

@Getter
@Builder
@Jacksonized
@AllArgsConstructor
@ToString
public class EditLeaderboardBody {

@NotBlank
private String name;

private OffsetDateTime shouldExpireBy;

private String syntaxHighlightingLanguage;

public void validate() {
var name = getName();

Check warning on line 26 in src/main/java/org/patinanetwork/codebloom/api/admin/body/EditLeaderboardBody.java

View check run for this annotation

SonarQubeCloud / [codebloom_backend] SonarCloud Code Analysis

Rename "name" which hides the field declared at line 18.

See more on https://sonarcloud.io/project/issues?id=codebloom_backend&issues=AZz-OWfWjVpmGUc2gj_H&open=AZz-OWfWjVpmGUc2gj_H&pullRequest=863
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");
}
Comment on lines +26 to +39
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.


if (expire != null) {
OffsetDateTime nowWithOffset = StandardizedOffsetDateTime.now();
OffsetDateTime expiresAtWithOffset = StandardizedOffsetDateTime.normalize(expire);
boolean isInFuture = nowWithOffset.isBefore(expiresAtWithOffset);

if (!isInFuture) {
throw new ValidationException("The expiration date must be in the future");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
.deletedAt(
Optional.ofNullable(resultSet.getTimestamp("deletedAt")).map(Timestamp::toLocalDateTime))
.name(resultSet.getString("name"))
.shouldExpireBy(Optional.ofNullable(resultSet.getTimestamp("shouldExpireBy"))

Check failure on line 45 in src/main/java/org/patinanetwork/codebloom/common/db/repos/leaderboard/LeaderboardSqlRepository.java

View check run for this annotation

SonarQubeCloud / [codebloom_backend] SonarCloud Code Analysis

Define a constant instead of duplicating this literal "shouldExpireBy" 3 times.

See more on https://sonarcloud.io/project/issues?id=codebloom_backend&issues=AZz-OWhGjVpmGUc2gj_I&open=AZz-OWhGjVpmGUc2gj_I&pullRequest=863
.map(Timestamp::toLocalDateTime))
.syntaxHighlightingLanguage(Optional.ofNullable(resultSet.getString("syntaxHighlightingLanguage")))
.build();
Expand Down Expand Up @@ -746,6 +746,7 @@
name = :name,
"createdAt" = :createdAt,
"deletedAt" = :deletedAt,
"shouldExpireBy" = :shouldExpireBy,
"syntaxHighlightingLanguage" = :syntaxHighlightingLanguage
WHERE id = :id
""";
Expand All @@ -755,6 +756,7 @@
stmt.setString("name", leaderboard.getName());
stmt.setObject("createdAt", leaderboard.getCreatedAt());
stmt.setObject("deletedAt", leaderboard.getDeletedAt().orElse(null));
stmt.setObject("shouldExpireBy", leaderboard.getShouldExpireBy().orElse(null));
stmt.setObject("id", UUID.fromString(leaderboard.getId()));
stmt.setString(
"syntaxHighlightingLanguage",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.patinanetwork.codebloom.api.admin.body.DeleteAnnouncementBody;
import org.patinanetwork.codebloom.api.admin.body.EditLeaderboardBody;
import org.patinanetwork.codebloom.api.admin.body.NewLeaderboardBody;
import org.patinanetwork.codebloom.api.admin.body.jda.DeleteMessageBody;
import org.patinanetwork.codebloom.common.components.DiscordClubManager;
Expand All @@ -29,6 +30,7 @@
import org.patinanetwork.codebloom.common.dto.Empty;
import org.patinanetwork.codebloom.common.dto.question.QuestionWithUserDto;
import org.patinanetwork.codebloom.common.security.Protector;
import org.patinanetwork.codebloom.common.time.StandardizedOffsetDateTime;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.server.ResponseStatusException;
Expand Down Expand Up @@ -524,4 +526,23 @@ void testDeleteDiscordMessageSuccess() {

verify(protector).validateAdminSession(request);
}

@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);
}
}
Loading