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,36 @@ public ResponseEntity<ApiResponder<Empty>> deleteDiscordMessage(

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

@Operation(summary = "Edit current leaderboard")
@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.ofNullable(editLeaderboardBody.getSyntaxHighlightingLanguage()))
.id(lb.getId())
.build();

leaderboardRepository.updateLeaderboard(updated);
});

if (currentLeaderboard.isEmpty()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "No current leaderboard found");
}

return ResponseEntity.ok().body(ApiResponder.success("Leaderboard updated successfully", Empty.of()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.patinanetwork.codebloom.api.admin.body;

import com.google.common.base.Strings;
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
Copy link
Owner

Choose a reason for hiding this comment

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

nit: unnecessary; @Builder will generate the constructor it needs and make it private

@ToString
public class EditLeaderboardBody {

private String name;

private OffsetDateTime shouldExpireBy;

private String syntaxHighlightingLanguage;

public void validate() {
var leaderboardName = getName();
var expire = getShouldExpireBy();

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

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

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

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 @@ -29,6 +29,7 @@ public class LeaderboardSqlRepository implements LeaderboardRepository {

private DataSource ds;
private final UserRepository userRepository;
private static final String SHOULD_EXPIRE_BY = "shouldExpireBy";

public LeaderboardSqlRepository(final DataSource ds, final UserRepository userRepository) {
this.ds = ds;
Expand All @@ -42,7 +43,7 @@ private Leaderboard parseResultSetToLeaderboard(final ResultSet resultSet) throw
.deletedAt(
Optional.ofNullable(resultSet.getTimestamp("deletedAt")).map(Timestamp::toLocalDateTime))
.name(resultSet.getString("name"))
.shouldExpireBy(Optional.ofNullable(resultSet.getTimestamp("shouldExpireBy"))
.shouldExpireBy(Optional.ofNullable(resultSet.getTimestamp(SHOULD_EXPIRE_BY))
.map(Timestamp::toLocalDateTime))
.syntaxHighlightingLanguage(Optional.ofNullable(resultSet.getString("syntaxHighlightingLanguage")))
.build();
Expand Down Expand Up @@ -86,7 +87,7 @@ public void addNewLeaderboard(final Leaderboard leaderboard) {
NamedPreparedStatement stmt = new NamedPreparedStatement(conn, sql)) {
stmt.setObject("id", UUID.fromString(leaderboard.getId()));
stmt.setString("name", leaderboard.getName());
stmt.setObject("shouldExpireBy", leaderboard.getShouldExpireBy().orElse(null));
stmt.setObject(SHOULD_EXPIRE_BY, leaderboard.getShouldExpireBy().orElse(null));
stmt.setString(
"syntaxHighlightingLanguage",
leaderboard.getSyntaxHighlightingLanguage().orElse(null));
Expand Down Expand Up @@ -746,6 +747,7 @@ public boolean updateLeaderboard(final Leaderboard leaderboard) {
name = :name,
"createdAt" = :createdAt,
"deletedAt" = :deletedAt,
"shouldExpireBy" = :shouldExpireBy,
"syntaxHighlightingLanguage" = :syntaxHighlightingLanguage
WHERE id = :id
""";
Expand All @@ -755,6 +757,7 @@ public boolean updateLeaderboard(final Leaderboard leaderboard) {
stmt.setString("name", leaderboard.getName());
stmt.setObject("createdAt", leaderboard.getCreatedAt());
stmt.setObject("deletedAt", leaderboard.getDeletedAt().orElse(null));
stmt.setObject(SHOULD_EXPIRE_BY, 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,8 @@
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.patinanetwork.codebloom.utilities.exception.ValidationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.server.ResponseStatusException;
Expand Down Expand Up @@ -524,4 +527,106 @@ 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();

Leaderboard currentLeaderboard =
Leaderboard.builder().name("current leaderboard").id("123").build();
when(leaderboardRepository.getRecentLeaderboardMetadata()).thenReturn(Optional.of(currentLeaderboard));

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

@Test
void testEditCurrentLeaderboardFailureNoCurrentLeaderboard() {
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();

when(leaderboardRepository.getRecentLeaderboardMetadata()).thenReturn(Optional.empty());

try {
adminController.editCurrentLeaderboard(body, request);
fail("Exception expected");
} catch (ResponseStatusException e) {
assertNotNull(e);
assertTrue(e.getMessage().contains("No current leaderboard found"));
}
}

@Test
void testEditCurrentLeaderboardFailureNameTooShort() {
OffsetDateTime date = StandardizedOffsetDateTime.normalize(OffsetDateTime.parse("4096-01-01T00:00:00Z"));
EditLeaderboardBody body = EditLeaderboardBody.builder()
.name("1")
.syntaxHighlightingLanguage("cpp")
.shouldExpireBy(date)
.build();

try {
adminController.editCurrentLeaderboard(body, request);
fail("Exception expected");
} catch (ValidationException e) {
assertNotNull(e);
assertTrue(e.getMessage().contains("Leaderboard name cannot have only 1 character"));
assertInstanceOf(ValidationException.class, e);
}
}

@Test
void testEditCurrentLeaderboardFailureNameTooLong() {
OffsetDateTime date = StandardizedOffsetDateTime.normalize(OffsetDateTime.parse("4096-01-01T00:00:00Z"));
String longName = "a".repeat(513);
EditLeaderboardBody body = EditLeaderboardBody.builder()
.name(longName)
.syntaxHighlightingLanguage("cpp")
.shouldExpireBy(date)
.build();

try {
adminController.editCurrentLeaderboard(body, request);
fail("Exception expected");
} catch (ValidationException e) {
assertNotNull(e);
assertTrue(e.getMessage().contains("Leaderboard name cannot have more than 512 characters"));
assertInstanceOf(ValidationException.class, e);
}
}

@Test
void testEditCurrentLeaderboardFailurePastDate() {
OffsetDateTime date = StandardizedOffsetDateTime.normalize(OffsetDateTime.parse("2000-01-01T00:00:00Z"));
EditLeaderboardBody body = EditLeaderboardBody.builder()
.name("new lb name")
.syntaxHighlightingLanguage("cpp")
.shouldExpireBy(date)
.build();

try {
adminController.editCurrentLeaderboard(body, request);
fail("Exception expected");
} catch (ValidationException e) {
assertNotNull(e);
assertTrue(e.getMessage().contains("The expiration date must be in the future"));
assertInstanceOf(ValidationException.class, e);
}
}
}
Loading