From 1fd2ba42552c66f11ffe2599279f892aa2a8a526 Mon Sep 17 00:00:00 2001
From: Eike Cochu <eike@cochu.com>
Date: Sun, 31 Jan 2016 23:05:15 +0100
Subject: [PATCH] added simple search

---
 ma-impl.sublime-workspace                     | 153 ++++++++++++++++--
 vipra-cmd.sh => vipra                         |   0
 .../src/main/java/de/vipra/cmd/Options.java   |   5 +
 .../vipra/rest/resource/ArticleResource.java  |  56 ++++++-
 .../vipra/rest/resource/SearchResource.java   |  86 ----------
 vipra-rest/src/main/resources/log4j2.xml      |   3 +-
 vipra-ui/app/adapters/application.js          |   2 +-
 vipra-ui/app/components/debounced-input.js    |   7 +-
 vipra-ui/app/controllers/articles/index.js    |   8 +
 vipra-ui/app/controllers/index.js             |  16 ++
 vipra-ui/app/controllers/topics/index.js      |   6 +
 vipra-ui/app/controllers/words/index.js       |   6 +
 vipra-ui/app/models/search.js                 |  17 ++
 vipra-ui/app/models/word.js                   |   2 -
 vipra-ui/app/routes/articles/index.js         |   6 +
 vipra-ui/app/routes/index.js                  |   2 +
 vipra-ui/app/routes/topics/index.js           |   6 +
 vipra-ui/app/routes/words/index.js            |   6 +
 vipra-ui/app/styles/app.scss                  |   5 +
 vipra-ui/app/templates/application.hbs        |   6 +-
 vipra-ui/app/templates/index.hbs              |  18 ++-
 21 files changed, 302 insertions(+), 114 deletions(-)
 rename vipra-cmd.sh => vipra (100%)
 create mode 100644 vipra-cmd/src/main/java/de/vipra/cmd/Options.java
 delete mode 100644 vipra-rest/src/main/java/de/vipra/rest/resource/SearchResource.java
 create mode 100644 vipra-ui/app/controllers/articles/index.js
 create mode 100644 vipra-ui/app/controllers/index.js
 create mode 100644 vipra-ui/app/controllers/topics/index.js
 create mode 100644 vipra-ui/app/controllers/words/index.js
 create mode 100644 vipra-ui/app/models/search.js

diff --git a/ma-impl.sublime-workspace b/ma-impl.sublime-workspace
index ab22af65..e7e2fa38 100644
--- a/ma-impl.sublime-workspace
+++ b/ma-impl.sublime-workspace
@@ -279,6 +279,30 @@
 	},
 	"buffers":
 	[
+		{
+			"file": "vipra-ui/app/templates/index.hbs",
+			"settings":
+			{
+				"buffer_size": 1530,
+				"line_ending": "Unix"
+			}
+		},
+		{
+			"file": "vipra-ui/app/styles/app.scss",
+			"settings":
+			{
+				"buffer_size": 914,
+				"line_ending": "Unix"
+			}
+		},
+		{
+			"file": "vipra-ui/app/adapters/application.js",
+			"settings":
+			{
+				"buffer_size": 506,
+				"line_ending": "Unix"
+			}
+		}
 	],
 	"build_system": "",
 	"build_system_choices":
@@ -462,33 +486,48 @@
 		"/home/eike/repos/master/ma-impl/vipra-ui/app",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/adapters",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/components",
-		"/home/eike/repos/master/ma-impl/vipra-ui/app/helpers",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/controllers/articles",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/controllers/topics",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/controllers/words",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/models",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/routes",
-		"/home/eike/repos/master/ma-impl/vipra-ui/app/templates"
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/routes/articles",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/routes/topics",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/routes/words",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/styles",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/templates",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/templates/articles",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/templates/components",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/templates/words"
 	],
 	"file_history":
 	[
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/models/search.js",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/controllers/index.js",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/routes/index.js",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/components/debounced-input.js",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/controllers/articles/index.js",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/templates/application.hbs",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/routes/topics/index.js",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/routes/words/index.js",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/controllers/words/index.js",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/routes/articles/index.js",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/templates/index.hbs",
+		"/home/eike/repos/master/ma-impl/vipra-ui/app/models/word.js",
 		"/home/eike/Downloads/files.txt",
-		"/home/eike/repos/master/ma-impl/vipra-ui/app/templates/application.hbs",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/components/pagination-bar.js",
 		"/home/eike/.cache/.fr-kZOCSD/META-INF/MANIFEST.MF",
 		"/home/eike/repos/master/ma-impl/Vagrantfile",
-		"/home/eike/repos/master/ma-impl/vipra-ui/app/routes/topics/index.js",
-		"/home/eike/repos/master/ma-impl/vipra-ui/app/routes/words/index.js",
-		"/home/eike/repos/master/ma-impl/vipra-ui/app/routes/articles/index.js",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/templates/articles/index.hbs",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/templates/topics/index.hbs",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/components/items-list.js",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/templates/components/items-list.hbs",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/templates/words/index.hbs",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/templates/components/pagination-bar.hbs",
-		"/home/eike/repos/master/ma-impl/vipra-ui/app/controllers/words/index.js",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/controllers/words.js",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/adapters/application.js",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/styles/app.scss",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/helpers/is-empty.js",
-		"/home/eike/repos/master/ma-impl/vipra-ui/app/routes/index.js",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/models/import.js",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/router.js",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/styles/sticky-footer.scss",
@@ -515,7 +554,6 @@
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/index.html",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/routes/words/show.js",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/templates/words/show.hbs",
-		"/home/eike/repos/master/ma-impl/vipra-ui/app/models/word.js",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/routes/words",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/models/topic.js",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/models/article.js",
@@ -591,11 +629,7 @@
 		"/home/eike/Repositories/fu/ss15/ma/impl/vipra-ui/app/templates/articles/list.hbs",
 		"/home/eike/Repositories/fu/ss15/ma/impl/vm/bootstrap.sh",
 		"/home/eike/Repositories/fu/ss15/ma/impl/vipra-ui/app/adapters/application.js",
-		"/home/eike/Repositories/fu/ss15/ma/impl/vipra-ui/app/routes/articles/list.js",
-		"/home/eike/Repositories/fu/ss15/ma/impl/vipra-ui/app/templates/articles/show.hbs",
-		"/home/eike/Repositories/fu/ss15/ma/impl/vipra-ui/app/components/filter-text.js",
-		"/home/eike/Repositories/fu/ss15/ma/impl/vm/webapps/test/index.html",
-		"/home/eike/Repositories/fu/ss15/ma/impl/vipra-ui/app/controllers/articles/list.js"
+		"/home/eike/Repositories/fu/ss15/ma/impl/vipra-ui/app/routes/articles/list.js"
 	],
 	"find":
 	{
@@ -919,8 +953,99 @@
 	"groups":
 	[
 		{
+			"selected": 1,
 			"sheets":
 			[
+				{
+					"buffer": 0,
+					"file": "vipra-ui/app/templates/index.hbs",
+					"semi_transient": false,
+					"settings":
+					{
+						"buffer_size": 1530,
+						"regions":
+						{
+						},
+						"selection":
+						[
+							[
+								1352,
+								1352
+							]
+						],
+						"settings":
+						{
+							"syntax": "Packages/Handlebars/grammars/Handlebars.tmLanguage",
+							"tab_size": 2,
+							"translate_tabs_to_spaces": true
+						},
+						"translation.x": 0.0,
+						"translation.y": 0.0,
+						"zoom_level": 1.0
+					},
+					"stack_index": 1,
+					"type": "text"
+				},
+				{
+					"buffer": 1,
+					"file": "vipra-ui/app/styles/app.scss",
+					"semi_transient": false,
+					"settings":
+					{
+						"buffer_size": 914,
+						"regions":
+						{
+						},
+						"selection":
+						[
+							[
+								681,
+								681
+							]
+						],
+						"settings":
+						{
+							"syntax": "Packages/SCSS/SCSS.tmLanguage",
+							"tab_size": 2,
+							"translate_tabs_to_spaces": true
+						},
+						"translation.x": 0.0,
+						"translation.y": 0.0,
+						"zoom_level": 1.0
+					},
+					"stack_index": 0,
+					"type": "text"
+				},
+				{
+					"buffer": 2,
+					"file": "vipra-ui/app/adapters/application.js",
+					"semi_transient": false,
+					"settings":
+					{
+						"buffer_size": 506,
+						"regions":
+						{
+						},
+						"selection":
+						[
+							[
+								119,
+								119
+							]
+						],
+						"settings":
+						{
+							"syntax": "Packages/JavaScriptNext - ES6 Syntax/JavaScriptNext.tmLanguage",
+							"tab_size": 2,
+							"translate_tabs_to_spaces": true
+						},
+						"translation.x": 0.0,
+						"translation.y": 0.0,
+						"zoom_level": 1.0
+					},
+					"stack_index": 2,
+					"type": "text"
+				}
 			]
 		}
 	],
diff --git a/vipra-cmd.sh b/vipra
similarity index 100%
rename from vipra-cmd.sh
rename to vipra
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/Options.java b/vipra-cmd/src/main/java/de/vipra/cmd/Options.java
new file mode 100644
index 00000000..8f1da6be
--- /dev/null
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/Options.java
@@ -0,0 +1,5 @@
+package de.vipra.cmd;
+
+public class Options {
+
+}
diff --git a/vipra-rest/src/main/java/de/vipra/rest/resource/ArticleResource.java b/vipra-rest/src/main/java/de/vipra/rest/resource/ArticleResource.java
index e4f8a2b8..f760797f 100644
--- a/vipra-rest/src/main/java/de/vipra/rest/resource/ArticleResource.java
+++ b/vipra-rest/src/main/java/de/vipra/rest/resource/ArticleResource.java
@@ -5,7 +5,10 @@ import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
+import java.util.ArrayList;
+import java.util.Date;
 import java.util.List;
+import java.util.Map;
 
 import javax.servlet.ServletContext;
 import javax.ws.rs.Consumes;
@@ -26,12 +29,18 @@ import org.bson.types.ObjectId;
 import org.ehcache.Cache;
 import org.ehcache.CacheManager;
 import org.ehcache.config.CacheConfigurationBuilder;
+import org.elasticsearch.action.search.SearchResponse;
+import org.elasticsearch.client.transport.TransportClient;
+import org.elasticsearch.index.query.QueryBuilders;
+import org.elasticsearch.search.SearchHit;
+import org.elasticsearch.search.SearchHits;
 
 import de.vipra.rest.APIMediaType;
 import de.vipra.rest.Messages;
 import de.vipra.rest.model.APIError;
 import de.vipra.rest.model.Wrapper;
 import de.vipra.util.Config;
+import de.vipra.util.ESClient;
 import de.vipra.util.MongoUtils;
 import de.vipra.util.StringUtils;
 import de.vipra.util.ex.ConfigException;
@@ -47,6 +56,7 @@ public class ArticleResource {
 
 	final Cache<String, ArticleFull> cache;
 	final DatabaseService<ArticleFull, ObjectId> service;
+	final TransportClient client;
 
 	public ArticleResource(@Context ServletContext servletContext) throws ConfigException, IOException {
 		Config config = Config.getConfig();
@@ -58,12 +68,22 @@ public class ArticleResource {
 			articleCache = manager.createCache("articlecache", CacheConfigurationBuilder.newCacheConfigurationBuilder()
 					.buildConfig(String.class, ArticleFull.class));
 		this.cache = articleCache;
+
+		client = ESClient.getClient(config);
 	}
 
 	@GET
 	@Produces(APIMediaType.APPLICATION_JSONAPI)
 	public Response getArticles(@QueryParam("skip") Integer skip, @QueryParam("limit") Integer limit,
-			@QueryParam("sort") @DefaultValue("date") String sortBy, @QueryParam("fields") String fields) {
+			@QueryParam("sort") @DefaultValue("date") String sortBy, @QueryParam("fields") String fields,
+			@QueryParam("query") String query) {
+		if (query == null)
+			return getArticlesFromDb(skip, limit, sortBy, fields);
+		else
+			return getArticlesFromIndex(skip, limit, query);
+	}
+
+	private Response getArticlesFromDb(Integer skip, Integer limit, String sortBy, String fields) {
 		Wrapper<List<ArticleFull>> res = new Wrapper<>();
 
 		if (skip != null && limit != null)
@@ -87,6 +107,40 @@ public class ArticleResource {
 		}
 	}
 
+	private Response getArticlesFromIndex(Integer skip, Integer limit, String query) {
+		Wrapper<List<ArticleFull>> res = new Wrapper<>();
+
+		if (skip == null || skip < 0)
+			skip = 0;
+
+		if (limit == null || limit < 0)
+			limit = 20;
+
+		SearchResponse response = null;
+		try {
+			response = client.prepareSearch("articles").setQuery(QueryBuilders.multiMatchQuery(query, "_all"))
+					.setFrom(skip).setSize(limit).execute().actionGet();
+		} catch (Exception e) {
+			res.addError(new APIError(Response.Status.BAD_REQUEST, "Error", e.getMessage()));
+			return res.badRequest();
+		}
+
+		SearchHits hits = response.getHits();
+		List<ArticleFull> articles = new ArrayList<>(10);
+		for (SearchHit hit : hits) {
+			Map<String, Object> source = hit.getSource();
+			ArticleFull article = new ArticleFull();
+			article.setId(MongoUtils.objectId(hit.getId()));
+			article.setTitle(source.get("title").toString());
+			article.setText(StringUtils.ellipsize(source.get("text").toString(), 150));
+			articles.add(article);
+		}
+
+		res.addMeta("total", articles.size());
+
+		return res.ok(articles);
+	}
+
 	@GET
 	@Produces(APIMediaType.APPLICATION_JSONAPI)
 	@Consumes(APIMediaType.APPLICATION_JSONAPI)
diff --git a/vipra-rest/src/main/java/de/vipra/rest/resource/SearchResource.java b/vipra-rest/src/main/java/de/vipra/rest/resource/SearchResource.java
deleted file mode 100644
index 3f7c24a6..00000000
--- a/vipra-rest/src/main/java/de/vipra/rest/resource/SearchResource.java
+++ /dev/null
@@ -1,86 +0,0 @@
-package de.vipra.rest.resource;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-import javax.servlet.ServletContext;
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.UriInfo;
-
-import org.elasticsearch.action.search.SearchResponse;
-import org.elasticsearch.action.search.SearchType;
-import org.elasticsearch.client.transport.TransportClient;
-import org.elasticsearch.index.query.QueryBuilders;
-import org.elasticsearch.search.SearchHit;
-import org.elasticsearch.search.SearchHits;
-
-import de.vipra.rest.APIMediaType;
-import de.vipra.rest.model.APIError;
-import de.vipra.rest.model.Wrapper;
-import de.vipra.util.Config;
-import de.vipra.util.ESClient;
-import de.vipra.util.MongoUtils;
-import de.vipra.util.StringUtils;
-import de.vipra.util.ex.ConfigException;
-import de.vipra.util.model.ArticleFull;
-
-@Path("search")
-public class SearchResource {
-
-	@Context
-	UriInfo uri;
-
-	final TransportClient client;
-
-	public SearchResource(@Context ServletContext servletContext) throws IOException, ConfigException {
-		Config config = Config.getConfig();
-		client = ESClient.getClient(config);
-	}
-
-	@GET
-	@Produces(APIMediaType.APPLICATION_JSONAPI)
-	public Response search(@QueryParam("query") String query, @QueryParam("skip") Integer skip,
-			@QueryParam("limit") Integer limit, @QueryParam("excerpt") Integer excerpt) {
-		Wrapper<List<ArticleFull>> res = new Wrapper<>();
-
-		if (skip == null || skip < 0)
-			skip = 0;
-
-		if (limit == null || limit < 0)
-			limit = 20;
-
-		SearchResponse response = null;
-		try {
-			response = client.prepareSearch("articles").setSearchType(SearchType.QUERY_AND_FETCH)
-					.setQuery(QueryBuilders.multiMatchQuery(query, "_all")).setFrom(skip).setSize(limit).execute()
-					.actionGet();
-		} catch (Exception e) {
-			res.addError(new APIError(Response.Status.BAD_REQUEST, "Error", e.getMessage()));
-			return res.badRequest();
-		}
-
-		SearchHits hits = response.getHits();
-		List<ArticleFull> articles = new ArrayList<>(10);
-		for (SearchHit hit : hits) {
-			Map<String, Object> source = hit.getSource();
-			ArticleFull article = new ArticleFull();
-			article.setId(MongoUtils.objectId(hit.getId()));
-			article.setTitle(source.get("title").toString());
-			if (excerpt != null && excerpt > 0)
-				article.setText(StringUtils.ellipsize(source.get("text").toString(), excerpt));
-			articles.add(article);
-		}
-
-		res.addMeta("total", articles.size());
-
-		return res.ok(articles);
-	}
-
-}
diff --git a/vipra-rest/src/main/resources/log4j2.xml b/vipra-rest/src/main/resources/log4j2.xml
index 87133400..f45f0f9e 100644
--- a/vipra-rest/src/main/resources/log4j2.xml
+++ b/vipra-rest/src/main/resources/log4j2.xml
@@ -6,9 +6,8 @@
 		</Console>
 	</Appenders>
 	<Loggers>
-		<Root level="ALL">
+		<Root level="DEBUG">
 			<AppenderRef ref="Console" />
 		</Root>
-		<Logger name="org.mongodb" level="INFO"/>
 	</Loggers>
 </Configuration>
\ No newline at end of file
diff --git a/vipra-ui/app/adapters/application.js b/vipra-ui/app/adapters/application.js
index c14dc91b..0d442b61 100644
--- a/vipra-ui/app/adapters/application.js
+++ b/vipra-ui/app/adapters/application.js
@@ -1,7 +1,7 @@
 import DS from 'ember-data';
 
 export default DS.JSONAPIAdapter.extend({
-  host: `http://${window.location.hostname}:8000`,
+  host: `http://${window.location.hostname}:8080`,
   namespace: 'vipra-rest',
   updateRecord(store, type, snapshot) {
     var data = {};
diff --git a/vipra-ui/app/components/debounced-input.js b/vipra-ui/app/components/debounced-input.js
index 5eee646f..ba23db7e 100644
--- a/vipra-ui/app/components/debounced-input.js
+++ b/vipra-ui/app/components/debounced-input.js
@@ -5,7 +5,12 @@ export default Ember.TextField.extend({
   fireAtStart: false,
 
   _elementValueDidChange() {
-    Ember.run.debounce(this, this._setValue, this.debounce, this.fireAtStart);
+    let val = this.$().val();
+    if(val) {
+      Ember.run.debounce(this, this._setValue, this.debounce, this.fireAtStart);
+    } else {
+      this._setValue();
+    }
   },
 
   _setValue() {
diff --git a/vipra-ui/app/controllers/articles/index.js b/vipra-ui/app/controllers/articles/index.js
new file mode 100644
index 00000000..f73630c1
--- /dev/null
+++ b/vipra-ui/app/controllers/articles/index.js
@@ -0,0 +1,8 @@
+import Ember from 'ember';
+
+export default Ember.Controller.extend({
+
+  queryParams: 'page',
+  page: 1
+  
+});
diff --git a/vipra-ui/app/controllers/index.js b/vipra-ui/app/controllers/index.js
new file mode 100644
index 00000000..faf5a820
--- /dev/null
+++ b/vipra-ui/app/controllers/index.js
@@ -0,0 +1,16 @@
+export default Ember.Controller.extend({
+
+  queryParams: 'q',
+  q: '',
+
+  search: Ember.computed('q', function() {
+    let query = this.get('q');
+    if(query) {
+      return this.store.query('article', {
+        query: query,
+        limit: 10
+      });
+    }
+  })
+
+});
\ No newline at end of file
diff --git a/vipra-ui/app/controllers/topics/index.js b/vipra-ui/app/controllers/topics/index.js
new file mode 100644
index 00000000..d215b2d1
--- /dev/null
+++ b/vipra-ui/app/controllers/topics/index.js
@@ -0,0 +1,6 @@
+import Ember from 'ember';
+
+export default Ember.Controller.extend({
+  queryParams: 'page',
+  page: 1
+});
diff --git a/vipra-ui/app/controllers/words/index.js b/vipra-ui/app/controllers/words/index.js
new file mode 100644
index 00000000..d215b2d1
--- /dev/null
+++ b/vipra-ui/app/controllers/words/index.js
@@ -0,0 +1,6 @@
+import Ember from 'ember';
+
+export default Ember.Controller.extend({
+  queryParams: 'page',
+  page: 1
+});
diff --git a/vipra-ui/app/models/search.js b/vipra-ui/app/models/search.js
new file mode 100644
index 00000000..fc38e095
--- /dev/null
+++ b/vipra-ui/app/models/search.js
@@ -0,0 +1,17 @@
+import DS from 'ember-data';
+
+export default DS.Model.extend({
+  title: DS.attr(),
+  text: DS.attr(),
+  url: DS.attr(),
+  date: DS.attr('date'),
+  stats: DS.attr(),
+  topics: DS.attr(),
+  created: DS.attr('date'),
+  modified: DS.attr('date'),
+
+  _name: function() {
+    var title = this.get('title');
+    return title ? title : this.get('id');
+  }.property('title')
+});
diff --git a/vipra-ui/app/models/word.js b/vipra-ui/app/models/word.js
index a18091a1..4d729630 100644
--- a/vipra-ui/app/models/word.js
+++ b/vipra-ui/app/models/word.js
@@ -1,8 +1,6 @@
 import DS from 'ember-data';
 
 export default DS.Model.extend({
-  word: DS.attr(),
-
   _name: function() {
     return this.get('id');
   }.property('id')
diff --git a/vipra-ui/app/routes/articles/index.js b/vipra-ui/app/routes/articles/index.js
index 7fcaffd1..b5920fb2 100644
--- a/vipra-ui/app/routes/articles/index.js
+++ b/vipra-ui/app/routes/articles/index.js
@@ -76,6 +76,12 @@ export default Ember.Route.extend({
       name: 'Articles',
       data: data
     }];
+  },
+
+  resetController(controller, isExiting, transition) {
+    if(isExiting) {
+      controller.set('page', 1);
+    }
   }
   
 });
diff --git a/vipra-ui/app/routes/index.js b/vipra-ui/app/routes/index.js
index 2e701b60..bfef9997 100644
--- a/vipra-ui/app/routes/index.js
+++ b/vipra-ui/app/routes/index.js
@@ -1,6 +1,7 @@
 import Ember from 'ember';
 
 export default Ember.Route.extend({
+
   model() {
     return {
       latestarticles: this.store.query('article', {limit: 5, sort: '-created'}),
@@ -8,4 +9,5 @@ export default Ember.Route.extend({
       latestwords: this.store.query('word', {limit: 5, sort: '-created'}),
     };
   }
+
 });
\ No newline at end of file
diff --git a/vipra-ui/app/routes/topics/index.js b/vipra-ui/app/routes/topics/index.js
index ce1a47c4..1540346b 100644
--- a/vipra-ui/app/routes/topics/index.js
+++ b/vipra-ui/app/routes/topics/index.js
@@ -26,6 +26,12 @@ export default Ember.Route.extend({
       limit: limit,
       topics: this.store.query('topic', query)
     });
+  },
+
+  resetController(controller, isExiting, transition) {
+    if(isExiting) {
+      controller.set('page', 1);
+    }
   }
 
 });
\ No newline at end of file
diff --git a/vipra-ui/app/routes/words/index.js b/vipra-ui/app/routes/words/index.js
index 52c99646..c714a5d1 100644
--- a/vipra-ui/app/routes/words/index.js
+++ b/vipra-ui/app/routes/words/index.js
@@ -27,6 +27,12 @@ export default Ember.Route.extend({
       limit: limit,
       words: this.store.query('word', query)
     });
+  },
+
+  resetController(controller, isExiting, transition) {
+    if(isExiting) {
+      controller.set('page', 1);
+    }
   }
 
 });
\ No newline at end of file
diff --git a/vipra-ui/app/styles/app.scss b/vipra-ui/app/styles/app.scss
index 34d181cb..418dbba8 100644
--- a/vipra-ui/app/styles/app.scss
+++ b/vipra-ui/app/styles/app.scss
@@ -41,6 +41,11 @@ body {
   text-overflow: ellipsis;
 }
 
+.search-result {
+  padding: 5px;
+  margin-bottom: 5px;
+}
+
 .navbar-default {
   .collapse:not(.in) {
     .navbar-nav > .active {
diff --git a/vipra-ui/app/templates/application.hbs b/vipra-ui/app/templates/application.hbs
index e7aa4f45..6e2bf2f7 100644
--- a/vipra-ui/app/templates/application.hbs
+++ b/vipra-ui/app/templates/application.hbs
@@ -14,9 +14,9 @@
     <!-- Collect the nav links, forms, and other content for toggling -->
     <div class="collapse navbar-collapse" id="vipra-navbar-collapse-1">
       <ul class="nav navbar-nav">
-        {{#link-to 'articles' tagName='li'}}{{link-to 'Articles' 'articles'}}{{/link-to}}
-        {{#link-to 'topics' tagName='li'}}{{link-to 'Topics' 'topics'}}{{/link-to}}
-        {{#link-to 'words' tagName='li'}}{{link-to 'Words' 'words'}}{{/link-to}}
+        {{#link-to 'articles' tagName='li'}}{{link-to 'Articles' 'articles' (query-params page=1)}}{{/link-to}}
+        {{#link-to 'topics' tagName='li'}}{{link-to 'Topics' 'topics' (query-params page=1)}}{{/link-to}}
+        {{#link-to 'words' tagName='li'}}{{link-to 'Words' 'words' (query-params page=1)}}{{/link-to}}
         {{outlet 'menu-left'}}
         <div id="testi"></div>
       </ul>
diff --git a/vipra-ui/app/templates/index.hbs b/vipra-ui/app/templates/index.hbs
index 88012faa..5332d139 100644
--- a/vipra-ui/app/templates/index.hbs
+++ b/vipra-ui/app/templates/index.hbs
@@ -6,7 +6,7 @@
       </div>
 
       <br>
-      {{debounced-input class='form-control input-lg' placeholder='Search...' value=filter debounce='500'}}
+      {{debounced-input value=q class="form-control input-lg" placeholder="Search..."}}
     </div>
   </div>
 
@@ -38,8 +38,18 @@
     </div>
   </div>
 
-  <div class="row">
-  {{filter}}
-  </div>
+  {{#if search}}
+    <hr>
+    <h4>Search results</h4>
+    <ol>
+      {{#each search as |article|}}
+        <li class="search-result">
+          {{link-to article.title 'articles.show' article.id}}<br>
+          &#9492; <small>{{article.text}}</small>
+        </li>
+      {{/each}}
+    </ol>
+
+  {{/if}}
 
 </div>
\ No newline at end of file
-- 
GitLab