Skip to content

Commit

Permalink
Merge pull request elastic#11 from ycombinator/master
Browse files Browse the repository at this point in the history
Merging demo project from personal repo into elastic/demo as subfolder
  • Loading branch information
ycombinator committed May 13, 2015
2 parents 3129d4d + 5f0cfd1 commit 0278e81
Show file tree
Hide file tree
Showing 13 changed files with 520 additions and 0 deletions.
2 changes: 2 additions & 0 deletions recipe_search_simple/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
vendor/
composer.lock
88 changes: 88 additions & 0 deletions recipe_search_simple/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Recipe Search - Simple

This sample application demonstrates:
* Searching for recipes by keywords, *and*
* Creating new recipes and saving them in Elasticsearch

![Screenshot of search page](../../blob/gh-pages/images/screenshot.png)

This sample application deliberately uses plain PHP code (that is, no PHP frameworks), a little bit of
[Bootstrap CSS](http://getbootstrap.com/css/) and even less [jQuery](https://jquery.com/). These minimalist choices
are deliberate. We want to keep non-Elasticsearch-related code to a minimum so it as easy as possible to focus on the
Elasticsearch-related code in this application.

## Running this on your own machine

1. Download and install PHP.

1. Download and unzip Elasticsearch.

```sh
$ wget 'https://download.elastic.co/elasticsearch/elasticsearch/elasticsearch-1.5.1.zip'
$ unzip elasticsearch-1.5.1.zip
```

1. Start a 1-node Elasticsearch cluster.

```sh
$ cd elasticsearch-1.5.1
$ ./bin/elasticsearch # The process started by this command corresponds to a single Elasticsearch node
```

By default the node's REST API will be available at `http://localhost:9200`, unless port 9200 is already taken. In
that case Elasticsearch will automatically choose another port. Read through the log messages emitted when you
start the node, and look for a log message containing `http`. In this message, look for `bound_address` and note the
port shown in the accompanying network address.

1. Download the code in this repo and unzip it.

```sh
$ wget -O elastic-demo.zip 'https://github.com/elastic/demo/archive/master.zip'
$ unzip elastic-demo.zip
$ mv demo-master/recipe_search_simple .
$ rm -rf demo-master elastic-demo.zip
$ cd recipe_search_simple
```

1. Install application dependencies.

```sh
$ composer install
```

1. Seed Elasticsearch index with initial recipe data.

```sh
$ php data/seed.php
```

1. Start the application using PHP's built-in web server.

```sh
$ cd public
$ php -S localhost:8000
```

By default this application will communicate with the Elasticsearch API at `http://localhost:9200`. If, in step 3, you
noted a different port than 9200 being used, you will need to pass this information to the application when starting
it up via an environment variable:

```sh
$ APP_ES_PORT=<PORT> php -S localhost:8000
```

1. Open your web browser and visit [`http://localhost:8000`](http://localhost:8000).

## Code Organization
The code in this project is organized as follows, starting at the root directory level (only relevant files and folders listed):

* `data/` &mdash; *contains seed data and loader script*
* `seed.txt` &mdash; *contains seed data in [bulk index](http://www.elastic.co/guide/en/elasticsearch/guide/master/bulk.html) format*
* `seed.php` &mdash; *script to load seed data*
* `public/` &mdash; *contains files served by web server*
* `css/` &mdash; *contains the Bootstrap CSS file*
* `js/` &mdash; *contains the jQuery and this project's Javascript files*
* `add.php` &mdash; *script to add a new recipe to Elasticsearch*
* `index.php` &mdash; *script to search for recipes in Elasticsearch*
* `view.php` &mdash; *script to view a recipe from Elasticsearch*
* `composer.json` &mdash; *file describing application dependencies, including the [Elasticsearch PHP language client](http://www.elastic.co/guide/en/elasticsearch/client/php-api/current/index.html)*
8 changes: 8 additions & 0 deletions recipe_search_simple/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"require": {
"elasticsearch/elasticsearch": "~1.0"
},
"autoload": {
"psr-4": { "RecipeSearchSimple\\": "src/RecipeSearchSimple/" }
}
}
28 changes: 28 additions & 0 deletions recipe_search_simple/data/seed.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

require __DIR__ . '/../vendor/autoload.php';

use RecipeSearchSimple\Constants;

// Connect to local Elasticsearch node
$esPort = getenv('APP_ES_PORT') ?: 9200;
$client = new Elasticsearch\Client([
'hosts' => [ 'localhost:' . $esPort ]
]);

// Delete index to clear out existing data
$deleteParams = [];
$deleteParams['index'] = Constants::ES_INDEX;

if ($client->indices()->exists($deleteParams)) {
$client->indices()->delete($deleteParams);
}

// Setup bulk index request for seed data
$params = [];
$params['index'] = Constants::ES_INDEX;
$params['type'] = Constants::ES_TYPE;
$params['body'] = file_get_contents(__DIR__ . '/seed.txt');

// Bulk load seed data
$ret = $client->bulk($params);
6 changes: 6 additions & 0 deletions recipe_search_simple/data/seed.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{ "index": { "_id": "lemon-chicken" } }
{ "name": "Lemon chicken", "description": "Baked boneless chicken with lemon-garlic seasoning", "cooking_time_min": 40, "ingredients": [ "Chicken breast, 1 fillet", "Lemon-garlic seasoning, 2 tbsp", "Olive oil, 1 tbsp" ], "directions": [ "Pre-heat oven to 350 F.", "Take an oven-safe ceramic utensil and add the olive oil to it.", "Place the chicken in the olive oil and move it around until it is completely covered by the oil.", "Sprinkle about half the lemon-pepper seasoning on one side, usually the top, of the chicken.", "Place the utensil in the oven once it has heated up to 350 F.", "After 20 minutes, remove the utensil, flip the chicken, sprinkle the rest of the seasoning on the other side and put the utensil back in the oven.", "After another 20 minutes, remove the utensil, slice up the chicken to desired size and serve hot."], "servings": 2 }
{ "index": { "_id": "salmon-in-dill-sauce" } }
{ "name": "Salmon in dill sauce", "description": "Baked and broiled salmon fillet in dill-mayo sauce", "cooking_time_min": 25, "tags": "fish", "ingredients": [ "Salmon, 1 fillet" ], "directions": [ "Pre-heat oven to 400 F.", "Do other stuff." ] }
{ "index": { "_id": "masala-chai" } }
{ "name": "Masala chai", "description": "Hot street-style Indian beverage infused with ginger and cardamom", "cooking_time_min": 15 }
172 changes: 172 additions & 0 deletions recipe_search_simple/public/add.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
<?php

require __DIR__ . '/../vendor/autoload.php';

use RecipeSearchSimple\Constants;
use RecipeSearchSimple\Util;

// Add recipe if one was submitted
if (count($_POST) > 0) {

// Connect to Elasticsearch (1-node cluster)
$esPort = getenv('APP_ES_PORT') ?: 9200;
$client = new Elasticsearch\Client([
'hosts' => [ 'localhost:' . $esPort ]
]);

// Convert recipe name to ID
$id = Util::recipeNameToId($_POST['name']);

// Check if recipe with this ID already exists
$exists = $client->exists([
'id' => $id,
'index' => Constants::ES_INDEX,
'type' => Constants::ES_TYPE
]);

if ($exists) {
$message = 'A recipe with this name already exists. You can view it '
. '<a href="/view.php?id=' . $id . '">here</a> or rename your recipe.';
} else {
// Index the recipe in Elasticsearch
$document = [
'id' => $id,
'index' => Constants::ES_INDEX,
'type' => Constants::ES_TYPE,
'body' => $_REQUEST
];
$client->index($document);

// Redirect user to recipe view page
$message = 'Recipe added!';
header('Location: /view.php?id=' . $id . '&message=' . $message);
exit();
}

}
?>
<html>
<head>
<title>Recipe Search</title>
<link rel="stylesheet" href="/css/bootstrap.min.css" />
</head>
<body>
<div class="container bg-danger" id="message">
<?php
if (!empty($message)) {
?>
<p><?php echo $message; ?></p>
<?php
}
?>
</div>
<div class="container">
<h1>Add Recipe</h1>
<form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>">

<!-- Basic information about the recipe -->
<div class="container">
<h3>The Basics</h3>
<div class="form-group">
<div class="row">
<div class="col-xs-7">
<label for="name">Name</label>
<input name="name" value="<?php echo $_REQUEST['name']; ?>" required="true" class="form-control" />
</div>
<div class="col-xs-2">
<label for="cooking_time_min">Cooking time</label>
<input name="cooking_time_min" value="<?php echo $_REQUEST['cooking_time_min']; ?>" type="number" placeholder="minutes" class="form-control"/>
</div>
<div class="col-xs-1">
<label for="servings">Servings</label>
<input name="servings" value="<?php echo $_REQUEST['servings']; ?>" type="number" class="form-control"/>
</div>
</div>
</div>
<div class="form-group">
<div class="row">
<div class="col-xs-10">
<label for="description">Description</label>
<input name="description" value="<?php echo $_REQUEST['description']; ?>" required="true" class="form-control"/>
</div>
</div>
</div>
<div class="form-group">
<div class="row">
<div class="col-xs-10">
<label for="tags">Tags</label>
<input name="tags" value="<?php echo $_REQUEST['tags']; ?>" placeholder="Comma-separated" class="form-control"/>
</div>
</div>
</div>
</div>

<!-- Ingredients -->
<div class="container">
<h3>Ingredients</h3>
<?php
if (isset($_REQUEST['ingredients']) && (count($_REQUEST['ingredients']) > 0)) {
foreach ($_REQUEST['ingredients'] as $index => $ingredient) {
?>
<div class="form-group">
<div class="row">
<div class="col-xs-6">
<input name="ingredients[<?php echo $index; ?>]" value="<?php echo $ingredient; ?>" required="true" class="form-control"/>
</div>
</div>
</div>
<?php
} // END foreach ingredients
} else {
?>
<div class="form-group">
<div class="row">
<div class="col-xs-6">
<input name="ingredients[]" required="true" class="form-control"/>
</div>
</div>
</div>
<?php
}
?>
<a id="add-ingredient" name="add-ingredient" href="#add-ingredient">Add another ingredient</a>
</div>

<!-- Directions -->
<div class="container">
<h3>Directions</h3>
<?php
if (isset($_REQUEST['directions']) && (count($_REQUEST['directions']) > 0)) {
foreach ($_REQUEST['directions'] as $index => $step) {
?>
<div class="form-group">
<div class="row">
<div class="col-xs-6">
<input name="directions[<?php echo $index; ?>]" value="<?php echo $step; ?>" required="true" class="form-control"/>
</div>
</div>
</div>
<?php
} // END foreach directions
} else {
?>
<div class="form-group">
<div class="row">
<div class="col-xs-6">
<input name="directions[]" required="true" class="form-control"/>
</div>
</div>
</div>
<?php
}
?>
<a id="add-step" href="#add-step">Add another step</a>
</div>

<input type="submit" value="Save" class="btn btn-default" />
</form>
</div>
<script language="javascript" src="/js/jquery.min.js"></script>
<script language="javascript" src="/js/script.js"></script>
</body>
</html>
7 changes: 7 additions & 0 deletions recipe_search_simple/public/css/bootstrap.min.css

Large diffs are not rendered by default.

75 changes: 75 additions & 0 deletions recipe_search_simple/public/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

require __DIR__ . '/../vendor/autoload.php';

use RecipeSearchSimple\Constants;

// Get search results from Elasticsearch if the user searched for something
$results = [];
if (!empty($_REQUEST['q'])) {

// Connect to Elasticsearch (1-node cluster)
$esPort = getenv('APP_ES_PORT') ?: 9200;
$client = new Elasticsearch\Client([
'hosts' => [ 'localhost:' . $esPort ]
]);

// Setup search query
$searchParams['index'] = Constants::ES_INDEX; // which index to search
$searchParams['type'] = Constants::ES_TYPE; // which type within the index to search
$searchParams['body']['query']['multi_match']['fields'] = [ 'name', 'description', 'tags', 'ingredients', 'directions' ]; // which fields within the type to search
$searchParams['body']['query']['multi_match']['query'] = $_REQUEST['q']; // what to search for

// Send search query to Elasticsearch and get results
$queryResponse = $client->search($searchParams);
$results = $queryResponse['hits']['hits'];
}
?>
<html>
<head>
<title>Recipe Search</title>
<link rel="stylesheet" href="/css/bootstrap.min.css" />
</head>
<body>
<div class="container">
<h1>Recipe Search</h1>
<form method="get" action="<?php echo $_SERVER['PHP_SELF']; ?>" class="form-inline">
<input name="q" value="<?php echo $_REQUEST['q']; ?>" type="text" placeholder="What are you hungry for?" class="form-control input-lg" size="40" />
<input type="submit" value="Search" class="btn btn-lg" />
</form>
<?php
if (count($results) > 0) {
?>
<table class="table table-striped">
<thead>
<th>Name</th>
<th>Description</th>
<th>Cooking time (minutes)</th>
</thead>
<?php
foreach ($results as $result) {
$recipe = $result['_source'];
?>
<tr>
<td><a href="/view.php?id=<?php echo $result['_id']; ?>"><?php echo $recipe['name']; ?></a></td>
<td><?php echo $recipe['description']; ?></td>
<td><?php echo $recipe['cooking_time_min']; ?></td>
</tr>
<?php
} // END foreach loop over results
?>
</table>
<?php
} // END if there are search results

elseif (!empty($_REQUEST['q'])) {
?>
<p>Sorry, no recipes with <em><?php echo $_REQUEST['q']; ?></em> found :( Would you like to <a href="/add.php">add</a> one?</p>
<?php

} // END elsif there are no search results

?>
</div>
</body>
</html>
4 changes: 4 additions & 0 deletions recipe_search_simple/public/js/jquery.min.js

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions recipe_search_simple/public/js/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
$( "#add-ingredient" ).on("click", function(e) {
var linkEl = e.target;
var newEl = $(linkEl.previousElementSibling).clone();
newEl.insertBefore(linkEl);
});

$( "#add-step" ).on("click", function(e) {
var linkEl = e.target;
var newEl = $(linkEl.previousElementSibling).clone();
newEl.insertBefore(linkEl);
});
Loading

0 comments on commit 0278e81

Please sign in to comment.