Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bump the frontend group in /frontend with 6 updates #96

Merged
merged 2 commits into from
Nov 24, 2023
Merged
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
6 changes: 5 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ jobs:
java-version: '17'

- name: Build with Maven
run: mvn clean package
run: mvn clean verify

- name: Publish Test Report
if: success() || failure()
uses: scacap/action-surefire-report@v1

release:
runs-on: ubuntu-latest
Expand Down
16 changes: 16 additions & 0 deletions backend/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
</dependency>
</dependencies>

<build>
Expand Down Expand Up @@ -107,6 +111,18 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,35 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.CacheControl;
import org.springframework.http.HttpStatus;
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.*;
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.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;

@Validated
@RestController
@RequestMapping("/api/v1/paste")
@RequestMapping({"/api/v1/paste", "/api/v1/paste/"})
class PasteController {

private static final Logger log = LoggerFactory.getLogger(PasteController.class);
Expand All @@ -49,23 +60,26 @@ public Mono<SingleView> findPaste(@PathVariable("pasteId") String pasteId, Serve
.find(pasteId)
.doOnNext(paste -> {
if (paste.isOneTime()) {
response.getHeaders().add(HttpHeaders.CACHE_CONTROL, "no-store");
} else {
if (!paste.isPermanent()) {
var in5min = LocalDateTime.now().plusMinutes(5);
if (in5min.isAfter(paste.getDateOfExpiry())) {
response.getHeaders().add(HttpHeaders.CACHE_CONTROL, "max-age=" + Duration.between(LocalDateTime.now(), paste.getDateOfExpiry()).toSeconds());
return;
}
}

response.getHeaders().add(HttpHeaders.CACHE_CONTROL, "max-age=300");
response.getHeaders().setCacheControl(CacheControl.noStore());
return;
}

if (paste.isPermanent() || isAfter(paste.getDateOfExpiry(), 5, ChronoUnit.MINUTES)) {
response.getHeaders().setCacheControl(CacheControl.maxAge(5, TimeUnit.MINUTES));
return;
}

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 All @@ -83,7 +97,7 @@ public Mono<SearchView> searchPastes(
final String term,
final ServerHttpResponse response
) {
response.getHeaders().add(HttpHeaders.CACHE_CONTROL, "max-age=60");
response.getHeaders().setCacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS));
return pasteService
.findByFullText(term)
.map(paste -> SearchItemView.of(paste, term))
Expand Down Expand Up @@ -129,5 +143,4 @@ private static String remoteAddress(ServerHttpRequest request) {

return request.getRemoteAddress().getAddress().getHostAddress();
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.github.binpastes.paste.business.tracking;

import com.github.binpastes.paste.business.tracking.MessagingClient.Message;
import com.github.binpastes.paste.domain.PasteRepository;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
Expand Down Expand Up @@ -35,7 +34,7 @@ public TrackingService(
private void run() {
this.messagingClient
.receiveMessage()
.doOnNext(this::receiveView)
.doOnNext(message -> receiveView(message.pasteId(), message.timeViewed()))
.repeat()
.subscribe();
}
Expand All @@ -55,8 +54,4 @@ public void receiveView(String pasteId, Instant timeViewed) {
.onErrorResume(OptimisticLockingFailureException.class, e -> Mono.empty())
.subscribe();
}

private void receiveView(Message message) {
this.receiveView(message.pasteId(), message.timeViewed());
}
}
23 changes: 14 additions & 9 deletions backend/src/main/java/com/github/binpastes/paste/domain/Paste.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.util.Objects;

import static com.github.binpastes.paste.domain.Paste.PasteSchema;
import static java.util.Objects.isNull;

@Table(PasteSchema.TABLE_NAME)
public class Paste {
Expand Down Expand Up @@ -119,35 +120,39 @@ public boolean isOneTime() {
}

public boolean isPermanent() {
return this.dateOfExpiry == null;
return isNull(this.dateOfExpiry);
}

public boolean isErasable(String remoteAddress) {
if (this.isUnlisted() || this.isOneTime()) {
if (isUnlisted() || isOneTime()) {
return true;
}

if (this.isPublic()) {
final var createdBySameAuthor = Objects.equals(remoteAddress, this.getRemoteAddress());
if (isPublic()) {
final var createdBySameAuthor = Objects.equals(remoteAddress, getRemoteAddress());

if (createdBySameAuthor) {
return LocalDateTime.now().minusHours(1).isBefore(this.getDateCreated());
return LocalDateTime.now().minusHours(1).isBefore(getDateCreated());
}
}

return false;
}

public Paste trackView(LocalDateTime lastViewed) {
if (this.getLastViewed() == null || this.getLastViewed().isBefore(lastViewed)) {
if (isNull(getLastViewed()) || getLastViewed().isBefore(lastViewed)) {
setLastViewed(lastViewed);
}

return this.setViews(this.getViews() + 1);
return setViews(this.getViews() + 1);
}

public Paste markAsExpired() {
return this.setDateOfExpiry(LocalDateTime.now());
if (isNull(getDateOfExpiry())) {
return setDateOfExpiry(LocalDateTime.now());
}

throw new IllegalStateException("Paste is already expired");
}

protected Paste setId(final String id) {
Expand Down Expand Up @@ -209,7 +214,7 @@ protected Paste setViews(final long views) {
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (isNull(o) || getClass() != o.getClass()) return false;
Paste paste = (Paste) o;
return Objects.equals(id, paste.id);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@ 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()));
.doOnSuccess(deletedPaste -> log.info("OneTime paste {} viewed and burnt", deletedPaste.getId()))
.onErrorComplete();
}

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

Expand All @@ -66,7 +66,7 @@ public Flux<Paste> findAll() {
public Flux<Paste> findByFullText(String text) {
return pasteRepository
.searchAllLegitByFullText(text)
// TODO remove when fulltext search is finalised
// TODO remove when fulltext search is 'good enough'
.collectList()
.doOnSuccess(pastes -> log.info("Found {} pastes searching for: {}", pastes.size(), text))
.flatMapMany(Flux::fromIterable);
Expand Down
12 changes: 0 additions & 12 deletions backend/src/test/java/com/github/binpastes/BinPastesTests.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package com.github.binpastes.paste.api;

import com.github.binpastes.paste.domain.Paste;
import com.github.binpastes.paste.domain.Paste.PasteExposure;
import com.github.binpastes.paste.domain.PasteRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.CacheControl;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.web.reactive.server.WebTestClient;

import static java.util.Collections.emptyList;
import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest
@AutoConfigureWebTestClient
@DirtiesContext
class OneTimePastesIT {

@Autowired
private WebTestClient webClient;

@Autowired
private PasteRepository pasteRepository;

@BeforeEach
void setUp() {
pasteRepository.deleteAll().block();
}

@Test
@DisplayName("GET /{pasteId} - one-time paste is never cached")
void getOneTimePaste() {
var oneTimePaste = givenOneTimePaste();

webClient.get()
.uri("/api/v1/paste/" + oneTimePaste.getId())
.exchange()
.expectStatus().isOk()
.expectHeader().cacheControl(CacheControl.noStore());
}

@Test
@DisplayName("GET /{pasteId} - one-time paste is read-once")
void getOneTimePasteTwice() {
var oneTimePaste = givenOneTimePaste();

webClient.get()
.uri("/api/v1/paste/" + oneTimePaste.getId())
.exchange()
.expectStatus().isOk();

webClient.get()
.uri("/api/v1/paste/" + oneTimePaste.getId())
.exchange()
.expectStatus().isNotFound();
}

@Test
@DisplayName("GET / - one-time paste is never listed")
void findAllPastes() {
givenOneTimePaste();

assertThat(pasteRepository.count().block()).isOne();
webClient.get()
.uri("/api/v1/paste/")
.exchange()
.expectStatus().isOk()
.expectBody().jsonPath("pastes", emptyList());
}

@Test
@DisplayName("GET /search - one-time paste cannot be searched for")
void searchAllPastes() {
givenOneTimePaste();

assertThat(pasteRepository.count().block()).isOne();
webClient.get()
.uri("/api/v1/paste/search?term={term}", "ipsum")
.exchange()
.expectStatus().isOk()
.expectBody().jsonPath("pastes", emptyList());
}

@Test
@DisplayName("DELETE /{pasteId} - one-time paste might always be deleted before reading")
void deleteOneTimePaste() {
var oneTimePaste = givenOneTimePaste();

webClient.delete()
.uri("/api/v1/paste/" + oneTimePaste.getId())
.exchange()
.expectStatus().isNoContent()
.expectBody().isEmpty();

webClient.get()
.uri("/api/v1/paste/" + oneTimePaste.getId())
.exchange()
.expectStatus().isNotFound();
}

private Paste givenOneTimePaste() {
return givenPaste(
Paste.newInstance(
"someTitle",
"Lorem ipsum dolor sit amet",
null,
false,
PasteExposure.ONCE,
"1.1.1.1"
)
);
}

private Paste givenPaste(Paste paste) {
return pasteRepository.save(paste).block();
}
}
Loading
Loading