diff --git a/rest/starbucks/README.md b/rest/starbucks/README.md index 12f5c011..e1a601d8 100644 --- a/rest/starbucks/README.md +++ b/rest/starbucks/README.md @@ -7,15 +7,19 @@ This sample app exposes 10843 Starbucks coffee shops via a RESTful API that allo 1. Install MongoDB (http://www.mongodb.org/downloads, unzip, run `bin/mongod --dbpath=data`) 2. Build and run the app (`mvn spring-boot:run`) 3. Access the root resource (`curl http://localhost:8080`) and traverse hyperlinks. -4. Or access the location search directly (e.g. `localhost:8080/stores/search/findByAddressLocationNear?location=40.740337,-73.995146&distance=0.5miles`) +4. Or access the location search directly (e.g. `http://localhost:8080/stores/search/findByAddressLocationNear?location=40.740337,-73.995146&distance=0.5miles`) -## API exploration +## Web UI -The module uses the HAL Browser module of Spring Data REST which serves a UI to explore the resources exposed. Point your browser to `http://localhost:8080` to see it. +The application provides a custom web UI using the exposed REST API to display the search result on a Google Map. Point you browser to `http://localhost:8080`. The UI is rendered using Thymeleaf, driven by the `StoresController`. A tiny JavaScript progressively enhances the view by picking up and enhancing a URI template rendered into the view (`
`). + +![Starbucks Web UI](webui.png "Starbucks Web UI") + +The API itself can be discovered using the HAL browser pulled in through the corresponding Spring Data REST module (`spring-data-rest-hal-browser`). It's exposed at the API root at `http://localhost:8080/api`. ## Technologies used - Spring Data REST & Spring Data MongoDB - MongoDB - Spring Batch (to read the CSV file containing the store data and pipe it into MongoDB) -- Spring Boot \ No newline at end of file +- Spring Boot diff --git a/rest/starbucks/pom.xml b/rest/starbucks/pom.xml index 90c637e5..7d315df5 100644 --- a/rest/starbucks/pom.xml +++ b/rest/starbucks/pom.xml @@ -40,6 +40,35 @@ runtime + + + + org.springframework.boot + spring-boot-starter-thymeleaf + runtime + + + + org.webjars + jquery + 2.1.3 + runtime + + + + org.webjars + bootstrap + 3.3.4 + runtime + + + + org.webjars + URI.js + 1.14.1 + runtime + + @@ -54,4 +83,4 @@ - \ No newline at end of file + diff --git a/rest/starbucks/src/main/java/example/stores/Address.java b/rest/starbucks/src/main/java/example/stores/Address.java index 4ced9fac..418aa637 100644 --- a/rest/starbucks/src/main/java/example/stores/Address.java +++ b/rest/starbucks/src/main/java/example/stores/Address.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,4 +30,12 @@ public class Address { private final String street, city, zip; private final @GeoSpatialIndexed Point location; + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + public String toString() { + return String.format("%s, %s %s", street, zip, city); + } } diff --git a/rest/starbucks/src/main/java/example/stores/web/StoresController.java b/rest/starbucks/src/main/java/example/stores/web/StoresController.java new file mode 100644 index 00000000..9e8d5f4d --- /dev/null +++ b/rest/starbucks/src/main/java/example/stores/web/StoresController.java @@ -0,0 +1,98 @@ +/* + * Copyright 2014-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.stores.web; + +import static org.springframework.data.geo.Metrics.*; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import lombok.RequiredArgsConstructor; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.geo.Distance; +import org.springframework.data.geo.Metrics; +import org.springframework.data.geo.Point; +import org.springframework.data.rest.webmvc.support.RepositoryEntityLinks; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +import example.stores.Store; +import example.stores.StoreRepository; + +/** + * A Spring MVC controller to produce an HTML frontend. + * + * @author Oliver Gierke + */ +@Controller +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +class StoresController { + + private static final List DISTANCES = Arrays.asList(new Distance(0.5, MILES), new Distance(1, MILES), + new Distance(2, MILES)); + private static final Distance DEFAULT_DISTANCE = new Distance(1, Metrics.MILES); + private static final Map KNOWN_LOCATIONS; + + static { + + Map locations = new HashMap<>(); + + locations.put("Pivotal SF", new Point(-122.4041764, 37.7819286)); + locations.put("Timesquare NY", new Point(-73.995146, 40.740337)); + + KNOWN_LOCATIONS = Collections.unmodifiableMap(locations); + } + + private final StoreRepository repository; + private final RepositoryEntityLinks entityLinks; + + /** + * Looks up the stores in the given distance around the given location. + * + * @param model the {@link Model} to populate. + * @param location the optional location, if none is given, no search results will be returned. + * @param distance the distance to use, if none is given the {@link #DEFAULT_DISTANCE} is used. + * @param pageable the pagination information + * @return + */ + @RequestMapping(value = "/", method = RequestMethod.GET) + String index(Model model, @RequestParam Optional location, @RequestParam Optional distance, + Pageable pageable) { + + Point point = location.orElse(KNOWN_LOCATIONS.get("Timesquare NY")); + + Page stores = repository.findByAddressLocationNear(point, distance.orElse(DEFAULT_DISTANCE), pageable); + + model.addAttribute("stores", stores); + model.addAttribute("distances", DISTANCES); + model.addAttribute("selectedDistance", distance.orElse(DEFAULT_DISTANCE)); + model.addAttribute("location", point); + model.addAttribute("locations", KNOWN_LOCATIONS); + model.addAttribute("api", entityLinks.linkToSearchResource(Store.class, "by-location", pageable).getHref()); + + return "index"; + } +} diff --git a/rest/starbucks/src/main/resources/application.properties b/rest/starbucks/src/main/resources/application.properties new file mode 100644 index 00000000..6846da4e --- /dev/null +++ b/rest/starbucks/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.data.rest.base-path=/api diff --git a/rest/starbucks/src/main/resources/static/img/map.png b/rest/starbucks/src/main/resources/static/img/map.png new file mode 100644 index 00000000..05d2959e Binary files /dev/null and b/rest/starbucks/src/main/resources/static/img/map.png differ diff --git a/rest/starbucks/src/main/resources/templates/index.html b/rest/starbucks/src/main/resources/templates/index.html new file mode 100644 index 00000000..a4e7c88c --- /dev/null +++ b/rest/starbucks/src/main/resources/templates/index.html @@ -0,0 +1,197 @@ + + + + Starbucks Storefinder + + + + + + + + + + + +
+ +

Starbucks Storefinder

+ +
+
+

Search

+
+
+ +
+ +
+ +
+ Foo +
+
+ +
+ +
+ +
+
+ + +
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+
+ +
+ +
+

Results

+
+ +
+ +
+ +
+
+

Found 1 results.

+
    +
  1. Store name
  2. +
+
+

No Results

+
+ +
+ +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/rest/starbucks/webui.png b/rest/starbucks/webui.png new file mode 100644 index 00000000..ad667299 Binary files /dev/null and b/rest/starbucks/webui.png differ