Skip to content

Commit

Permalink
spring boot upkeep
Browse files Browse the repository at this point in the history
  • Loading branch information
querwurzel committed Nov 25, 2023
1 parent ecebd3e commit ea37e93
Show file tree
Hide file tree
Showing 9 changed files with 32 additions and 51 deletions.
1 change: 1 addition & 0 deletions backend/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import com.github.binpastes.paste.api.model.SearchView;
import com.github.binpastes.paste.api.model.SearchView.SearchItemView;
import com.github.binpastes.paste.api.model.SingleView;
import com.github.binpastes.paste.domain.Paste;
import com.github.binpastes.paste.application.PasteService;
import com.github.binpastes.paste.domain.Paste;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
Expand All @@ -19,23 +19,13 @@
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
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.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.support.WebExchangeBindException;
import org.springframework.web.server.ResponseStatusException;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;

import static com.github.binpastes.paste.api.model.ListView.ListItemView;
Expand Down Expand Up @@ -64,22 +54,19 @@ public Mono<SingleView> findPaste(@PathVariable("pasteId") String pasteId, Serve
return;
}

if (paste.isPermanent() || isAfter(paste.getDateOfExpiry(), 5, ChronoUnit.MINUTES)) {
response.getHeaders().setCacheControl(CacheControl.maxAge(5, TimeUnit.MINUTES));
return;
var now = LocalDateTime.now();
if (paste.isPermanent() || paste.getDateOfExpiry().plusMinutes(5).isAfter(now)) {
response.getHeaders().setCacheControl(
CacheControl.maxAge(5, TimeUnit.MINUTES));
} else {
response.getHeaders().setCacheControl(
CacheControl.maxAge(Duration.between(now, paste.getDateOfExpiry())));
}

response.getHeaders().setCacheControl(
CacheControl.maxAge(Duration.between(LocalDateTime.now(), paste.getDateOfExpiry())));
})
.map(reference -> SingleView.of(reference, remoteAddress(request)))
.switchIfEmpty(Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND)));
}

private static boolean isAfter(LocalDateTime dateTime, long amount, ChronoUnit unit) {
return LocalDateTime.now().plus(amount, unit).isBefore(dateTime);
}

@GetMapping
public Mono<ListView> findPastes() {
return pasteService
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,7 @@ public String title() {
}

public String content() {
return StringUtils.hasText(content)
? content
: null;
return content;
}

public boolean isEncrypted() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,15 @@ public Mono<Paste> find(String id) {
}

private Mono<Paste> trackAccess(Paste paste) {
trackingService.trackView(paste.getId());

if (paste.isOneTime()) {
return pasteRepository
.save(paste.markAsExpired())
.doOnSuccess(deletedPaste -> log.info("OneTime paste {} viewed and burnt", deletedPaste.getId()))
.onErrorComplete();
.retry()
.doOnSuccess(deletedPaste -> log.info("OneTime paste {} viewed and burnt", deletedPaste.getId()));
}

trackingService.trackView(paste.getId());
return Mono.just(paste);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

import java.time.Instant;
import java.time.LocalDateTime;
Expand Down Expand Up @@ -49,9 +47,8 @@ public void receiveView(String pasteId, Instant timeViewed) {
pasteRepository
.findById(pasteId)
.flatMap(paste -> pasteRepository.save(paste.trackView(timestamp)))
.retry()
.doOnNext(paste -> log.debug("Tracked view on paste {}", paste.getId()))
.doOnError(OptimisticLockingFailureException.class, e -> messagingClient.sendMessage(pasteId, timeViewed))
.onErrorResume(OptimisticLockingFailureException.class, e -> Mono.empty())
.subscribe();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,17 @@
import static org.mockito.Mockito.timeout;
import static org.springframework.test.util.ReflectionTestUtils.setField;

@SpringBootTest
@SpringBootTest(properties = "logging.level.com.github.binpastes.paste.application.tracking=INFO")
@AutoConfigureWebTestClient
@DirtiesContext
class TrackingIT {

@Autowired
private WebTestClient webClient;

@Autowired
private PasteRepository pasteRepository;

@Autowired
private TrackingService trackingService;

@SpyBean
private PasteRepository pasteRepository;
@SpyBean
private MessagingClient messagingClient;

Expand Down Expand Up @@ -78,23 +75,23 @@ void trackConcurrentPasteViews() {
var intialPaste = givenPublicPaste();

Flux.fromStream(Stream.generate(intialPaste::getId))
.take(1000)
.take(500)
.doOnNext(trackingService::trackView)
// enforce a concurrent update
// simulate a concurrent update
.doOnNext(id -> pasteRepository.findById(id)
.doOnNext(paste -> setField(paste, "remoteAddress", "concurrentUpdate"))
.flatMap(paste -> pasteRepository.save(paste))
.onErrorComplete() // ignore errors in test
.onErrorComplete()
.subscribe())
.subscribeOn(Schedulers.parallel())
.subscribe();

await().atMost(Duration.ofMinutes(1)).pollInterval(Duration.ofMillis(500)).until(
await().atMost(Duration.ofMinutes(1)).pollInterval(Duration.ofSeconds(1)).until(
() -> pasteRepository.findById(intialPaste.getId()).block().getViews(),
equalTo(1000L)
equalTo(500L)
);

Mockito.verify(messagingClient, atLeast(1001)).sendMessage(any(), any());
Mockito.verify(pasteRepository, atLeast(500 + 100 /* contention */)).save(eq(intialPaste));
}

@Test
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/SearchPastes/SearchPastes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ import styles from "./searchPastes.module.css";
type SearchPastesProps = {
term: string
pastes: Array<PasteSearchView>
onSearchEnter: (term: string) => void
onSearchPastes: (term: string) => void
}

const SearchPastes: Component<SearchPastesProps> = ({term, pastes, onSearchEnter}): JSX.Element => {
const SearchPastes: Component<SearchPastesProps> = ({term, pastes, onSearchPastes}): JSX.Element => {

let searchInput: HTMLInputElement;

function submitOnClick(e: Event) {
e.preventDefault();

if (searchInput.value?.length >= 3) {
onSearchEnter(searchInput.value);
onSearchPastes(searchInput.value);
}
}

Expand Down
4 changes: 2 additions & 2 deletions frontend/src/pages/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const Search: () => JSX.Element = () => {
return (searchTerm.q && searchTerm.q.length >= 3) ? searchTerm.q : null;
}

function onSearchEnter(term: string) {
function onSearchPastes(term: string) {
setSearchTerm({q: term})
}

Expand All @@ -32,7 +32,7 @@ const Search: () => JSX.Element = () => {
</Match>
<Match when={pastes.latest}>

<SearchPastes term={effectiveTerm()} pastes={pastes()} onSearchEnter={onSearchEnter} />
<SearchPastes term={effectiveTerm()} pastes={pastes()} onSearchPastes={onSearchPastes} />

</Match>
</Switch>
Expand Down
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.5</version>
<version>3.1.6</version>
<relativePath/><!-- lookup parent from repository -->
</parent>

Expand Down Expand Up @@ -43,7 +43,7 @@
<jacoco-maven-plugin.version>0.8.11</jacoco-maven-plugin.version>

<!-- 3rd-party library versions -->
<spring-boot-bom.version>3.1.5</spring-boot-bom.version>
<spring-boot-bom.version>3.1.6</spring-boot-bom.version>
<junixsocket.version>2.8.3</junixsocket.version>
<flyway.version>9.22.3</flyway.version>
<artemis.version>2.31.2</artemis.version>
Expand Down

0 comments on commit ea37e93

Please sign in to comment.