diff --git a/src/main/config/openapi/openapi-user.yaml b/src/main/config/openapi/openapi-user.yaml index dbe61ca1b..66ecca377 100644 --- a/src/main/config/openapi/openapi-user.yaml +++ b/src/main/config/openapi/openapi-user.yaml @@ -335,6 +335,91 @@ paths: '500': $ref: '#/components/responses/InternalServerError' + /favorites: + get: + tags: + - favorite + summary: List favorites + description: Returns favorited document IDs + operationId: listFavorites + parameters: + - name: queryId + in: query + description: Query ID where the document is contained + required: true + schema: + type: string + example: queryid + responses: + '200': + description: Successful operation + content: + application/json: + schema: + type: object + properties: + record_count: + type: integer + example: 9 + data: + type: array + items: + type: object + properties: + doc_id: + type: string + example: "e79fbfdfb09d4bffb58ec230c68f6f7e" + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + + /documents/{docId}/favorite: + post: + tags: + - favorite + summary: Set a favorite mark + description: Set a favorite mark to the document + operationId: setFavorite + parameters: + - name: docId + in: path + description: Document ID to be favorited + required: true + schema: + type: string + example: documentid + - name: queryId + in: query + description: Query ID where the document is contained + required: true + schema: + type: string + example: queryid + responses: + '201': + description: Successful operation + content: + application/json: + schema: + type: object + properties: + result: + type: string + example: created + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + /popular-words: get: tags: diff --git a/src/main/java/org/codelibs/fess/api/json/SearchApiManager.java b/src/main/java/org/codelibs/fess/api/json/SearchApiManager.java index c73d9738a..c556d9c94 100644 --- a/src/main/java/org/codelibs/fess/api/json/SearchApiManager.java +++ b/src/main/java/org/codelibs/fess/api/json/SearchApiManager.java @@ -87,9 +87,15 @@ public class SearchApiManager extends BaseApiManager { private static final Logger logger = LogManager.getLogger(SearchApiManager.class); - private static final String MESSAGE_FIELD = "message"; + protected static final String MESSAGE_FIELD = "message"; - private static final String RESULT_FIELD = "result"; + protected static final String RESULT_FIELD = "result"; + + private static final String DOC_ID_FIELD = "doc_id"; + + protected static final String GET = "GET"; + + protected static final String POST = "POST"; protected String mimeType = "application/json"; @@ -115,7 +121,10 @@ public class SearchApiManager extends BaseApiManager { } final String type = value.toLowerCase(Locale.ROOT); if ("documents".equals(type)) { - // return FormatType.FAVORITE; + if (values.length > 5 && "favorite".equals(values[5])) { + request.setAttribute(DOC_ID_FIELD, values[4]); + return FormatType.FAVORITE; + } // return FormatType.SCROLL; return FormatType.SEARCH; } else if ("labels".equals(type)) { @@ -128,6 +137,8 @@ public class SearchApiManager extends BaseApiManager { return FormatType.PING; } else if ("suggest-words".equals(type)) { return FormatType.SUGGEST; + } else if ("scroll".equals(type)) { + return FormatType.SCROLL; } else { // default return FormatType.OTHER; @@ -179,8 +190,22 @@ public class SearchApiManager extends BaseApiManager { } } + protected boolean acceptHttpMethod(final HttpServletRequest request, final String... methods) { + final String method = request.getMethod(); + for (String m : methods) { + if (m.equals(method)) { + return true; + } + } + writeJsonResponse(HttpServletResponse.SC_METHOD_NOT_ALLOWED, escapeJsonKeyValue(MESSAGE_FIELD, method + " is not allowed.")); + return false; + } + protected void processScrollSearchRequest(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) { + if (!acceptHttpMethod(request, GET)) { + return; + } final SearchHelper searchHelper = ComponentUtil.getSearchHelper(); final FessConfig fessConfig = ComponentUtil.getFessConfig(); @@ -231,12 +256,12 @@ public class SearchApiManager extends BaseApiManager { } } catch (final InvalidQueryException | ResultOffsetExceededException e) { if (logger.isDebugEnabled()) { - logger.debug("Failed to process a search request.", e); + logger.debug("Failed to process a scroll request.", e); } writeJsonResponse(HttpServletResponse.SC_BAD_REQUEST, e); } catch (final Exception e) { if (logger.isDebugEnabled()) { - logger.debug("Failed to process a search request.", e); + logger.debug("Failed to process a scroll request.", e); } writeJsonResponse(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); } @@ -244,6 +269,10 @@ public class SearchApiManager extends BaseApiManager { } protected void processPingRequest(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) { + if (!acceptHttpMethod(request, GET)) { + return; + } + final SearchEngineClient searchEngineClient = ComponentUtil.getSearchEngineClient(); try { final PingResponse pingResponse = searchEngineClient.ping(); @@ -258,6 +287,10 @@ public class SearchApiManager extends BaseApiManager { } protected void processSearchRequest(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) { + if (!acceptHttpMethod(request, GET)) { + return; + } + final SearchHelper searchHelper = ComponentUtil.getSearchHelper(); final FessConfig fessConfig = ComponentUtil.getFessConfig(); final RelatedQueryHelper relatedQueryHelper = ComponentUtil.getRelatedQueryHelper(); @@ -461,6 +494,10 @@ public class SearchApiManager extends BaseApiManager { } protected void processLabelRequest(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) { + if (!acceptHttpMethod(request, GET)) { + return; + } + final LabelTypeHelper labelTypeHelper = ComponentUtil.getLabelTypeHelper(); final StringBuilder buf = new StringBuilder(255); @@ -499,6 +536,10 @@ public class SearchApiManager extends BaseApiManager { protected void processPopularWordRequest(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) { + if (!acceptHttpMethod(request, GET)) { + return; + } + if (!ComponentUtil.getFessConfig().isWebApiPopularWord()) { writeJsonResponse(HttpServletResponse.SC_BAD_REQUEST, escapeJsonKeyValue(MESSAGE_FIELD, "Unsupported operation.")); return; @@ -545,6 +586,10 @@ public class SearchApiManager extends BaseApiManager { } protected void processFavoriteRequest(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) { + if (!acceptHttpMethod(request, POST)) { + return; + } + if (!ComponentUtil.getFessConfig().isUserFavorite()) { writeJsonResponse(HttpServletResponse.SC_BAD_REQUEST, escapeJsonKeyValue(MESSAGE_FIELD, "Unsupported operation.")); return; @@ -557,7 +602,11 @@ public class SearchApiManager extends BaseApiManager { final SystemHelper systemHelper = ComponentUtil.getSystemHelper(); try { - final String docId = request.getParameter("docId"); + final Object docIdObj = request.getAttribute(DOC_ID_FIELD); + if (docIdObj == null) { + throw new WebApiException(HttpServletResponse.SC_BAD_REQUEST, "docId is empty."); + } + final String docId = docIdObj.toString(); final String queryId = request.getParameter("queryId"); final String[] docIds = userInfoHelper.getResultDocIds(URLDecoder.decode(queryId, Constants.UTF_8)); @@ -630,6 +679,10 @@ public class SearchApiManager extends BaseApiManager { } protected void processFavoritesRequest(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) { + if (!acceptHttpMethod(request, GET)) { + return; + } + if (!ComponentUtil.getFessConfig().isUserFavorite()) { writeJsonResponse(HttpServletResponse.SC_BAD_REQUEST, escapeJsonKeyValue(MESSAGE_FIELD, "Unsupported operation.")); return; @@ -676,14 +729,14 @@ public class SearchApiManager extends BaseApiManager { } final StringBuilder buf = new StringBuilder(255); - buf.append("\"num\":").append(docIdList.size()); - buf.append(", \"doc_ids\":["); + buf.append("\"record_count\":").append(docIdList.size()); + buf.append(", \"data\":["); if (!docIdList.isEmpty()) { for (int i = 0; i < docIdList.size(); i++) { if (i > 0) { buf.append(','); } - buf.append(escapeJson(docIdList.get(i))); + buf.append('{').append(escapeJsonKeyValue(DOC_ID_FIELD, docIdList.get(i))).append('}'); } } buf.append(']'); @@ -704,6 +757,10 @@ public class SearchApiManager extends BaseApiManager { protected void processSuggestRequest(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) throws IOException, ServletException { + if (!acceptHttpMethod(request, GET)) { + return; + } + final FessConfig fessConfig = ComponentUtil.getFessConfig(); if (!fessConfig.isAcceptedSearchReferer(request.getHeader("referer"))) { writeJsonResponse(HttpServletResponse.SC_BAD_REQUEST, escapeJsonKeyValue(MESSAGE_FIELD, "Referer is invalid.")); diff --git a/src/main/webapp/js/search.js b/src/main/webapp/js/search.js index 386369247..c58722274 100644 --- a/src/main/webapp/js/search.js +++ b/src/main/webapp/js/search.js @@ -93,8 +93,8 @@ $(function() { actionUrl, docId; if (values.length === 2 && $queryId.length > 0) { - actionUrl = contextPath + "/json"; docId = values[1]; + actionUrl = contextPath + "/api/v1/documents/" + docId + "/favorite"; $.ajax({ dataType: "json", cache: false, @@ -102,19 +102,13 @@ $(function() { timeoutNumber: 10000, url: actionUrl, data: { - type: "favorite", - docId: docId, queryId: $queryId.val() } }) .done(function(data) { var $favorited, $favoritedCount; - if ( - data.response.status === 0 && - typeof data.response.result !== "undefined" && - data.response.result === "ok" - ) { + if (data.result === "created") { $favorited = $favorite.siblings(".favorited"); $favoritedCount = $(".favorited-count", $favorited); $favoritedCount.css("display", "none"); @@ -136,25 +130,20 @@ $(function() { $.ajax({ dataType: "json", cache: false, - type: "post", + type: "get", timeoutNumber: 10000, - url: contextPath + "/json", + url: contextPath + "/api/v1/favorites", data: { - type: "favorites", queryId: $queryId.val() } }) .done(function(data) { var docIds, i; - if ( - data.response.status === 0 && - typeof data.response.num !== "undefined" && - data.response.num > 0 - ) { - docIds = data.response.doc_ids; + if (data.record_count > 0) { + docIds = data.data; for (i = 0; i < docIds.length; i++) { - docIds[i] = "#" + docIds[i]; + docIds[i] = "#" + docIds[i].doc_id; } $favorites.each(function(index) { var $favorite = $(this),