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 241a0a322e01ea3b4728b26b029064ec926477d2..2a3714b4dd05085370f4648d2edb324fc3bb05c9 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 a5733b72762fc2a78859341186adc8944e67f8ba..a203c319058450c835c190ffed646eca9e49fe11 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 4d2512d0680b5d6953f9400f19cab4689235478d..a1b3e03a4ad4bfd939f9c9f5fe2ab33cbebb6c0e 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 ad2abe9657655038fec44e26ba267b41684fa69d..6e15887984f366576be421f248b27105b4cd61b2 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 7c5a3a6072caeba2f70963e8af39dfec6312ba4e..45868a68a05c786f9df2ebb5f1860391f60d490a 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 e5f4708a6a06468058c3116f9cf00228422ff6ec..7a7e9ad8cba7e7ff63b97a4ce4846853d53bf8cb 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 0000000000000000000000000000000000000000..441726d4f24504505d8be94a5b8b05f3a65808e4 --- /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 c73784d554b015eedb7bd85777468bb940a0dfa2..3eac37a54100df93353100043d209b7551c3d6e1 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 753ba0cb6fb51391e2ce0ab4f73a108cddab9836..b82d508a5eb0310ad796ef763ab30a76d14bbc08 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 d0850631087c163741b71d4adb5c5577feedef64..1029777136968351c6b7eaa40e02b7c446a93111 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 749c267fd716f73df8c5baf240b26c1cc5db0a79..c5e0cf03ecb2023cea4535976f1d2f38e28ae8c5 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 8c4471f6423f35b1fd30c146947a6b40f004095a..319bbda45fb5260143e2d11e49421dea9781b090 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 be65d8ccde29f2e537f8b56e32c985c8970e7565..e0a708a3ebb8df4780b650797562994649684350 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 cdbd83dd059c70b0a3508d3f0b8ee3b105836570..ae8935fd3dd446f873d682de949680e001de4e0d 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 93059f3c3aea253225ed88aff6e02bdfe8ab90a4..2d05ad9c3fca2d57bc73bbbb83b33dccf0e1c2e1 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 80efd1c61b77bff3443195c9b5753f8673be29ce..37a1632fc03d42fbb7975c636cc8095ae4903da5 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 31c3ef07289f5754dae26222e3726269e171fead..64f87779659b9a80b8caaf30409e084f12f1947c 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 a1be97047b06d257b015252a398026ede6957952..de6153ed540f3bb905948e34934b4a698569da53 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 1613e8192bef59b7e63640a181b18d2a0cdae57c..ba1a02b25c1f616feac92669f3318650687017a5 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 12577016f2bd75d7b0a1043da7e8299de4bb3906..354ce7839de3bceb33aff469acd9d3e8b68cbfe1 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 b1d88bc6a1aa795a1e4bc7aae6df17b61c49d357..055132c88fa64c7ed731d95816960beba915f906 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 8e2c7df19d58e8ef23039a749ecde931a12ca96d..21398b273a03512967411f9deabefa38ea8efef4 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 ba27f1753231f123869b9a66cb0f5e68aa72da35..126abc28a6e6e618cf8c9a944b7c51c6b01eae1e 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 ad75e6f5b947e577a7d68281babf051af087f92e..dcff079775e7f2448772a2fb5919c90b35d7c74e 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 268a8dc2b899e10b7ea842fd3bac22892ae12809..0bcd53950ee4a27b421802692acdc84ec007587d 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 d5dcc1f7849cb499c988d2f0f344150ed44fcbd0..9ae45b21c70dbe04c4477131d2baf1f84469de3b 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 b193737c2ef447afc360c366d849887daef720e0..99808fdf9075410069193046cd74c324e76c8cda 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