From a159589bfd7cf4f08cca5d5943ba39aed0e9a8e6 Mon Sep 17 00:00:00 2001 From: Eike Cochu <eike@cochu.com> Date: Thu, 18 Feb 2016 01:26:29 +0100 Subject: [PATCH] using word id, simplified ui routes ui: simplified routes, removed .index routes ui: added ui-view to route bases ui: moved network graph link to actions bar ui: added alert directive to display alerts ui: added alert to topic renaming backend: removed word attribute from word model, using id instead backend: moved topics query from single word query to subpath /topics backend: removed uuid from apierror, unused backend: removed wrapper from resource put requests, serializing to model directly backend: fixed deserializing topicword/word when receiving from frontend --- .../java/de/vipra/rest/model/APIError.java | 7 -- .../vipra/rest/resource/ArticleResource.java | 4 +- .../de/vipra/rest/resource/TopicResource.java | 12 +-- .../de/vipra/rest/resource/WordResource.java | 28 +++++-- vipra-ui/app/html/articles/index.html | 20 ++--- vipra-ui/app/html/articles/show.html | 6 +- vipra-ui/app/html/directives/alert.html | 6 ++ vipra-ui/app/html/topics/index.html | 20 ++--- vipra-ui/app/html/topics/show.html | 29 ++++--- vipra-ui/app/html/words/index.html | 54 +++++++------ vipra-ui/app/html/words/show.html | 4 +- vipra-ui/app/index.html | 6 +- vipra-ui/app/js/app.js | 24 +----- vipra-ui/app/js/controllers.js | 80 +++++++++++-------- vipra-ui/app/js/directives.js | 25 ++++++ vipra-ui/app/js/factories.js | 14 ++-- vipra-ui/app/js/helpers.js | 17 +++- vipra-ui/app/less/app.less | 8 +- .../src/main/java/de/vipra/util/WordMap.java | 2 +- .../main/java/de/vipra/util/an/ConfigKey.java | 5 ++ .../java/de/vipra/util/an/ElasticIndex.java | 4 + .../java/de/vipra/util/an/QueryIgnore.java | 5 ++ .../java/de/vipra/util/model/TopicFull.java | 2 +- .../java/de/vipra/util/model/TopicWord.java | 20 +++-- .../main/java/de/vipra/util/model/Word.java | 40 +--------- .../de/vipra/util/service/MongoService.java | 2 +- .../java/de/vipra/util/service/Service.java | 4 +- 27 files changed, 247 insertions(+), 201 deletions(-) create mode 100644 vipra-ui/app/html/directives/alert.html diff --git a/vipra-backend/src/main/java/de/vipra/rest/model/APIError.java b/vipra-backend/src/main/java/de/vipra/rest/model/APIError.java index 241a0a32..2a3714b4 100644 --- a/vipra-backend/src/main/java/de/vipra/rest/model/APIError.java +++ b/vipra-backend/src/main/java/de/vipra/rest/model/APIError.java @@ -1,12 +1,9 @@ package de.vipra.rest.model; -import java.util.UUID; - import javax.ws.rs.core.Response.Status; public class APIError { - private final String id = UUID.randomUUID().toString(); private String status; private String code; private String title; @@ -25,10 +22,6 @@ public class APIError { this(Integer.toString(status.getStatusCode()), status.getReasonPhrase(), title, detail); } - public String getId() { - return id; - } - public String getStatus() { return status; } diff --git a/vipra-backend/src/main/java/de/vipra/rest/resource/ArticleResource.java b/vipra-backend/src/main/java/de/vipra/rest/resource/ArticleResource.java index a5733b72..a203c319 100644 --- a/vipra-backend/src/main/java/de/vipra/rest/resource/ArticleResource.java +++ b/vipra-backend/src/main/java/de/vipra/rest/resource/ArticleResource.java @@ -156,9 +156,9 @@ public class ArticleResource { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @Path("{id}") - public Response replaceArticle(@PathParam("id") String id, Wrapper<ArticleFull> wrapper) { - ArticleFull article = wrapper.getData(); + public Response replaceArticle(@PathParam("id") String id, ArticleFull article) { Wrapper<ArticleFull> res = new Wrapper<>(); + try { dbArticles.updateSingle(article); return res.ok(article); diff --git a/vipra-backend/src/main/java/de/vipra/rest/resource/TopicResource.java b/vipra-backend/src/main/java/de/vipra/rest/resource/TopicResource.java index 4d2512d0..a1b3e03a 100644 --- a/vipra-backend/src/main/java/de/vipra/rest/resource/TopicResource.java +++ b/vipra-backend/src/main/java/de/vipra/rest/resource/TopicResource.java @@ -119,8 +119,10 @@ public class TopicResource { Wrapper<List<ArticleFull>> res = new Wrapper<>(); try { Topic topic = new Topic(MongoUtils.objectId(id)); - List<ArticleFull> articles = dbArticles.getMultiple( - QueryBuilder.builder().criteria("topics.topic", topic).fields(true, StringUtils.getFields(fields))); + QueryBuilder query = QueryBuilder.builder().criteria("topics.topic", topic); + if (fields != null && !fields.isEmpty()) + query.fields(true, StringUtils.getFields(fields)); + List<ArticleFull> articles = dbArticles.getMultiple(query); return res.ok(articles); } catch (Exception e) { e.printStackTrace(); @@ -133,15 +135,15 @@ public class TopicResource { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @Path("{id}") - public Response replaceTopic(@PathParam("id") String id, Wrapper<TopicFull> wrapper) { - TopicFull topic = wrapper.getData(); + public Response replaceTopic(@PathParam("id") String id, TopicFull topic) { Wrapper<TopicFull> res = new Wrapper<>(); + try { dbTopics.updateSingle(topic); return res.ok(topic); } catch (DatabaseException e) { e.printStackTrace(); - res = new Wrapper<>(new APIError(Response.Status.INTERNAL_SERVER_ERROR, "item could not be updated", + res.addError(new APIError(Response.Status.INTERNAL_SERVER_ERROR, "item could not be updated", "item could not be updated due to an internal server error")); return res.serverError(); } diff --git a/vipra-backend/src/main/java/de/vipra/rest/resource/WordResource.java b/vipra-backend/src/main/java/de/vipra/rest/resource/WordResource.java index ad2abe96..6e158879 100644 --- a/vipra-backend/src/main/java/de/vipra/rest/resource/WordResource.java +++ b/vipra-backend/src/main/java/de/vipra/rest/resource/WordResource.java @@ -47,7 +47,7 @@ public class WordResource { @GET @Produces(MediaType.APPLICATION_JSON) public Response getWords(@QueryParam("skip") Integer skip, @QueryParam("limit") Integer limit, - @QueryParam("sort") @DefaultValue("word") String sortBy, @QueryParam("fields") String fields) { + @QueryParam("sort") @DefaultValue("id") String sortBy, @QueryParam("fields") String fields) { Wrapper<List<Word>> res = new Wrapper<>(); if (skip != null && limit != null) @@ -94,16 +94,32 @@ public class WordResource { } if (word != null) { - List<TopicFull> topics = dbTopics.getMultiple( - QueryBuilder.builder().fields(false, "index", "created", "modified").criteria("words.word", word)); - word.setTopics(topics); - return res.ok(word); } else { - String msg = String.format(Messages.NOT_FOUND, "word", id); + String msg = String.format(Messages.NOT_FOUND, "id", id); res.addError(new APIError(Response.Status.NOT_FOUND, "Resource not found", msg)); return res.notFound(); } } + @GET + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + @Path("{id}/topics") + public Response getWordTopics(@PathParam("id") String id, @QueryParam("fields") String fields) { + Wrapper<List<TopicFull>> res = new Wrapper<>(); + try { + Word word = new Word(id); + QueryBuilder query = QueryBuilder.builder().fields(true, "id", "name").criteria("words.word", word); + if (fields != null && !fields.isEmpty()) + query.fields(true, StringUtils.getFields(fields)); + List<TopicFull> topics = dbTopics.getMultiple(query); + return res.ok(topics); + } catch (Exception e) { + e.printStackTrace(); + res.addError(new APIError(Response.Status.BAD_REQUEST, "Error", e.getMessage())); + return res.badRequest(); + } + } + } diff --git a/vipra-ui/app/html/articles/index.html b/vipra-ui/app/html/articles/index.html index 7c5a3a60..45868a68 100644 --- a/vipra-ui/app/html/articles/index.html +++ b/vipra-ui/app/html/articles/index.html @@ -1,11 +1,13 @@ -<div class="well"> - Found <span ng-bind="articlesMeta.total"></span> articles in the database <query-time/>.<br> -</div> +<div ui-view> + <div class="well"> + Found <span ng-bind="articlesMeta.total"></span> articles in the database <query-time/>.<br> + </div> -<ul class="dashed"> - <li ng-repeat="article in articles"> - <a ui-sref="articles.show({id: article.id})" ng-bind="::article.title"></a> - </li> -</ul> + <ul class="dashed"> + <li ng-repeat="article in articles"> + <a ui-sref="articles.show({id: article.id})" ng-bind="::article.title"></a> + </li> + </ul> -<pagination total="articlesMeta.total" page="page" limit="limit" change="changePage"/> \ No newline at end of file + <pagination total="articlesMeta.total" page="page" limit="limit" change="changePage"/> +</div> \ No newline at end of file diff --git a/vipra-ui/app/html/articles/show.html b/vipra-ui/app/html/articles/show.html index e5f4708a..7a7e9ad8 100644 --- a/vipra-ui/app/html/articles/show.html +++ b/vipra-ui/app/html/articles/show.html @@ -14,7 +14,7 @@ </tr> <tr> <th>Date</th> - <td ng-bind="::article.date"></td> + <td ng-bind="::articleDate"></td> </tr> <tr> <th>URL</th> @@ -22,11 +22,11 @@ </tr> <tr> <th>Created</th> - <td ng-bind="::article.created"></td> + <td ng-bind="::articleCreated"></td> </tr> <tr> <th>Last modified</th> - <td ng-bind="::article.modified"></td> + <td ng-bind="::articleModified"></td> </tr> <tr> <th>Word count</th> diff --git a/vipra-ui/app/html/directives/alert.html b/vipra-ui/app/html/directives/alert.html new file mode 100644 index 00000000..441726d4 --- /dev/null +++ b/vipra-ui/app/html/directives/alert.html @@ -0,0 +1,6 @@ +<div ng-attr-class="{{classes}}" role="alert"> + <button type="button" class="close" data-dismiss="alert" aria-label="Close" ng-show="dismissible"> + <span aria-hidden="true">×</span> + </button> + <ng-transclude/> +</div> \ No newline at end of file diff --git a/vipra-ui/app/html/topics/index.html b/vipra-ui/app/html/topics/index.html index c73784d5..3eac37a5 100644 --- a/vipra-ui/app/html/topics/index.html +++ b/vipra-ui/app/html/topics/index.html @@ -1,11 +1,13 @@ -<div class="well"> - Found <span ng-bind="topicsMeta.total"></span> topics in the database <query-time/>. -</div> +<div ui-view> + <div class="well"> + Found <span ng-bind="topicsMeta.total"></span> topics in the database <query-time/>. + </div> -<ul class="dashed"> - <li ng-repeat="topic in topics"> - <a ui-sref="topics.show({id: topic.id})">{{topic.name}}</a> - </li> -</ul> + <ul class="dashed"> + <li ng-repeat="topic in topics"> + <a ui-sref="topics.show({id: topic.id})">{{topic.name}}</a> + </li> + </ul> -<pagination total="topicsMeta.total" page="page" limit="limit" change="changePage"/> \ No newline at end of file + <pagination total="topicsMeta.total" page="page" limit="limit" change="changePage"/> +</div> \ No newline at end of file diff --git a/vipra-ui/app/html/topics/show.html b/vipra-ui/app/html/topics/show.html index 753ba0cb..b82d508a 100644 --- a/vipra-ui/app/html/topics/show.html +++ b/vipra-ui/app/html/topics/show.html @@ -13,10 +13,23 @@ </div> </div> </h1> + + <bs-alert type="danger" ng-if="renameErrors"> + <span ng-bind-html="renameErrors"></span> + </bs-alert> - <bs-dropdown label="Actions"> - <li><a ng-click="startRename()">Rename</a></li> - </bs-dropdown> + <table class="item-actions"> + <tr> + <td> + <bs-dropdown label="Actions"> + <li><a ng-click="startRename()">Rename</a></li> + </bs-dropdown> + </td> + <td> + <a class="btn btn-default" ui-sref="network({type:'topics', id:topic.id})">Network graph</a> + </td> + </tr> + </table> </div> <h3>Info <hide-link target="#info"/></h3> @@ -35,17 +48,11 @@ </tr> <tr> <th>Created</th> - <td ng-bind="::topic.created"></td> + <td ng-bind="::topicCreated"></td> </tr> <tr> <th>Last modified</th> - <td ng-bind="::topic.modified"></td> - </tr> - <tr> - <th>Links</th> - <td> - <a ui-sref="network({type:'topics', id:topic.id})">Network graph</a> - </td> + <td ng-bind="::topicModified"></td> </tr> </tbody> </table> diff --git a/vipra-ui/app/html/words/index.html b/vipra-ui/app/html/words/index.html index d0850631..10297771 100644 --- a/vipra-ui/app/html/words/index.html +++ b/vipra-ui/app/html/words/index.html @@ -1,29 +1,31 @@ -<div class="well"> - Found <span ng-bind="wordsMeta.total"></span> words in the database <query-time/>. -</div> - -<div class="row"> - <div class="col-md-4"> - <ul class="list-unstyled"> - <li ng-repeat="word in words.slice(0,100)"> - <a ui-sref="words.show({id: word.id})">{{word.id}}</a> - </li> - </ul> - </div> - <div class="col-md-4"> - <ul class="list-unstyled"> - <li ng-repeat="word in words.slice(100,200)"> - <a ui-sref="words.show({id: word.id})">{{word.id}}</a> - </li> - </ul> +<div ui-view> + <div class="well"> + Found <span ng-bind="wordsMeta.total"></span> words in the database <query-time/>. </div> - <div class="col-md-4"> - <ul class="list-unstyled"> - <li ng-repeat="word in words.slice(200,300)"> - <a ui-sref="words.show({id: word.id})">{{word.id}}</a> - </li> - </ul> + + <div class="row"> + <div class="col-md-4"> + <ul class="list-unstyled"> + <li ng-repeat="word in words.slice(0,100)"> + <a ui-sref="words.show({id: word.id})">{{word.id}}</a> + </li> + </ul> + </div> + <div class="col-md-4"> + <ul class="list-unstyled"> + <li ng-repeat="word in words.slice(100,200)"> + <a ui-sref="words.show({id: word.id})">{{word.id}}</a> + </li> + </ul> + </div> + <div class="col-md-4"> + <ul class="list-unstyled"> + <li ng-repeat="word in words.slice(200,300)"> + <a ui-sref="words.show({id: word.id})">{{word.id}}</a> + </li> + </ul> + </div> </div> -</div> -<pagination total="wordsMeta.total" page="page" limit="limit" change="changePage"/> \ No newline at end of file + <pagination total="wordsMeta.total" page="page" limit="limit" change="changePage"/> +</div> \ No newline at end of file diff --git a/vipra-ui/app/html/words/show.html b/vipra-ui/app/html/words/show.html index 749c267f..c5e0cf03 100644 --- a/vipra-ui/app/html/words/show.html +++ b/vipra-ui/app/html/words/show.html @@ -10,7 +10,7 @@ <tbody> <tr> <th>Created</th> - <td ng-bind="::word.created"></td> + <td ng-bind="::wordCreated"></td> </tr> </tbody> </table> @@ -22,7 +22,7 @@ <div class="row" id="topics"> <div class="col-md-12"> <ul class="list-unstyled"> - <li ng-repeat="topic in ::word.topics"> + <li ng-repeat="topic in ::topics"> <topic-link topic="topic"/> </li> </ul> diff --git a/vipra-ui/app/index.html b/vipra-ui/app/index.html index 8c4471f6..319bbda4 100644 --- a/vipra-ui/app/index.html +++ b/vipra-ui/app/index.html @@ -51,9 +51,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"> - <li ng-class="{active:$state.includes('articles')}"><a ui-sref="articles.index">Articles</a></li> - <li ng-class="{active:$state.includes('topics')}"><a ui-sref="topics.index">Topics</a></li> - <li ng-class="{active:$state.includes('words')}"><a ui-sref="words.index">Words</a></li> + <li ng-class="{active:$state.includes('articles')}"><a ui-sref="articles">Articles</a></li> + <li ng-class="{active:$state.includes('topics')}"><a ui-sref="topics">Topics</a></li> + <li ng-class="{active:$state.includes('words')}"><a ui-sref="words">Words</a></li> </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> diff --git a/vipra-ui/app/js/app.js b/vipra-ui/app/js/app.js index be65d8cc..e0a708a3 100644 --- a/vipra-ui/app/js/app.js +++ b/vipra-ui/app/js/app.js @@ -38,13 +38,7 @@ // states: articles $stateProvider.state('articles', { - url: '/articles', - abstract: true, - template: '<ui-view/>' - }); - - $stateProvider.state('articles.index', { - url: '?page', + url: '/articles?page', templateUrl: tplBase + '/articles/index.html', controller: 'ArticlesIndexController', reloadOnSearch: false @@ -59,13 +53,7 @@ // states: topics $stateProvider.state('topics', { - url: '/topics', - abstract: true, - template: '<ui-view/>' - }); - - $stateProvider.state('topics.index', { - url: '?page', + url: '/topics?page', templateUrl: tplBase + '/topics/index.html', controller: 'TopicsIndexController', reloadOnSearch: false @@ -80,13 +68,7 @@ // states: words $stateProvider.state('words', { - url: '/words', - abstract: true, - template: '<ui-view/>' - }); - - $stateProvider.state('words.index', { - url: '?page', + url: '/words?page', templateUrl: tplBase + '/words/index.html', controller: 'WordsIndexController', reloadOnSearch: false diff --git a/vipra-ui/app/js/controllers.js b/vipra-ui/app/js/controllers.js index cdbd83dd..ae8935fd 100644 --- a/vipra-ui/app/js/controllers.js +++ b/vipra-ui/app/js/controllers.js @@ -286,16 +286,17 @@ app.controller('ArticlesShowController', ['$scope', '$stateParams', 'ArticleFactory', function($scope, $stateParams, ArticleFactory, testService) { + $scope.topicSort = $scope.topicSort || 'topic.share'; + $scope.topicSortRev = typeof $scope.topicSortRev === 'undefined' ? false : $scope.topicSortRev; + ArticleFactory.get({id: $stateParams.id}, function(response) { $scope.article = response.data; $scope.article.text = createInitial($scope.article.text); - $scope.article.date = formatDate($scope.article.date); - $scope.article.created = formatDateTime($scope.article.created); - $scope.article.modified = formatDateTime($scope.article.modified); + $scope.articleDate = formatDate($scope.article.date); + $scope.articleCreated = formatDateTime($scope.article.created); + $scope.articleModified = formatDateTime($scope.article.modified); $scope.articleMeta = response.meta; $scope.queryTime = response.$queryTime; - $scope.topicSort = $scope.topicSort || 'topic.share'; - $scope.topicSortRev = typeof $scope.topicSortRev === 'undefined' ? false : $scope.topicSortRev; // calculate percentage share var topicShareSeries = [], @@ -323,7 +324,6 @@ }; $scope.topicShare = topicShare; - }); }]); @@ -370,39 +370,47 @@ app.controller('TopicsShowController', ['$scope', '$stateParams', '$timeout', 'TopicFactory', function($scope, $stateParams, $timeout, TopicFactory) { + $scope.wordSort = $scope.wordSort || 'likeliness'; + $scope.wordSortRev = typeof $scope.wordSortRev === 'undefined' ? true : $scope.wordSortRev; + TopicFactory.get({id: $stateParams.id}, function(response) { $scope.topic = response.data; - $scope.topic.created = formatDateTime($scope.topic.created); - $scope.topic.modified = formatDateTime($scope.topic.modified); + $scope.topicCreated = formatDateTime($scope.topic.created); + $scope.topicModified = formatDateTime($scope.topic.modified); $scope.topicMeta = response.meta; $scope.queryTime = response.$queryTime; - $scope.wordSort = $scope.wordSort || 'likeliness'; - $scope.wordSortRev = typeof $scope.wordSortRev === 'undefined' ? true : $scope.wordSortRev; - - $scope.startRename = function() { - $scope.origName = $scope.topic.name; - $scope.isRename = true; - $timeout(function() { - $('#topicName').select(); - }, 0); - }; + }); - $scope.endRename = function(save) { + $scope.startRename = function() { + $scope.origName = $scope.topic.name; + $scope.isRename = true; + $timeout(function() { + $('#topicName').select(); + }, 0); + }; + + $scope.endRename = function(save) { + delete $scope.renameErrors; + if(save) { + TopicFactory.update({id:$scope.topic.id}, $scope.topic, function(response) { + $scope.topic = response.data; + $scope.isRename = false; + }, function(response) { + if(response.data) + $scope.renameErrors = getErrors(response.data.errors); + }); + } else { $scope.isRename = false; - if(save) { - // TODO implement - } else { - $scope.topic.name = $scope.origName; - } - }; + $scope.topic.name = $scope.origName; + } + }; - $scope.keyup = function($event) { - if($event.which === 13 || $event.which === 27) { - $scope.endRename($event.which === 13); - $event.preventDefault(); - } - }; - }); + $scope.keyup = function($event) { + if($event.which === 13 || $event.which === 27) { + $scope.endRename($event.which === 13); + $event.preventDefault(); + } + }; }]); @@ -418,7 +426,7 @@ $scope.page = Math.max($stateParams.page || 1, 1); $scope.limit = 300; - $scope.sort = Store('sortwords') || 'word'; + $scope.sort = Store('sortwords') || 'id'; $scope.order = Store('orderwords') || ''; $scope.reload = function() { @@ -451,11 +459,15 @@ WordFactory.get({id: $stateParams.id}, function(response) { $scope.word = response.data; - $scope.word.created = formatDateTime($scope.word.created); + $scope.wordCreated = formatDateTime($scope.word.created); $scope.wordMeta = response.meta; $scope.queryTime = response.$queryTime; }); + WordFactory.topics({id: $stateParams.id}, function(response) { + $scope.topics = response.data; + }); + }]); /**************************************************************************** diff --git a/vipra-ui/app/js/directives.js b/vipra-ui/app/js/directives.js index 93059f3c..2d05ad9c 100644 --- a/vipra-ui/app/js/directives.js +++ b/vipra-ui/app/js/directives.js @@ -155,6 +155,31 @@ }; }); + app.directive('bsAlert', function() { + return { + scope: { + type: '@', + dismissible: '@' + }, + transclude: true, + replace: true, + templateUrl: 'html/directives/alert.html', + link: function($scope) { + if(!$scope.type) { + console.log('no alert type given'); + return; + } + + $scope.dismissible = $scope.dismissible !== 'false'; + + var classes = 'alert alert-' + $scope.type; + if($scope.dismissible) + classes += ' alert-dismissible'; + $scope.classes = classes; + } + }; + }); + app.directive('sortBy', ['Store', function(Store) { return { scope: { diff --git a/vipra-ui/app/js/factories.js b/vipra-ui/app/js/factories.js index 80efd1c6..37a1632f 100644 --- a/vipra-ui/app/js/factories.js +++ b/vipra-ui/app/js/factories.js @@ -6,30 +6,32 @@ var app = angular.module('vipra.factories', []); - var endpoint = '//' + location.hostname + ':8000/vipra/rest'; + var endpoint = '//' + location.hostname + ':8080/vipra/rest'; app.factory('ArticleFactory', ['$resource', function($resource) { return $resource(endpoint + '/articles/:id', {}, { - query: { isArray: false, cache: true } + query: { isArray: false } }); }]); app.factory('TopicFactory', ['$resource', function($resource) { return $resource(endpoint + '/topics/:id', {}, { - query: { isArray: false, cache: true }, - articles: { method: 'GET', isArray: false, url: endpoint + '/topics/:id/articles', cache: true } + query: { isArray: false }, + update: { method: 'PUT' }, + articles: { url: endpoint + '/topics/:id/articles' } }); }]); app.factory('WordFactory', ['$resource', function($resource) { return $resource(endpoint + '/words/:id', {}, { - query: { isArray: false, cache: true } + query: { isArray: false }, + topics: { url: endpoint + '/words/:id/topics' } }); }]); app.factory('SearchFactory', ['$resource', function($resource) { return $resource(endpoint + '/search', {}, { - query: { isArray: false, cache: true } + query: { isArray: false } }); }]); diff --git a/vipra-ui/app/js/helpers.js b/vipra-ui/app/js/helpers.js index 31c3ef07..64f87779 100644 --- a/vipra-ui/app/js/helpers.js +++ b/vipra-ui/app/js/helpers.js @@ -27,6 +27,19 @@ return 'id' + Math.random().toString(36).substring(7); }; + window.console = window.console || { + log: function () {} + }; + + window.getErrors = function(errors) { + var html = []; + if(errors && errors.length) { + for(var i = 0; i < errors.length; i++) + html.push('<strong>' + errors[i].title + '</strong>: ' + errors[i].detail); + } + return html.join('<br>'); + } + String.prototype.ellipsize = function(max) { max = max || 20; if(this.length > max) { @@ -44,8 +57,4 @@ return this.lastIndexOf(start, 0) === 0; }; - window.console = window.console || { - log: function () {} - }; - })(); \ No newline at end of file diff --git a/vipra-ui/app/less/app.less b/vipra-ui/app/less/app.less index a1be9704..de6153ed 100644 --- a/vipra-ui/app/less/app.less +++ b/vipra-ui/app/less/app.less @@ -185,7 +185,13 @@ ul.dashed { } .item-actions { - padding: 5px 0 0 10px; + td { + vertical-align: middle; + } + + td + td { + padding-left: 10px; + } } .row { diff --git a/vipra-util/src/main/java/de/vipra/util/WordMap.java b/vipra-util/src/main/java/de/vipra/util/WordMap.java index 1613e819..ba1a02b2 100644 --- a/vipra-util/src/main/java/de/vipra/util/WordMap.java +++ b/vipra-util/src/main/java/de/vipra/util/WordMap.java @@ -30,7 +30,7 @@ public class WordMap { this.newWords = new HashSet<>(); List<Word> words = dbWords.getAll(); for (Word word : words) - wordMap.put(word.getWord().toLowerCase(), word); + wordMap.put(word.getId().toLowerCase(), word); } public Word get(Object w) { diff --git a/vipra-util/src/main/java/de/vipra/util/an/ConfigKey.java b/vipra-util/src/main/java/de/vipra/util/an/ConfigKey.java index 12577016..354ce783 100644 --- a/vipra-util/src/main/java/de/vipra/util/an/ConfigKey.java +++ b/vipra-util/src/main/java/de/vipra/util/an/ConfigKey.java @@ -5,6 +5,11 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * The ConfigKey field annotates configuration values. The key itself is the + * properties file key under which the value will be stored in the configuration + * file. + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ConfigKey { diff --git a/vipra-util/src/main/java/de/vipra/util/an/ElasticIndex.java b/vipra-util/src/main/java/de/vipra/util/an/ElasticIndex.java index b1d88bc6..055132c8 100644 --- a/vipra-util/src/main/java/de/vipra/util/an/ElasticIndex.java +++ b/vipra-util/src/main/java/de/vipra/util/an/ElasticIndex.java @@ -5,6 +5,10 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * The ElasticIndex annotation marks fields for use when creating and updating + * ElasticSearch indexes. + */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD, ElementType.METHOD }) public @interface ElasticIndex { diff --git a/vipra-util/src/main/java/de/vipra/util/an/QueryIgnore.java b/vipra-util/src/main/java/de/vipra/util/an/QueryIgnore.java index 8e2c7df1..21398b27 100644 --- a/vipra-util/src/main/java/de/vipra/util/an/QueryIgnore.java +++ b/vipra-util/src/main/java/de/vipra/util/an/QueryIgnore.java @@ -5,6 +5,11 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * The QueryIgnore annotation allows ignoring annotated fields on single, multi, + * and all resource requests. Example: ignore large fields on multi query to + * reduce payload size. + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface QueryIgnore { diff --git a/vipra-util/src/main/java/de/vipra/util/model/TopicFull.java b/vipra-util/src/main/java/de/vipra/util/model/TopicFull.java index ba27f175..126abc28 100644 --- a/vipra-util/src/main/java/de/vipra/util/model/TopicFull.java +++ b/vipra-util/src/main/java/de/vipra/util/model/TopicFull.java @@ -117,7 +117,7 @@ public class TopicFull implements Model<ObjectId>, Serializable { int size = Math.min(Constants.AUTO_TOPIC_WORDS, words.size()); List<String> topWords = new ArrayList<>(size); for (int i = 0; i < size; i++) { - topWords.add(words.get(i).getWord().getWord()); + topWords.add(words.get(i).getWord().getId()); } name = StringUtils.join(topWords); } diff --git a/vipra-util/src/main/java/de/vipra/util/model/TopicWord.java b/vipra-util/src/main/java/de/vipra/util/model/TopicWord.java index ad75e6f5..dcff0797 100644 --- a/vipra-util/src/main/java/de/vipra/util/model/TopicWord.java +++ b/vipra-util/src/main/java/de/vipra/util/model/TopicWord.java @@ -3,11 +3,11 @@ package de.vipra.util.model; import java.io.Serializable; import org.mongodb.morphia.annotations.Embedded; -import org.mongodb.morphia.annotations.PostLoad; import org.mongodb.morphia.annotations.Reference; +import com.fasterxml.jackson.annotation.JsonGetter; import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; @SuppressWarnings("serial") @Embedded @@ -17,9 +17,6 @@ public class TopicWord implements Comparable<TopicWord>, Serializable { @JsonIgnore private Word word; - @JsonProperty("id") - private String wordString; - private Double likeliness; public TopicWord() {} @@ -37,8 +34,14 @@ public class TopicWord implements Comparable<TopicWord>, Serializable { this.word = word; } + @JsonGetter("id") public String getWordString() { - return wordString; + return word.getId(); + } + + @JsonSetter("id") + public void setWordString(String word) { + this.word = new Word(word); } public Double getLikeliness() { @@ -64,9 +67,4 @@ public class TopicWord implements Comparable<TopicWord>, Serializable { return TopicWord.class.getSimpleName() + "[word:" + word + ", likeliness:" + likeliness + "]"; } - @PostLoad - private void postLoad() { - this.wordString = word.getWord(); - } - } diff --git a/vipra-util/src/main/java/de/vipra/util/model/Word.java b/vipra-util/src/main/java/de/vipra/util/model/Word.java index 268a8dc2..0bcd5395 100644 --- a/vipra-util/src/main/java/de/vipra/util/model/Word.java +++ b/vipra-util/src/main/java/de/vipra/util/model/Word.java @@ -2,7 +2,6 @@ package de.vipra.util.model; import java.io.Serializable; import java.util.Date; -import java.util.List; import org.mongodb.morphia.annotations.Entity; import org.mongodb.morphia.annotations.Id; @@ -22,23 +21,8 @@ import de.vipra.util.an.QueryIgnore; @Indexes(@Index("-created")) public class Word implements Model<String>, Serializable { - /** - * This is the id. It is used by the frontend, which expects an 'id' field. - * This field is populated on load from the database and it is not stored. - */ - @Transient - private String id; - - /** - * This is the actual word. It is used as the database id and is not - * returned to the frontend. - */ @Id - @JsonIgnore - private String word; - - @Transient - private List<TopicFull> topics; + private String id; @QueryIgnore(multi = true) private Date created; @@ -54,8 +38,8 @@ public class Word implements Model<String>, Serializable { public Word() {} - public Word(String word) { - this.word = word; + public Word(String id) { + this.id = id; } @Override @@ -68,23 +52,6 @@ public class Word implements Model<String>, Serializable { this.id = id; } - public String getWord() { - return word; - } - - public void setWord(String word) { - this.word = word; - this.id = word; - } - - public List<TopicFull> getTopics() { - return topics; - } - - public void setTopics(List<TopicFull> topics) { - this.topics = topics; - } - public boolean isCreated() { return isCreated; } @@ -104,7 +71,6 @@ public class Word implements Model<String>, Serializable { @PostLoad @PostPersist private void postLoadPersist() { - this.id = word; this.isCreated = true; } diff --git a/vipra-util/src/main/java/de/vipra/util/service/MongoService.java b/vipra-util/src/main/java/de/vipra/util/service/MongoService.java index d5dcc1f7..9ae45b21 100644 --- a/vipra-util/src/main/java/de/vipra/util/service/MongoService.java +++ b/vipra-util/src/main/java/de/vipra/util/service/MongoService.java @@ -117,7 +117,7 @@ public class MongoService<Type extends Model<IdType>, IdType> implements Service } @Override - public void updateSingle(Type t) throws DatabaseException { + public void updateSingle(Type t, String... fields) throws DatabaseException { datastore.save(t); } diff --git a/vipra-util/src/main/java/de/vipra/util/service/Service.java b/vipra-util/src/main/java/de/vipra/util/service/Service.java index b193737c..99808fdf 100644 --- a/vipra-util/src/main/java/de/vipra/util/service/Service.java +++ b/vipra-util/src/main/java/de/vipra/util/service/Service.java @@ -98,9 +98,11 @@ public interface Service<Type extends Model<IdType>, IdType, E extends Exception * * @param t * Entity to be updated + * @param fields + * Fields to be updated * @throws E */ - void updateSingle(Type t) throws E; + void updateSingle(Type t, String... fields) throws E; /** * Drop all entities from the database -- GitLab