caio.co/de/cerberus

Render recipe URLs with all search parameters

This patch switches from using crawlUrl() to using the /go/
route and appends all search parameters to said urls following
the idea outlined in b31fb8d995
Id
170eb16f126a18afb0557e582e10f6f7d677f2f1
Author
Caio
Commit time
2019-02-18T18:16:05-03:00

Modified pom.xml

@@ -39,7 +39,7
<resilience4j.version>0.13.2</resilience4j.version>
<chronicle.version>3.17.0</chronicle.version>
<jsoup.version>1.11.3</jsoup.version>
- <tablier.version>0.2.0</tablier.version>
+ <tablier.version>0.2.1</tablier.version>
</properties>

<dependencyManagement>

Modified src/main/java/co/caio/cerberus/boot/ModelView.java

@@ -1,9 +1,8
package co.caio.cerberus.boot;

import co.caio.cerberus.db.RecipeMetadata;
import co.caio.cerberus.db.RecipeMetadataDatabase;
import co.caio.cerberus.model.SearchQuery;
-import co.caio.cerberus.model.SearchQuery.RangedSpec;
import co.caio.cerberus.model.SearchResult;
import co.caio.cerberus.model.SearchResultRecipe;
import co.caio.tablier.model.ErrorInfo;
@@ -20,12 +19,14
import com.fizzed.rocker.RockerModel;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
+import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

@Component
@@ -48,7 +49,6
private static final PageInfo defaultErrorPage =
new PageInfo.Builder().title(ERROR_PAGE_TITLE).build();
private static final String DEFAULT_UNKNOWN_ERROR_SUBTITLE = "Unknown Error Cause";
- private static final RangedSpec unselectedRange = RangedSpec.of(0, 0);

private final int pageSize;
private final RecipeMetadataDatabase db;
@@ -72,6 +72,8
}
}

+ static final String GO_SLUG_ID_PATH = "/go/{slug}/{recipeId}";
+
RockerModel renderSearch(
SearchQuery query, SearchResult result, UriComponentsBuilder uriBuilder) {

@@ -86,13 +88,14
boolean isLastPage = query.offset() + pageSize >= result.totalHits();
int currentPage = (query.offset() / pageSize) + 1;

+ var recipeGoUriComponents =
+ uriBuilder.cloneBuilder().replacePath(GO_SLUG_ID_PATH).encode().build();
+
var searchBuilder =
new SearchResultsInfo.Builder()
.paginationStart(query.offset() + 1)
.paginationEnd(result.recipes().size() + query.offset())
.numMatching(result.totalHits());
-
- uriBuilder.fragment("results");

if (!isLastPage) {
searchBuilder.nextPageHref(
@@ -104,7 +107,7
uriBuilder.replaceQueryParam("page", currentPage - 1).build().toUriString());
}

- searchBuilder.recipes(renderRecipes(result.recipes()));
+ searchBuilder.recipes(renderRecipes(result.recipes(), recipeGoUriComponents));

// Sidebar links always lead to the first page
uriBuilder.replaceQueryParam("page");
@@ -132,11 +135,12
.count();
}

- private Iterable<RecipeInfo> renderRecipes(List<SearchResultRecipe> recipes) {
+ private Iterable<RecipeInfo> renderRecipes(
+ List<SearchResultRecipe> recipes, UriComponents uriComponents) {
var recipeIds = recipes.stream().map(SearchResultRecipe::recipeId).collect(Collectors.toList());
return db.findAllById(recipeIds)
.stream()
- .map(RecipeMetadataRecipeInfoAdapter::new)
+ .map(r -> new RecipeMetadataRecipeInfoAdapter(r, uriComponents))
.collect(Collectors.toList());
}

@@ -162,21 +166,27
return recipe;
}

- RockerModel renderSingleRecipe(long recipeId, String slug) {
+ RockerModel renderSingleRecipe(long recipeId, String slug, UriComponentsBuilder builder) {
var recipe = fetchRecipe(recipeId, slug);

return Recipe.template(
defaultSite,
new PageInfo.Builder().title(recipe.getName()).build(),
defaultSearchForm,
- new RecipeMetadataRecipeInfoAdapter(recipe));
+ new RecipeMetadataRecipeInfoAdapter(
+ recipe, builder.replacePath(GO_SLUG_ID_PATH).encode().build()));
}

static class RecipeMetadataRecipeInfoAdapter implements RecipeInfo {
private final RecipeMetadata metadata;
+ private final String goUrl;

- RecipeMetadataRecipeInfoAdapter(RecipeMetadata metadata) {
+ RecipeMetadataRecipeInfoAdapter(RecipeMetadata metadata, UriComponents uriComponents) {
this.metadata = metadata;
+ this.goUrl =
+ uriComponents
+ .expand(Map.of("slug", metadata.getSlug(), "recipeId", metadata.getRecipeId()))
+ .toUriString();
}

@Override
@@ -190,13 +200,13
}

@Override
- public String crawlUrl() {
- return metadata.getCrawlUrl();
+ public String goUrl() {
+ return goUrl;
}

@Override
- public String slug() {
- return String.format("%s/%d", metadata.getSlug(), metadata.getRecipeId());
+ public String infoUrl() {
+ return goUrl.replace("/go/", "/recipe/");
}

@Override

Modified src/main/java/co/caio/cerberus/boot/RequestHandler.java

@@ -64,7 +64,10
var recipeId = Long.parseLong(request.pathVariable("recipeId"));
return ServerResponse.ok()
.contentType(MediaType.TEXT_HTML)
- .body(BodyInserters.fromObject(modelView.renderSingleRecipe(recipeId, slug)));
+ .body(
+ BodyInserters.fromObject(
+ modelView.renderSingleRecipe(
+ recipeId, slug, UriComponentsBuilder.fromUri(request.uri()))));
}

public Mono<ServerResponse> go(ServerRequest request) {

Modified src/test/java/co/caio/cerberus/boot/ModelViewTest.java

@@ -138,7 +138,8

assertTrue(doc.title().startsWith(ModelView.SEARCH_PAGE_TITLE));

- assertTrue(doc.selectFirst("nav.pagination a.pagination-previous").attr("href").contains("page=1"));
+ assertTrue(
+ doc.selectFirst("nav.pagination a.pagination-previous").attr("href").contains("page=1"));
assertTrue(doc.selectFirst("nav.pagination a.pagination-next").attr("href").contains("page=3"));
}

@@ -158,7 +159,8

assertTrue(doc.title().startsWith(ModelView.SEARCH_PAGE_TITLE));

- assertTrue(doc.selectFirst("nav.pagination a.pagination-previous").attr("href").contains("page=1"));
+ assertTrue(
+ doc.selectFirst("nav.pagination a.pagination-previous").attr("href").contains("page=1"));
assertTrue(doc.selectFirst("nav.pagination a.pagination-next").attr("href").isEmpty());
}

@@ -204,19 +206,26
var recipe = Util.getSampleRecipes().limit(1).findFirst().orElseThrow();
assertThrows(
RecipeNotFoundError.class,
- () -> modelView.renderSingleRecipe(recipe.recipeId(), "incorrect slug"));
+ () ->
+ modelView.renderSingleRecipe(
+ recipe.recipeId(), "incorrect slug", UriComponentsBuilder.newInstance()));
}

@Test
void incorrectIdYieldsNotFound() {
var recipe = Util.getSampleRecipes().limit(1).findFirst().orElseThrow();
- assertThrows(RecipeNotFoundError.class, () -> modelView.renderSingleRecipe(213, recipe.slug()));
+ assertThrows(
+ RecipeNotFoundError.class,
+ () -> modelView.renderSingleRecipe(213, recipe.slug(), UriComponentsBuilder.newInstance()));
}

@Test
void renderSingleRecipe() {
var recipe = Util.getSampleRecipes().limit(1).findFirst().orElseThrow();
- var doc = parseOutput(modelView.renderSingleRecipe(recipe.recipeId(), recipe.slug()));
+ var doc =
+ parseOutput(
+ modelView.renderSingleRecipe(
+ recipe.recipeId(), recipe.slug(), UriComponentsBuilder.newInstance()));
assertTrue(doc.title().startsWith(recipe.name()));
}
}