diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 0353c8a..352b752 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -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
diff --git a/backend/pom.xml b/backend/pom.xml
index fd5f17a..204856b 100644
--- a/backend/pom.xml
+++ b/backend/pom.xml
@@ -79,6 +79,10 @@
h2
test
+
+ org.awaitility
+ awaitility
+
@@ -107,6 +111,18 @@
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+
+ integration-test
+ verify
+
+
+
+
org.apache.maven.plugins
maven-deploy-plugin
diff --git a/backend/src/main/java/com/github/binpastes/paste/api/PasteController.java b/backend/src/main/java/com/github/binpastes/paste/api/PasteController.java
index b094fe7..554540b 100644
--- a/backend/src/main/java/com/github/binpastes/paste/api/PasteController.java
+++ b/backend/src/main/java/com/github/binpastes/paste/api/PasteController.java
@@ -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);
@@ -49,23 +60,26 @@ public Mono 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 findPastes() {
return pasteService
@@ -83,7 +97,7 @@ public Mono 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))
@@ -129,5 +143,4 @@ private static String remoteAddress(ServerHttpRequest request) {
return request.getRemoteAddress().getAddress().getHostAddress();
}
-
}
diff --git a/backend/src/main/java/com/github/binpastes/paste/business/tracking/TrackingService.java b/backend/src/main/java/com/github/binpastes/paste/business/tracking/TrackingService.java
index d71240f..1382fac 100644
--- a/backend/src/main/java/com/github/binpastes/paste/business/tracking/TrackingService.java
+++ b/backend/src/main/java/com/github/binpastes/paste/business/tracking/TrackingService.java
@@ -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;
@@ -35,7 +34,7 @@ public TrackingService(
private void run() {
this.messagingClient
.receiveMessage()
- .doOnNext(this::receiveView)
+ .doOnNext(message -> receiveView(message.pasteId(), message.timeViewed()))
.repeat()
.subscribe();
}
@@ -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());
- }
}
diff --git a/backend/src/main/java/com/github/binpastes/paste/domain/Paste.java b/backend/src/main/java/com/github/binpastes/paste/domain/Paste.java
index 48592db..1252b6a 100644
--- a/backend/src/main/java/com/github/binpastes/paste/domain/Paste.java
+++ b/backend/src/main/java/com/github/binpastes/paste/domain/Paste.java
@@ -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 {
@@ -119,19 +120,19 @@ 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());
}
}
@@ -139,15 +140,19 @@ public boolean isErasable(String remoteAddress) {
}
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) {
@@ -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);
}
diff --git a/backend/src/main/java/com/github/binpastes/paste/domain/PasteService.java b/backend/src/main/java/com/github/binpastes/paste/domain/PasteService.java
index 0dd74f9..7ac8789 100644
--- a/backend/src/main/java/com/github/binpastes/paste/domain/PasteService.java
+++ b/backend/src/main/java/com/github/binpastes/paste/domain/PasteService.java
@@ -48,14 +48,14 @@ public Mono find(String id) {
}
private Mono 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);
}
@@ -66,7 +66,7 @@ public Flux findAll() {
public Flux 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);
diff --git a/backend/src/test/java/com/github/binpastes/BinPastesTests.java b/backend/src/test/java/com/github/binpastes/BinPastesTests.java
deleted file mode 100644
index fa27f05..0000000
--- a/backend/src/test/java/com/github/binpastes/BinPastesTests.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.github.binpastes;
-
-import org.junit.jupiter.api.Test;
-import org.springframework.boot.test.context.SpringBootTest;
-
-@SpringBootTest
-class BinPastesTests {
-
- @Test
- void contextLoads() {}
-
-}
diff --git a/backend/src/test/java/com/github/binpastes/paste/api/OneTimePastesIT.java b/backend/src/test/java/com/github/binpastes/paste/api/OneTimePastesIT.java
new file mode 100644
index 0000000..89a9e11
--- /dev/null
+++ b/backend/src/test/java/com/github/binpastes/paste/api/OneTimePastesIT.java
@@ -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();
+ }
+}
diff --git a/backend/src/test/java/com/github/binpastes/paste/api/PasteControllerTest.java b/backend/src/test/java/com/github/binpastes/paste/api/PasteControllerTest.java
index dabed7b..e7c585a 100644
--- a/backend/src/test/java/com/github/binpastes/paste/api/PasteControllerTest.java
+++ b/backend/src/test/java/com/github/binpastes/paste/api/PasteControllerTest.java
@@ -10,7 +10,6 @@
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.CacheControl;
-import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Flux;
@@ -45,7 +44,7 @@ void findUnknownPaste() {
.uri("/api/v1/paste/" + somePasteId)
.exchange()
.expectStatus().isNotFound()
- .expectHeader().doesNotExist(HttpHeaders.CACHE_CONTROL);
+ .expectHeader().cacheControl(CacheControl.empty());
}
@Test
@@ -69,7 +68,7 @@ void searchPastes() {
.uri("/api/v1/paste/search?term={term}", "foobar")
.exchange()
.expectStatus().isOk()
- .expectHeader().cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS))
+ .expectHeader().cacheControl(CacheControl.maxAge(1, TimeUnit.MINUTES))
.expectBody().jsonPath("pastes", emptyList());
}
@@ -92,20 +91,14 @@ void createPaste(Mono payload) {
.contentType(MediaType.APPLICATION_JSON)
.body(payload, String.class)
.exchange()
- .expectStatus().isBadRequest()
- .expectBody().consumeWith(System.out::println);
+ .expectStatus().isBadRequest();
}
private static Stream invalidPayloads() {
return Stream.of(
arguments(named("body is null", Mono.empty())),
arguments(named("body blank", Mono.just(""))),
- arguments(named("title too long", Mono.just("""
- {
- "title": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
- "content": "validContent"
- }
- """))),
+ arguments(named("title too long", Mono.just("{\"content\": \"validContent\", \"title\": " + "X".repeat(256 + 1) + "\"}"))),
arguments(named("content blank", Mono.just("""
{
"content": " ",
diff --git a/backend/src/test/java/com/github/binpastes/paste/api/PublicPastesIT.java b/backend/src/test/java/com/github/binpastes/paste/api/PublicPastesIT.java
new file mode 100644
index 0000000..f6f10b3
--- /dev/null
+++ b/backend/src/test/java/com/github/binpastes/paste/api/PublicPastesIT.java
@@ -0,0 +1,124 @@
+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.http.HttpHeaders;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.web.reactive.server.WebTestClient;
+
+import java.time.LocalDateTime;
+import java.util.concurrent.TimeUnit;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest
+@AutoConfigureWebTestClient
+@DirtiesContext
+class PublicPastesIT {
+
+ @Autowired
+ private WebTestClient webClient;
+
+ @Autowired
+ private PasteRepository pasteRepository;
+
+ @BeforeEach
+ void setUp() {
+ pasteRepository.deleteAll().block();
+ }
+
+ @Test
+ @DisplayName("GET /{pasteId} - public paste is cached")
+ void getPublicPaste() {
+ var paste = givenPublicPaste();
+
+ webClient.get()
+ .uri("/api/v1/paste/" + paste.getId())
+ .exchange()
+ .expectStatus().isOk()
+ .expectHeader().cacheControl(CacheControl.maxAge(5, TimeUnit.MINUTES));
+ }
+
+ @Test
+ @DisplayName("GET /{pasteId} - public paste is cached until expiry")
+ void getExpiringPublicPaste() {
+ var paste = givenPaste(Paste.newInstance(
+ "someTitle",
+ "Lorem ipsum dolor sit amet",
+ LocalDateTime.now().plusMinutes(5).minusSeconds(1), // under 5min remaining
+ false,
+ PasteExposure.PUBLIC,
+ "1.1.1.1"
+ ));
+
+ webClient.get()
+ .uri("/api/v1/paste/" + paste.getId())
+ .exchange()
+ .expectStatus().isOk()
+ .expectHeader().value(
+ HttpHeaders.CACHE_CONTROL,
+ (value) -> assertThat(Long.valueOf(value.replace("max-age=", "")))
+ .isLessThanOrEqualTo(TimeUnit.MINUTES.toSeconds(5)));
+ }
+
+ @Test
+ @DisplayName("GET / - public paste is listed")
+ void findAllPastes() {
+ givenPublicPaste();
+
+ assertThat(pasteRepository.count().block()).isOne();
+ webClient.get()
+ .uri("/api/v1/paste/")
+ .exchange()
+ .expectStatus().isOk()
+ .expectBody().jsonPath("$.pastes.length()", 1);
+ }
+
+ @Test
+ @DisplayName("DELETE /{pasteId} - public paste might be deleted")
+ void deletePublicPaste() {
+ var paste = givenPublicPaste();
+
+ webClient.get()
+ .uri("/api/v1/paste/" + paste.getId())
+ .exchange()
+ .expectStatus().isOk();
+
+ webClient.delete()
+ .uri("/api/v1/paste/" + paste.getId())
+ .header("X-Forwarded-For", "someAuthor")
+ .exchange()
+ .expectStatus().isNoContent()
+ .expectBody().isEmpty();
+
+ webClient.get()
+ .uri("/api/v1/paste/" + paste.getId())
+ .exchange()
+ .expectStatus().isNotFound();
+ }
+
+ private Paste givenPublicPaste() {
+ return givenPaste(
+ Paste.newInstance(
+ "someTitle",
+ "Lorem ipsum dolor sit amet",
+ null,
+ false,
+ PasteExposure.PUBLIC,
+ "someAuthor"
+ )
+ );
+ }
+
+ private Paste givenPaste(Paste paste) {
+ return pasteRepository.save(paste).block();
+ }
+}
diff --git a/backend/src/test/java/com/github/binpastes/paste/api/SearchPastesIT.java b/backend/src/test/java/com/github/binpastes/paste/api/SearchPastesIT.java
new file mode 100644
index 0000000..bcfdfe5
--- /dev/null
+++ b/backend/src/test/java/com/github/binpastes/paste/api/SearchPastesIT.java
@@ -0,0 +1,74 @@
+package com.github.binpastes.paste.api;
+
+import com.github.binpastes.paste.domain.Paste;
+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.test.annotation.DirtiesContext;
+import org.springframework.test.web.reactive.server.WebTestClient;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest
+@AutoConfigureWebTestClient
+@DirtiesContext
+class SearchPastesIT {
+
+ @Autowired
+ private WebTestClient webClient;
+
+ @Autowired
+ private PasteRepository pasteRepository;
+
+ @BeforeEach
+ void setUp() {
+ pasteRepository.deleteAll().block();
+ }
+
+ @Test
+ @DisplayName("GET /search - paste is found if text matches")
+ void findIfMatch() {
+ givenPublicPaste();
+
+ assertThat(pasteRepository.count().block()).isOne();
+ webClient.get()
+ .uri("/api/v1/paste/search?term={term}", "ipsum")
+ .exchange()
+ .expectStatus().isOk()
+ .expectBody().jsonPath("$.pastes.length()", 1);
+ }
+
+ @Test
+ @DisplayName("GET /search - paste is not found if no match")
+ void findNothing() {
+ givenPublicPaste();
+
+ assertThat(pasteRepository.count().block()).isOne();
+ webClient.get()
+ .uri("/api/v1/paste/search?term={term}", "foobar")
+ .exchange()
+ .expectStatus().isOk()
+ .expectBody().jsonPath("$.pastes.length()", 0);
+ }
+
+ private Paste givenPublicPaste() {
+ return givenPaste(
+ Paste.newInstance(
+ "someTitle",
+ "Lorem ipsum dolor sit amet",
+ null,
+ false,
+ Paste.PasteExposure.PUBLIC,
+ "someAuthor"
+ )
+ );
+ }
+
+ private Paste givenPaste(Paste paste) {
+ return pasteRepository.save(paste).block();
+ }
+}
diff --git a/backend/src/test/java/com/github/binpastes/paste/api/TrackingIT.java b/backend/src/test/java/com/github/binpastes/paste/api/TrackingIT.java
new file mode 100644
index 0000000..c91ed56
--- /dev/null
+++ b/backend/src/test/java/com/github/binpastes/paste/api/TrackingIT.java
@@ -0,0 +1,125 @@
+package com.github.binpastes.paste.api;
+
+import com.github.binpastes.paste.business.tracking.MessagingClient;
+import com.github.binpastes.paste.business.tracking.TrackingService;
+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.mockito.Mockito;
+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.boot.test.mock.mockito.SpyBean;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.web.reactive.server.WebTestClient;
+import reactor.core.publisher.Flux;
+import reactor.core.scheduler.Schedulers;
+
+import java.time.Duration;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.await;
+import static org.awaitility.Awaitility.waitAtMost;
+import static org.hamcrest.Matchers.equalTo;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.timeout;
+import static org.springframework.test.util.ReflectionTestUtils.setField;
+
+@SpringBootTest
+@AutoConfigureWebTestClient
+@DirtiesContext
+class TrackingIT {
+
+ @Autowired
+ private WebTestClient webClient;
+
+ @Autowired
+ private PasteRepository pasteRepository;
+
+ @Autowired
+ private TrackingService trackingService;
+
+ @SpyBean
+ private MessagingClient messagingClient;
+
+ @BeforeEach
+ void setUp() {
+ pasteRepository.deleteAll().block();
+ }
+
+ @Test
+ @DisplayName("tracking - paste is tracked on direct access eventually")
+ void trackPaste() {
+ var paste = givenPublicPaste();
+
+ assertThat(paste.getViews()).isZero();
+
+ webClient.get()
+ .uri("/api/v1/paste/" + paste.getId())
+ .exchange()
+ .expectStatus().isOk();
+
+ waitAtMost(Duration.ofSeconds(1)).until(
+ () -> pasteRepository.findById(paste.getId()).block().getViews(),
+ equalTo(1L)
+ );
+ }
+
+ @Test
+ @DisplayName("tracking - deal with concurrent tracking events")
+ void trackConcurrentPasteViews() {
+ var intialPaste = givenPublicPaste();
+
+ Flux.fromStream(Stream.generate(intialPaste::getId))
+ .take(1000)
+ .doOnNext(trackingService::trackView)
+ // enforce a concurrent update
+ .doOnNext(id -> pasteRepository.findById(id)
+ .doOnNext(paste -> setField(paste, "remoteAddress", "concurrentUpdate"))
+ .flatMap(paste -> pasteRepository.save(paste))
+ .onErrorComplete() // ignore errors in test
+ .subscribe())
+ .subscribeOn(Schedulers.parallel())
+ .subscribe();
+
+ await().atMost(Duration.ofMinutes(1)).pollInterval(Duration.ofMillis(500)).until(
+ () -> pasteRepository.findById(intialPaste.getId()).block().getViews(),
+ equalTo(1000L)
+ );
+
+ Mockito.verify(messagingClient, atLeast(1001)).sendMessage(any(), any());
+ }
+
+ @Test
+ @DisplayName("tracking - unknown paste is not tracked indefinitely")
+ void trackUnknownPaste() {
+ trackingService.trackView("4711");
+
+ Mockito.verify(messagingClient).sendMessage(eq("4711"), any());
+ Mockito.verify(messagingClient, timeout(TimeUnit.SECONDS.toMillis(1))).receiveMessage();
+ }
+
+ private Paste givenPublicPaste() {
+ return givenPaste(
+ Paste.newInstance(
+ "someTitle",
+ "Lorem ipsum dolor sit amet",
+ null,
+ false,
+ PasteExposure.PUBLIC,
+ "someAuthor"
+ )
+ );
+ }
+
+ private Paste givenPaste(Paste paste) {
+ return pasteRepository.save(paste).block();
+ }
+}
diff --git a/backend/src/test/java/com/github/binpastes/paste/api/UnlistedPastesIT.java b/backend/src/test/java/com/github/binpastes/paste/api/UnlistedPastesIT.java
new file mode 100644
index 0000000..adc5b85
--- /dev/null
+++ b/backend/src/test/java/com/github/binpastes/paste/api/UnlistedPastesIT.java
@@ -0,0 +1,137 @@
+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.http.HttpHeaders;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.web.reactive.server.WebTestClient;
+
+import java.time.LocalDateTime;
+import java.util.concurrent.TimeUnit;
+
+import static java.util.Collections.emptyList;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest
+@AutoConfigureWebTestClient
+@DirtiesContext
+class UnlistedPastesIT {
+
+ @Autowired
+ private WebTestClient webClient;
+
+ @Autowired
+ private PasteRepository pasteRepository;
+
+ @BeforeEach
+ void setUp() {
+ pasteRepository.deleteAll().block();
+ }
+
+ @Test
+ @DisplayName("GET /{pasteId} - unlisted paste is cached")
+ void getUnlistedPaste() {
+ var unlistedPaste = givenUnlistedPaste();
+
+ webClient.get()
+ .uri("/api/v1/paste/" + unlistedPaste.getId())
+ .exchange()
+ .expectStatus().isOk()
+ .expectHeader().cacheControl(CacheControl.maxAge(5, TimeUnit.MINUTES));
+ }
+
+ @Test
+ @DisplayName("GET /{pasteId} - unlisted paste is cached only until expiry")
+ void getExpiringUnlistedPaste() {
+ var unlistedPaste = givenPaste(Paste.newInstance(
+ "someTitle",
+ "Lorem ipsum dolor sit amet",
+ LocalDateTime.now().plusMinutes(5).minusSeconds(1),
+ false,
+ PasteExposure.UNLISTED,
+ "1.1.1.1"
+ ));
+
+ webClient.get()
+ .uri("/api/v1/paste/" + unlistedPaste.getId())
+ .exchange()
+ .expectStatus().isOk()
+ .expectHeader().value(
+ HttpHeaders.CACHE_CONTROL,
+ (value) -> assertThat(Long.valueOf(value.replace("max-age=", "")))
+ .isLessThanOrEqualTo(TimeUnit.MINUTES.toSeconds(5)));
+ }
+
+ @Test
+ @DisplayName("GET / - unlisted paste is never listed")
+ void findAllPastes() {
+ givenUnlistedPaste();
+
+ assertThat(pasteRepository.count().block()).isOne();
+ webClient.get()
+ .uri("/api/v1/paste/")
+ .exchange()
+ .expectStatus().isOk()
+ .expectBody().jsonPath("pastes", emptyList());
+ }
+
+ @Test
+ @DisplayName("GET /search - unlisted paste cannot be searched for")
+ void searchAllPastes() {
+ givenUnlistedPaste();
+
+ 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} - unlisted paste might always be deleted")
+ void deleteUnlistedPaste() {
+ var unlistedPaste = givenUnlistedPaste();
+
+ webClient.get()
+ .uri("/api/v1/paste/" + unlistedPaste.getId())
+ .exchange()
+ .expectStatus().isOk();
+
+ webClient.delete()
+ .uri("/api/v1/paste/" + unlistedPaste.getId())
+ .exchange()
+ .expectStatus().isNoContent()
+ .expectBody().isEmpty();
+
+ webClient.get()
+ .uri("/api/v1/paste/" + unlistedPaste.getId())
+ .exchange()
+ .expectStatus().isNotFound();
+ }
+
+ private Paste givenUnlistedPaste() {
+ return givenPaste(
+ Paste.newInstance(
+ "someTitle",
+ "Lorem ipsum dolor sit amet",
+ null,
+ false,
+ PasteExposure.UNLISTED,
+ "1.1.1.1"
+ )
+ );
+ }
+
+ private Paste givenPaste(Paste paste) {
+ return pasteRepository.save(paste).block();
+ }
+}
diff --git a/frontend/package.json b/frontend/package.json
index 181c264..b08de69 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -19,17 +19,17 @@
},
"devDependencies": {
"@types/crypto-js": "^4.2.1",
- "pnpm": "^8.10.4",
- "typescript": "^5.2.2",
- "vite": "^4.5.0",
+ "pnpm": "^8.10.5",
+ "typescript": "^5.3.2",
+ "vite": "^5.0.2",
"vite-plugin-solid": "^2.7.2"
},
"dependencies": {
"@solidjs/router": "^0.9.1",
"crypto-js": "^4.2.0",
- "linkify-element": "^4.1.2",
- "linkifyjs": "^4.1.2",
+ "linkify-element": "^4.1.3",
+ "linkifyjs": "^4.1.3",
"mvp.css": "^1.14.0",
- "solid-js": "^1.8.5"
+ "solid-js": "^1.8.6"
}
}
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index 5a46cfb..82d574c 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -7,39 +7,39 @@ settings:
dependencies:
'@solidjs/router':
specifier: ^0.9.1
- version: 0.9.1(solid-js@1.8.5)
+ version: 0.9.1(solid-js@1.8.6)
crypto-js:
specifier: ^4.2.0
version: 4.2.0
linkify-element:
- specifier: ^4.1.2
- version: 4.1.2(linkifyjs@4.1.2)
+ specifier: ^4.1.3
+ version: 4.1.3(linkifyjs@4.1.3)
linkifyjs:
- specifier: ^4.1.2
- version: 4.1.2
+ specifier: ^4.1.3
+ version: 4.1.3
mvp.css:
specifier: ^1.14.0
version: 1.14.0
solid-js:
- specifier: ^1.8.5
- version: 1.8.5
+ specifier: ^1.8.6
+ version: 1.8.6
devDependencies:
'@types/crypto-js':
specifier: ^4.2.1
version: 4.2.1
pnpm:
- specifier: ^8.10.4
- version: 8.10.4
+ specifier: ^8.10.5
+ version: 8.10.5
typescript:
- specifier: ^5.2.2
- version: 5.2.2
+ specifier: ^5.3.2
+ version: 5.3.2
vite:
- specifier: ^4.5.0
- version: 4.5.0
+ specifier: ^5.0.2
+ version: 5.0.2
vite-plugin-solid:
specifier: ^2.7.2
- version: 2.7.2(solid-js@1.8.5)(vite@4.5.0)
+ version: 2.7.2(solid-js@1.8.6)(vite@5.0.2)
packages:
@@ -371,8 +371,8 @@ packages:
to-fast-properties: 2.0.0
dev: true
- /@esbuild/android-arm64@0.18.20:
- resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
+ /@esbuild/android-arm64@0.19.7:
+ resolution: {integrity: sha512-YEDcw5IT7hW3sFKZBkCAQaOCJQLONVcD4bOyTXMZz5fr66pTHnAet46XAtbXAkJRfIn2YVhdC6R9g4xa27jQ1w==}
engines: {node: '>=12'}
cpu: [arm64]
os: [android]
@@ -380,8 +380,8 @@ packages:
dev: true
optional: true
- /@esbuild/android-arm@0.18.20:
- resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==}
+ /@esbuild/android-arm@0.19.7:
+ resolution: {integrity: sha512-YGSPnndkcLo4PmVl2tKatEn+0mlVMr3yEpOOT0BeMria87PhvoJb5dg5f5Ft9fbCVgtAz4pWMzZVgSEGpDAlww==}
engines: {node: '>=12'}
cpu: [arm]
os: [android]
@@ -389,8 +389,8 @@ packages:
dev: true
optional: true
- /@esbuild/android-x64@0.18.20:
- resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==}
+ /@esbuild/android-x64@0.19.7:
+ resolution: {integrity: sha512-jhINx8DEjz68cChFvM72YzrqfwJuFbfvSxZAk4bebpngGfNNRm+zRl4rtT9oAX6N9b6gBcFaJHFew5Blf6CvUw==}
engines: {node: '>=12'}
cpu: [x64]
os: [android]
@@ -398,8 +398,8 @@ packages:
dev: true
optional: true
- /@esbuild/darwin-arm64@0.18.20:
- resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==}
+ /@esbuild/darwin-arm64@0.19.7:
+ resolution: {integrity: sha512-dr81gbmWN//3ZnBIm6YNCl4p3pjnabg1/ZVOgz2fJoUO1a3mq9WQ/1iuEluMs7mCL+Zwv7AY5e3g1hjXqQZ9Iw==}
engines: {node: '>=12'}
cpu: [arm64]
os: [darwin]
@@ -407,8 +407,8 @@ packages:
dev: true
optional: true
- /@esbuild/darwin-x64@0.18.20:
- resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==}
+ /@esbuild/darwin-x64@0.19.7:
+ resolution: {integrity: sha512-Lc0q5HouGlzQEwLkgEKnWcSazqr9l9OdV2HhVasWJzLKeOt0PLhHaUHuzb8s/UIya38DJDoUm74GToZ6Wc7NGQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [darwin]
@@ -416,8 +416,8 @@ packages:
dev: true
optional: true
- /@esbuild/freebsd-arm64@0.18.20:
- resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==}
+ /@esbuild/freebsd-arm64@0.19.7:
+ resolution: {integrity: sha512-+y2YsUr0CxDFF7GWiegWjGtTUF6gac2zFasfFkRJPkMAuMy9O7+2EH550VlqVdpEEchWMynkdhC9ZjtnMiHImQ==}
engines: {node: '>=12'}
cpu: [arm64]
os: [freebsd]
@@ -425,8 +425,8 @@ packages:
dev: true
optional: true
- /@esbuild/freebsd-x64@0.18.20:
- resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==}
+ /@esbuild/freebsd-x64@0.19.7:
+ resolution: {integrity: sha512-CdXOxIbIzPJmJhrpmJTLx+o35NoiKBIgOvmvT+jeSadYiWJn0vFKsl+0bSG/5lwjNHoIDEyMYc/GAPR9jxusTA==}
engines: {node: '>=12'}
cpu: [x64]
os: [freebsd]
@@ -434,8 +434,8 @@ packages:
dev: true
optional: true
- /@esbuild/linux-arm64@0.18.20:
- resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==}
+ /@esbuild/linux-arm64@0.19.7:
+ resolution: {integrity: sha512-inHqdOVCkUhHNvuQPT1oCB7cWz9qQ/Cz46xmVe0b7UXcuIJU3166aqSunsqkgSGMtUCWOZw3+KMwI6otINuC9g==}
engines: {node: '>=12'}
cpu: [arm64]
os: [linux]
@@ -443,8 +443,8 @@ packages:
dev: true
optional: true
- /@esbuild/linux-arm@0.18.20:
- resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==}
+ /@esbuild/linux-arm@0.19.7:
+ resolution: {integrity: sha512-Y+SCmWxsJOdQtjcBxoacn/pGW9HDZpwsoof0ttL+2vGcHokFlfqV666JpfLCSP2xLxFpF1lj7T3Ox3sr95YXww==}
engines: {node: '>=12'}
cpu: [arm]
os: [linux]
@@ -452,8 +452,8 @@ packages:
dev: true
optional: true
- /@esbuild/linux-ia32@0.18.20:
- resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==}
+ /@esbuild/linux-ia32@0.19.7:
+ resolution: {integrity: sha512-2BbiL7nLS5ZO96bxTQkdO0euGZIUQEUXMTrqLxKUmk/Y5pmrWU84f+CMJpM8+EHaBPfFSPnomEaQiG/+Gmh61g==}
engines: {node: '>=12'}
cpu: [ia32]
os: [linux]
@@ -461,8 +461,8 @@ packages:
dev: true
optional: true
- /@esbuild/linux-loong64@0.18.20:
- resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==}
+ /@esbuild/linux-loong64@0.19.7:
+ resolution: {integrity: sha512-BVFQla72KXv3yyTFCQXF7MORvpTo4uTA8FVFgmwVrqbB/4DsBFWilUm1i2Oq6zN36DOZKSVUTb16jbjedhfSHw==}
engines: {node: '>=12'}
cpu: [loong64]
os: [linux]
@@ -470,8 +470,8 @@ packages:
dev: true
optional: true
- /@esbuild/linux-mips64el@0.18.20:
- resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==}
+ /@esbuild/linux-mips64el@0.19.7:
+ resolution: {integrity: sha512-DzAYckIaK+pS31Q/rGpvUKu7M+5/t+jI+cdleDgUwbU7KdG2eC3SUbZHlo6Q4P1CfVKZ1lUERRFP8+q0ob9i2w==}
engines: {node: '>=12'}
cpu: [mips64el]
os: [linux]
@@ -479,8 +479,8 @@ packages:
dev: true
optional: true
- /@esbuild/linux-ppc64@0.18.20:
- resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==}
+ /@esbuild/linux-ppc64@0.19.7:
+ resolution: {integrity: sha512-JQ1p0SmUteNdUaaiRtyS59GkkfTW0Edo+e0O2sihnY4FoZLz5glpWUQEKMSzMhA430ctkylkS7+vn8ziuhUugQ==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [linux]
@@ -488,8 +488,8 @@ packages:
dev: true
optional: true
- /@esbuild/linux-riscv64@0.18.20:
- resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==}
+ /@esbuild/linux-riscv64@0.19.7:
+ resolution: {integrity: sha512-xGwVJ7eGhkprY/nB7L7MXysHduqjpzUl40+XoYDGC4UPLbnG+gsyS1wQPJ9lFPcxYAaDXbdRXd1ACs9AE9lxuw==}
engines: {node: '>=12'}
cpu: [riscv64]
os: [linux]
@@ -497,8 +497,8 @@ packages:
dev: true
optional: true
- /@esbuild/linux-s390x@0.18.20:
- resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==}
+ /@esbuild/linux-s390x@0.19.7:
+ resolution: {integrity: sha512-U8Rhki5PVU0L0nvk+E8FjkV8r4Lh4hVEb9duR6Zl21eIEYEwXz8RScj4LZWA2i3V70V4UHVgiqMpszXvG0Yqhg==}
engines: {node: '>=12'}
cpu: [s390x]
os: [linux]
@@ -506,8 +506,8 @@ packages:
dev: true
optional: true
- /@esbuild/linux-x64@0.18.20:
- resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==}
+ /@esbuild/linux-x64@0.19.7:
+ resolution: {integrity: sha512-ZYZopyLhm4mcoZXjFt25itRlocKlcazDVkB4AhioiL9hOWhDldU9n38g62fhOI4Pth6vp+Mrd5rFKxD0/S+7aQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [linux]
@@ -515,8 +515,8 @@ packages:
dev: true
optional: true
- /@esbuild/netbsd-x64@0.18.20:
- resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==}
+ /@esbuild/netbsd-x64@0.19.7:
+ resolution: {integrity: sha512-/yfjlsYmT1O3cum3J6cmGG16Fd5tqKMcg5D+sBYLaOQExheAJhqr8xOAEIuLo8JYkevmjM5zFD9rVs3VBcsjtQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [netbsd]
@@ -524,8 +524,8 @@ packages:
dev: true
optional: true
- /@esbuild/openbsd-x64@0.18.20:
- resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==}
+ /@esbuild/openbsd-x64@0.19.7:
+ resolution: {integrity: sha512-MYDFyV0EW1cTP46IgUJ38OnEY5TaXxjoDmwiTXPjezahQgZd+j3T55Ht8/Q9YXBM0+T9HJygrSRGV5QNF/YVDQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [openbsd]
@@ -533,8 +533,8 @@ packages:
dev: true
optional: true
- /@esbuild/sunos-x64@0.18.20:
- resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==}
+ /@esbuild/sunos-x64@0.19.7:
+ resolution: {integrity: sha512-JcPvgzf2NN/y6X3UUSqP6jSS06V0DZAV/8q0PjsZyGSXsIGcG110XsdmuWiHM+pno7/mJF6fjH5/vhUz/vA9fw==}
engines: {node: '>=12'}
cpu: [x64]
os: [sunos]
@@ -542,8 +542,8 @@ packages:
dev: true
optional: true
- /@esbuild/win32-arm64@0.18.20:
- resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==}
+ /@esbuild/win32-arm64@0.19.7:
+ resolution: {integrity: sha512-ZA0KSYti5w5toax5FpmfcAgu3ZNJxYSRm0AW/Dao5up0YV1hDVof1NvwLomjEN+3/GMtaWDI+CIyJOMTRSTdMw==}
engines: {node: '>=12'}
cpu: [arm64]
os: [win32]
@@ -551,8 +551,8 @@ packages:
dev: true
optional: true
- /@esbuild/win32-ia32@0.18.20:
- resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==}
+ /@esbuild/win32-ia32@0.19.7:
+ resolution: {integrity: sha512-CTOnijBKc5Jpk6/W9hQMMvJnsSYRYgveN6O75DTACCY18RA2nqka8dTZR+x/JqXCRiKk84+5+bRKXUSbbwsS0A==}
engines: {node: '>=12'}
cpu: [ia32]
os: [win32]
@@ -560,8 +560,8 @@ packages:
dev: true
optional: true
- /@esbuild/win32-x64@0.18.20:
- resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==}
+ /@esbuild/win32-x64@0.19.7:
+ resolution: {integrity: sha512-gRaP2sk6hc98N734luX4VpF318l3w+ofrtTu9j5L8EQXF+FzQKV6alCOHMVoJJHvVK/mGbwBXfOL1HETQu9IGQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [win32]
@@ -599,12 +599,108 @@ packages:
'@jridgewell/sourcemap-codec': 1.4.15
dev: true
- /@solidjs/router@0.9.1(solid-js@1.8.5):
+ /@rollup/rollup-android-arm-eabi@4.5.2:
+ resolution: {integrity: sha512-ee7BudTwwrglFYSc3UnqInDDjCLWHKrFmGNi4aK7jlEyg4CyPa1DCMrZfsN1O13YT76UFEqXz2CoN7BCGpUlJw==}
+ cpu: [arm]
+ os: [android]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-android-arm64@4.5.2:
+ resolution: {integrity: sha512-xOuhj9HHtn8128ir8veoQsBbAUBasDbHIBniYTEx02pAmu9EXL+ZjJqngnNEy6ZgZ4h1JwL33GMNu3yJL5Mzow==}
+ cpu: [arm64]
+ os: [android]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-darwin-arm64@4.5.2:
+ resolution: {integrity: sha512-NTGJWoL8bKyqyWFn9/RzSv4hQ4wTbaAv0lHHRwf4OnpiiP4P8W0jiXbm8Nc5BCXKmWAwuvJY82mcIU2TayC20g==}
+ cpu: [arm64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-darwin-x64@4.5.2:
+ resolution: {integrity: sha512-hlKqj7bpPvU15sZo4za14u185lpMzdwWLMc9raMqPK4wywt0wR23y1CaVQ4oAFXat3b5/gmRntyfpwWTKl+vvA==}
+ cpu: [x64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-arm-gnueabihf@4.5.2:
+ resolution: {integrity: sha512-7ZIZx8c3u+pfI0ohQsft/GywrXez0uR6dUP0JhBuCK3sFO5TfdLn/YApnVkvPxuTv3+YKPIZend9Mt7Cz6sS3Q==}
+ cpu: [arm]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-arm64-gnu@4.5.2:
+ resolution: {integrity: sha512-7Pk/5mO11JW/cH+a8lL/i0ZxmRGrbpYqN0VwO2DHhU+SJWWOH2zE1RAcPaj8KqiwC8DCDIJOSxjV9+9lLb6aeA==}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-arm64-musl@4.5.2:
+ resolution: {integrity: sha512-KrRnuG5phJx756e62wxvWH2e+TK84MP2IVuPwfge+GBvWqIUfVzFRn09TKruuQBXzZp52Vyma7FjMDkwlA9xpg==}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-x64-gnu@4.5.2:
+ resolution: {integrity: sha512-My+53GasPa2D2tU5dXiyHYwrELAUouSfkNlZ3bUKpI7btaztO5vpALEs3mvFjM7aKTvEbc7GQckuXeXIDKQ0fg==}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-x64-musl@4.5.2:
+ resolution: {integrity: sha512-/f0Q6Sc+Vw54Ws6N8fxaEe4R7at3b8pFyv+O/F2VaQ4hODUJcRUcCBJh6zuqtgQQt7w845VTkGLFgWZkP3tUoQ==}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-win32-arm64-msvc@4.5.2:
+ resolution: {integrity: sha512-NCKuuZWLht6zj7s6EIFef4BxCRX1GMr83S2W4HPCA0RnJ4iHE4FS1695q6Ewoa6A9nFjJe1//yUu0kgBU07Edw==}
+ cpu: [arm64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-win32-ia32-msvc@4.5.2:
+ resolution: {integrity: sha512-J5zL3riR4AOyU/J3M/i4k/zZ8eP1yT+nTmAKztCXJtnI36jYH0eepvob22mAQ/kLwfsK2TB6dbyVY1F8c/0H5A==}
+ cpu: [ia32]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-win32-x64-msvc@4.5.2:
+ resolution: {integrity: sha512-pL0RXRHuuGLhvs7ayX/SAHph1hrDPXOM5anyYUQXWJEENxw3nfHkzv8FfVlEVcLyKPAEgDRkd6RKZq2SMqS/yg==}
+ cpu: [x64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@solidjs/router@0.9.1(solid-js@1.8.6):
resolution: {integrity: sha512-kRY75piOQsyoH75E/RP6lr7uVGFCjeeCCCJx7Z2D1Vc6+I1yFQjLCvE+6agXGwqDoWi6vbETP1g7gmp/L1mNLg==}
peerDependencies:
solid-js: ^1.8.4
dependencies:
- solid-js: 1.8.5
+ solid-js: 1.8.6
dev: false
/@types/babel__core@7.20.3:
@@ -730,34 +826,34 @@ packages:
resolution: {integrity: sha512-dg5gj5qOgfZNkPNeyKBZQAQitIQ/xwfIDmEQJHCbXaD9ebTZxwJXUsDYcBlAvZGZLi+/354l35J1wkmP6CqYaw==}
dev: true
- /esbuild@0.18.20:
- resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==}
+ /esbuild@0.19.7:
+ resolution: {integrity: sha512-6brbTZVqxhqgbpqBR5MzErImcpA0SQdoKOkcWK/U30HtQxnokIpG3TX2r0IJqbFUzqLjhU/zC1S5ndgakObVCQ==}
engines: {node: '>=12'}
hasBin: true
requiresBuild: true
optionalDependencies:
- '@esbuild/android-arm': 0.18.20
- '@esbuild/android-arm64': 0.18.20
- '@esbuild/android-x64': 0.18.20
- '@esbuild/darwin-arm64': 0.18.20
- '@esbuild/darwin-x64': 0.18.20
- '@esbuild/freebsd-arm64': 0.18.20
- '@esbuild/freebsd-x64': 0.18.20
- '@esbuild/linux-arm': 0.18.20
- '@esbuild/linux-arm64': 0.18.20
- '@esbuild/linux-ia32': 0.18.20
- '@esbuild/linux-loong64': 0.18.20
- '@esbuild/linux-mips64el': 0.18.20
- '@esbuild/linux-ppc64': 0.18.20
- '@esbuild/linux-riscv64': 0.18.20
- '@esbuild/linux-s390x': 0.18.20
- '@esbuild/linux-x64': 0.18.20
- '@esbuild/netbsd-x64': 0.18.20
- '@esbuild/openbsd-x64': 0.18.20
- '@esbuild/sunos-x64': 0.18.20
- '@esbuild/win32-arm64': 0.18.20
- '@esbuild/win32-ia32': 0.18.20
- '@esbuild/win32-x64': 0.18.20
+ '@esbuild/android-arm': 0.19.7
+ '@esbuild/android-arm64': 0.19.7
+ '@esbuild/android-x64': 0.19.7
+ '@esbuild/darwin-arm64': 0.19.7
+ '@esbuild/darwin-x64': 0.19.7
+ '@esbuild/freebsd-arm64': 0.19.7
+ '@esbuild/freebsd-x64': 0.19.7
+ '@esbuild/linux-arm': 0.19.7
+ '@esbuild/linux-arm64': 0.19.7
+ '@esbuild/linux-ia32': 0.19.7
+ '@esbuild/linux-loong64': 0.19.7
+ '@esbuild/linux-mips64el': 0.19.7
+ '@esbuild/linux-ppc64': 0.19.7
+ '@esbuild/linux-riscv64': 0.19.7
+ '@esbuild/linux-s390x': 0.19.7
+ '@esbuild/linux-x64': 0.19.7
+ '@esbuild/netbsd-x64': 0.19.7
+ '@esbuild/openbsd-x64': 0.19.7
+ '@esbuild/sunos-x64': 0.19.7
+ '@esbuild/win32-arm64': 0.19.7
+ '@esbuild/win32-ia32': 0.19.7
+ '@esbuild/win32-x64': 0.19.7
dev: true
/escalade@3.1.1:
@@ -818,16 +914,16 @@ packages:
hasBin: true
dev: true
- /linkify-element@4.1.2(linkifyjs@4.1.2):
- resolution: {integrity: sha512-Pg/A44SXkF7Hwwss5WMX8pbve/u4Co2a5uChXzSTFD1bOSlzdxJMIdh0VuHN6Cvk6oplA2ImBHOtrBUsBn2l2Q==}
+ /linkify-element@4.1.3(linkifyjs@4.1.3):
+ resolution: {integrity: sha512-oUoG7BWaR3Q6kAKdlLi8slsu5rkVRxbiDVVlkpoL7vtidY5THggLzRHIBtmcj+tvMpcAUQomJApDxg0ub0qpdA==}
peerDependencies:
linkifyjs: ^4.0.0
dependencies:
- linkifyjs: 4.1.2
+ linkifyjs: 4.1.3
dev: false
- /linkifyjs@4.1.2:
- resolution: {integrity: sha512-1elJrH8MwUgr77Rgmx4JgB/nBgISYVoGossH6pAfCeHG+07TblTn6RWKx0MKozEMJU6NCFYHRih9M8ZtV3YZ+Q==}
+ /linkifyjs@4.1.3:
+ resolution: {integrity: sha512-auMesunaJ8yfkHvK4gfg1K0SaKX/6Wn9g2Aac/NwX+l5VdmFZzo/hdPGxEOETj+ryRa4/fiOPjeeKURSAJx1sg==}
dev: false
/lru-cache@5.1.1:
@@ -851,8 +947,8 @@ packages:
resolution: {integrity: sha512-f7F8dUw3kCA80z4myG4z5ymsrNK0oft2JQ1Fz0brpc+vIimJhpLk5B/rI44IQLgjpBDNIRzaZfwZUdndKs6j3w==}
dev: false
- /nanoid@3.3.6:
- resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==}
+ /nanoid@3.3.7:
+ resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
dev: true
@@ -865,8 +961,8 @@ packages:
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
dev: true
- /pnpm@8.10.4:
- resolution: {integrity: sha512-KH7bZKf5dYOIhK80/tKuRqbwbD9Khkd59TP0OES/Wr8yJqBKQnhQSrEYYRpxDW4XFLUnKhoxLy5vJTz1h7mlVA==}
+ /pnpm@8.10.5:
+ resolution: {integrity: sha512-nBYfQz2FVRxY8bOhCxjMPfcrWgLSyu5lZswFtvIK3e+UfnldkMOQM7+S3lUXfq1p2H9iqdqtyR56LjtY9JNToA==}
engines: {node: '>=16.14'}
hasBin: true
dev: true
@@ -875,16 +971,28 @@ packages:
resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
engines: {node: ^10 || ^12 || >=14}
dependencies:
- nanoid: 3.3.6
+ nanoid: 3.3.7
picocolors: 1.0.0
source-map-js: 1.0.2
dev: true
- /rollup@3.29.4:
- resolution: {integrity: sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==}
- engines: {node: '>=14.18.0', npm: '>=8.0.0'}
+ /rollup@4.5.2:
+ resolution: {integrity: sha512-CRK1uoROBfkcqrZKyaFcqCcZWNsvJ6yVYZkqTlRocZhO2s5yER6Z3f/QaYtO8RGyloPnmhwgzuPQpNGeK210xQ==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
optionalDependencies:
+ '@rollup/rollup-android-arm-eabi': 4.5.2
+ '@rollup/rollup-android-arm64': 4.5.2
+ '@rollup/rollup-darwin-arm64': 4.5.2
+ '@rollup/rollup-darwin-x64': 4.5.2
+ '@rollup/rollup-linux-arm-gnueabihf': 4.5.2
+ '@rollup/rollup-linux-arm64-gnu': 4.5.2
+ '@rollup/rollup-linux-arm64-musl': 4.5.2
+ '@rollup/rollup-linux-x64-gnu': 4.5.2
+ '@rollup/rollup-linux-x64-musl': 4.5.2
+ '@rollup/rollup-win32-arm64-msvc': 4.5.2
+ '@rollup/rollup-win32-ia32-msvc': 4.5.2
+ '@rollup/rollup-win32-x64-msvc': 4.5.2
fsevents: 2.3.3
dev: true
@@ -893,17 +1001,17 @@ packages:
hasBin: true
dev: true
- /seroval@0.12.0:
- resolution: {integrity: sha512-5qvk9WRORhvOdHVtDv6KEKrTDbG8O9nODDdETfQcJ0u9iaZJ6mAASg/nQgPzqtuV9cqln9msE7WNmD+sJomrIw==}
+ /seroval@0.14.1:
+ resolution: {integrity: sha512-ZlC9y1KVDhZFdEHLYZup1RjKDutyX1tt3ffOauqRbRURa2vRr2NU/bHuVEuNEqR3zE2uCU3WM6LqH6Oinc3tWg==}
engines: {node: '>=10'}
- /solid-js@1.8.5:
- resolution: {integrity: sha512-xvtJvzJzWbsn35oKFhW9kNwaxG1Z/YLMsDp4tLVcYZTMPzvzQ8vEZuyDQ6nt7xDArVgZJ7TUFrJUwrui/oq53A==}
+ /solid-js@1.8.6:
+ resolution: {integrity: sha512-yiH6ZfBBZ3xj/aU/PBpVKB+8r8WWp100NGF7k/Z0IrK9Y8Lv0jwvFiJY1cHdc6Tj7GqXArKnMBabM0m1k+LzkA==}
dependencies:
csstype: 3.1.2
- seroval: 0.12.0
+ seroval: 0.14.1
- /solid-refresh@0.5.3(solid-js@1.8.5):
+ /solid-refresh@0.5.3(solid-js@1.8.6):
resolution: {integrity: sha512-Otg5it5sjOdZbQZJnvo99TEBAr6J7PQ5AubZLNU6szZzg3RQQ5MX04oteBIIGDs0y2Qv8aXKm9e44V8z+UnFdw==}
peerDependencies:
solid-js: ^1.3
@@ -911,7 +1019,7 @@ packages:
'@babel/generator': 7.23.0
'@babel/helper-module-imports': 7.22.15
'@babel/types': 7.23.0
- solid-js: 1.8.5
+ solid-js: 1.8.6
dev: true
/source-map-js@1.0.2:
@@ -931,8 +1039,8 @@ packages:
engines: {node: '>=4'}
dev: true
- /typescript@5.2.2:
- resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==}
+ /typescript@5.3.2:
+ resolution: {integrity: sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==}
engines: {node: '>=14.17'}
hasBin: true
dev: true
@@ -952,7 +1060,7 @@ packages:
resolution: {integrity: sha512-hGdgQozCsQJMyfK5urgFcWEqsSSrK63Awe0t/IMR0bZ0QMtnuaiHzThW81guu3qx9abLi99NEuiaN6P9gVYsNg==}
dev: true
- /vite-plugin-solid@2.7.2(solid-js@1.8.5)(vite@4.5.0):
+ /vite-plugin-solid@2.7.2(solid-js@1.8.6)(vite@5.0.2):
resolution: {integrity: sha512-GV2SMLAibOoXe76i02AsjAg7sbm/0lngBlERvJKVN67HOrJsHcWgkt0R6sfGLDJuFkv2aBe14Zm4vJcNME+7zw==}
peerDependencies:
solid-js: ^1.7.2
@@ -963,20 +1071,20 @@ packages:
'@types/babel__core': 7.20.3
babel-preset-solid: 1.8.2(@babel/core@7.23.2)
merge-anything: 5.1.7
- solid-js: 1.8.5
- solid-refresh: 0.5.3(solid-js@1.8.5)
- vite: 4.5.0
- vitefu: 0.2.5(vite@4.5.0)
+ solid-js: 1.8.6
+ solid-refresh: 0.5.3(solid-js@1.8.6)
+ vite: 5.0.2
+ vitefu: 0.2.5(vite@5.0.2)
transitivePeerDependencies:
- supports-color
dev: true
- /vite@4.5.0:
- resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==}
- engines: {node: ^14.18.0 || >=16.0.0}
+ /vite@5.0.2:
+ resolution: {integrity: sha512-6CCq1CAJCNM1ya2ZZA7+jS2KgnhbzvxakmlIjN24cF/PXhRMzpM/z8QgsVJA/Dm5fWUWnVEsmtBoMhmerPxT0g==}
+ engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
peerDependencies:
- '@types/node': '>= 14'
+ '@types/node': ^18.0.0 || >=20.0.0
less: '*'
lightningcss: ^1.21.0
sass: '*'
@@ -999,14 +1107,14 @@ packages:
terser:
optional: true
dependencies:
- esbuild: 0.18.20
+ esbuild: 0.19.7
postcss: 8.4.31
- rollup: 3.29.4
+ rollup: 4.5.2
optionalDependencies:
fsevents: 2.3.3
dev: true
- /vitefu@0.2.5(vite@4.5.0):
+ /vitefu@0.2.5(vite@5.0.2):
resolution: {integrity: sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==}
peerDependencies:
vite: ^3.0.0 || ^4.0.0 || ^5.0.0
@@ -1014,7 +1122,7 @@ packages:
vite:
optional: true
dependencies:
- vite: 4.5.0
+ vite: 5.0.2
dev: true
/yallist@3.1.1: