/* * Copyright 2012 Nodeable Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.streamreduce.rest.resource.api; import com.streamreduce.core.event.EventId; import com.streamreduce.core.model.Event; import com.streamreduce.core.model.User; import com.streamreduce.core.model.messages.MessageComment; import com.streamreduce.core.model.messages.SobaMessage; import com.streamreduce.core.service.EmailService; import com.streamreduce.core.service.exception.MessageNotFoundException; import com.streamreduce.core.service.exception.TargetNotFoundException; import com.streamreduce.rest.dto.response.MessageCommentResponseDTO; import com.streamreduce.rest.dto.response.MessageCommentsResponseDTO; import com.streamreduce.rest.dto.response.SobaMessageResponseDTO; import com.streamreduce.rest.resource.ErrorMessages; import com.streamreduce.util.MessageUtils; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import net.sf.json.JSONObject; import org.bson.types.ObjectId; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component @Path("api/messages") @Produces(MediaType.APPLICATION_JSON) public class MessageResource extends AbstractTagableSobaResource { @Autowired private EmailService emailService; /** * Creates a new USER generated message. Use created messages will show up as "from" the user who created it, either as a DM or an * Account level message. * * @param json - The message to create * @return - Response.Status.CREATED and a DTO of SobaMessage that was created. * @response.representation.201.doc Returned when the message was successfully created and sent * @response.representation.400.doc Returned when the client attempts to create a user message that is empty or sends to an invalid target. */ @POST @Consumes(MediaType.APPLICATION_JSON) public Response addMessage(JSONObject json) { String message = getJSON(json, "message"); if (isEmpty(message)) { return error("Message payload can not be empty", Response.status(Response.Status.BAD_REQUEST)); } SobaMessage sobaMessage; User sender = applicationManager.getSecurityService().getCurrentUser(); Map<String, Object> eventContext = new HashMap<>(); eventContext.put("message", message); eventContext.put("payload", json); Event event = applicationManager.getEventService().createEvent(EventId.USER_MESSAGE, null, eventContext); try { sobaMessage = applicationManager.getMessageService().sendUserMessage(event, sender, message); } catch (TargetNotFoundException e) { return error(e.getMessage(), Response.status(Response.Status.BAD_REQUEST)); } return Response .status(Response.Status.CREATED) .entity(SobaMessageResponseDTO.fromSobaMessage(sobaMessage)) .build(); } /** * Retrieves all messages for the client. * * @param after - time in ms, after this date * @param before - time in ms, before this date * @param limit - only return this value, optional, but capped at something like 100 if set to 0 * @param ascending - default to true * @param search - string to search on * @param fullText - get the full message payload, or else truncate it * @param hashtags - limits results to those that only contain all of the specified hashtags * @param sender - limits results to only those messages whose sender is specified. sender can represent the id or alias of the sender * @return DTO of messages * @response.representation.200.doc Returned when all messages were successfully rendered */ @GET public Response getAllMessages(@QueryParam("after") Long after, @QueryParam("before") Long before, @QueryParam("limit") int limit, @DefaultValue("true") @QueryParam("ascending") boolean ascending, @QueryParam("search") String search, @QueryParam("fullText") boolean fullText, @QueryParam("hashtag") List<String> hashtags, @QueryParam("sender") String sender, @QueryParam("excludeInsights") boolean excludeInsights) { User user = applicationManager.getSecurityService().getCurrentUser(); List<SobaMessage> allMessages = applicationManager.getMessageService().getAllMessages(user, after, before, limit, ascending, search, hashtags, sender, excludeInsights); return Response .ok(SobaMessageResponseDTO.fromSobaMessages(allMessages, fullText)) .build(); } /** * Retreives the full contents of a message given the message id. * * @param messageId -- messageId * @return - will only return the message if you can access it (ie, if it's a DIRECT message to you * or a subscriber message, etc. * @response.representation.200.doc Returned when all direct messages were successfully rendered * @response.representation.404.doc Returned when a message could not be found given the ID for the client */ @GET @Path("{messageId}") public Response getMessageById(@PathParam("messageId") ObjectId messageId) { User user = applicationManager.getSecurityService().getCurrentUser(); SobaMessage sobaMessage; try { sobaMessage = applicationManager.getMessageService().getMessage(user.getAccount(), messageId); } catch (MessageNotFoundException e) { return error(e.getMessage(), Response.status(Response.Status.NOT_FOUND)); } return Response .ok(SobaMessageResponseDTO.fromSobaMessage(sobaMessage)) .build(); } @DELETE @Path("{messageId}") public Response deleteMessage(@PathParam("messageId") ObjectId messageId) { User user = applicationManager.getSecurityService().getCurrentUser(); try { SobaMessage sobaMessage = applicationManager.getMessageService().getMessage(user.getAccount(), messageId); // users can only nullify a message from a resource they own if (sobaMessage.getOwnerId().equals(user.getId())) { applicationManager.getMessageService().nullifyMessage(user, sobaMessage); } else { return error(ErrorMessages.APPLICATION_ACCESS_DENIED, Response.status(Response.Status.BAD_REQUEST)); } } catch (MessageNotFoundException e) { return error(e.getMessage(), Response.status(Response.Status.NOT_FOUND)); } return Response.ok().build(); } /** * Add a comment or a tag (via the comment) to a given message. * * @param messageId - messageId * @param json - the comment payload * @return no content * @response.representation.204.doc Returned when a comment or tag was added to a message. * @response.representation.400.doc Returned when the client attempts to add an empty comment * @response.representation.404.doc Returned when a message being added could not be found given the ID for the client */ @PUT @Path("{messageId}/comment") @Consumes(MediaType.APPLICATION_JSON) public Response addCommentToMessage(@PathParam("messageId") ObjectId messageId, JSONObject json) { String comment = getJSON(json, "comment"); if (isEmpty(comment)) { return error(ErrorMessages.MISSING_REQUIRED_FIELD + "comment", Response.status(Response.Status.BAD_REQUEST)); } User user = applicationManager.getSecurityService().getCurrentUser(); // check if the user added any tags MessageUtils.ParsedMessage parsedMessage = MessageUtils.parseMessage(comment); Set<String> hashtags = parsedMessage.getTags(); MessageComment messageComment = new MessageComment(user, parsedMessage.getMessage()); try { SobaMessage sobaMessage = applicationManager.getMessageService().getMessage(user.getAccount(), messageId); // this is now a conversation, so add that tag hashtags.add("#conversation"); applicationManager.getMessageService().addCommentToMessage(user.getAccount(), sobaMessage.getId(), messageComment, hashtags); } catch (MessageNotFoundException e) { return error(e.getMessage(), Response.status(Response.Status.NOT_FOUND)); } return Response .status(Response.Status.NO_CONTENT) .build(); } /** * Retrieves all comments that have been added to a given message * * @param messageId the id of the messsage that comments will be returned for * @return message comment DTO * @response.representation.200.doc Returned when all comments to a message were succesfully rendered * @response.representation.404.doc Returned when the message does not exist */ @GET @Path("{messageId}/comment") public Response getCommentsForMessage(@PathParam("messageId") ObjectId messageId) { User user = applicationManager.getSecurityService().getCurrentUser(); SobaMessage sobaMessage; try { sobaMessage = applicationManager.getMessageService().getMessage(user.getAccount(), messageId); } catch (MessageNotFoundException e) { return error(e.getMessage(), Response.status(Response.Status.NOT_FOUND)); } MessageCommentsResponseDTO dto = new MessageCommentsResponseDTO(); List<MessageCommentResponseDTO> allComments = new ArrayList<>(); for (MessageComment comment : sobaMessage.getComments()) { allComments.add(comment.toDTO()); } dto.setComments(allComments); return Response .ok(dto) .build(); } /** * Remove the comment from the User on the timestamp indicated, since we don't have a messageId * * @return */ @DELETE @Path("{messageId}/comment/{created}") public Response deleteCommentForMessage(@PathParam("messageId") ObjectId messageId, @PathParam("created") Long created) { User user = applicationManager.getSecurityService().getCurrentUser(); try { SobaMessage sobaMessage = applicationManager.getMessageService().getMessage(user.getAccount(), messageId); for (MessageComment messageComment : sobaMessage.getComments()) { // find the message comment to update if (messageComment.getSenderId().equals(user.getId()) && messageComment.getCreated().equals(created)) { applicationManager.getMessageService().nullifyMessageComment(user, sobaMessage, messageComment); break; } } } catch (MessageNotFoundException e) { return error(e.getMessage(), Response.status(Response.Status.NOT_FOUND)); } return Response .ok() .build(); } /** * Applies a hashtag directly to a message * * @param messageId - internal Id of message * @param json - hasstag dto * @return - 200 OK * @response.representation.200.doc Returned when a hashtag was successfully added to a message * @response.representation.400.doc Returned when a client attempts to add an empty hashtag or the message can not be found */ @POST @Path("{messageId}/hashtag") @Consumes(MediaType.APPLICATION_JSON) @Override public Response addTag(@PathParam("messageId") String messageId, JSONObject json) { String hashtag = getJSON(json, HASHTAG); if (isEmpty(hashtag)) { return error("Hashtag payload is empty", Response.status(Response.Status.BAD_REQUEST)); } User user = applicationManager.getSecurityService().getCurrentUser(); try { applicationManager.getMessageService().addHashtagToMessage(user.getAccount(), new ObjectId(messageId), hashtag); } catch (MessageNotFoundException e) { return error(e.getMessage(), Response.status(Response.Status.BAD_REQUEST)); } return Response .ok() .build(); } /** * Retrieves a list of all hashtags currently applied to a given message. * * @param messageId * @response.representation.200.doc Returned when a hashtags for the given message were successfully rendered * @response.representation.400.doc Returned the client does not have access to the message * @response.representation.404.doc Returned when the message that hashtags are requested for can't be found */ @GET @Path("{messageId}/hashtag") @Override public Response getTags(@PathParam("messageId") String messageId) { User user = applicationManager.getSecurityService().getCurrentUser(); Set<String> tags; try { SobaMessage sobaMessage = applicationManager.getMessageService().getMessage(user.getAccount(), new ObjectId(messageId)); tags = sobaMessage.getHashtags(); } catch (MessageNotFoundException e) { return error(e.getMessage(), Response.status(Response.Status.NOT_FOUND)); } return Response .ok(tags) .build(); } /** * Remove a hashtag from a given message. * * @param messageId * @param hashtag * @return * @response.representation.200.doc Returned when the hashtag for the message was removed successfully. * @response.representation.400.doc Returned the client does not have access to the message * @response.representation.404.doc Returned when the message that hashtags are requested for can't be found */ @DELETE @Path("{messageId}/hashtag/{tagname}") @Override public Response removeTag(@PathParam("messageId") String messageId, @PathParam("tagname") String hashtag) { if (isEmpty(hashtag)) { return error("Hashtag payload is empty", Response.status(Response.Status.BAD_REQUEST)); } User user = applicationManager.getSecurityService().getCurrentUser(); try { applicationManager.getMessageService().removeHashtagFromMessage(user.getAccount(), new ObjectId(messageId), hashtag); } catch (MessageNotFoundException e) { return error(e.getMessage(), Response.status(Response.Status.BAD_REQUEST)); } return Response.ok().build(); } @POST @Path("{messageId}/email") @Consumes(MediaType.APPLICATION_JSON) public Response email(@PathParam("messageId") ObjectId messageId, JSONObject json) { User user = applicationManager.getSecurityService().getCurrentUser(); SobaMessage message; try { message = applicationManager.getMessageService().getMessage(user.getAccount(), messageId); } catch (MessageNotFoundException mnfe) { logger.error("Unable to find message with ID {}", messageId); return Response.status(404).build(); } emailService.sendUserMessageEmail(user, message, json); return Response .ok() .build(); } }