From 7384a6b597bfe48db006250b20f712d2a8c26f4d Mon Sep 17 00:00:00 2001 From: Thore Date: Thu, 13 May 2021 17:30:16 +0200 Subject: [PATCH 1/3] Add implementation for a user dashboard --- .../acis/bazaar/service/dal/DALFacade.java | 42 ++++++++- .../bazaar/service/dal/DALFacadeImpl.java | 30 ++++++- .../service/dal/entities/Dashboard.java | 34 ++++++++ .../bazaar/service/dal/helpers/PageInfo.java | 22 +++-- .../dal/repositories/CategoryRepository.java | 2 + .../repositories/CategoryRepositoryImpl.java | 26 ++++++ .../dal/repositories/ProjectRepository.java | 3 +- .../repositories/ProjectRepositoryImpl.java | 35 +++++++- .../repositories/RequirementRepository.java | 3 +- .../RequirementRepositoryImpl.java | 38 +++++++- .../service/resources/UsersResource.java | 87 +++++++++++++++---- .../dbis/acis/bazaar/service/BazaarTest.java | 41 +++++++++ 12 files changed, 329 insertions(+), 34 deletions(-) create mode 100644 reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/entities/Dashboard.java diff --git a/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/DALFacade.java b/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/DALFacade.java index 5cd7b81e..9fde65a8 100644 --- a/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/DALFacade.java +++ b/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/DALFacade.java @@ -186,8 +186,9 @@ public interface DALFacade { /** * Deletes a given project + * * @param projectId id of the project to delete - * @param userId id of the user + * @param userId id of the user */ Project deleteProjectById(int projectId, Integer userId) throws Exception; @@ -226,6 +227,16 @@ public interface DALFacade { * @throws BazaarException */ void removeUserFromProject(int userId, Integer context) throws BazaarException; + + /** + * Returns the count most recent active projects followed by the user + * + * @param userId id of the follower + * @param count how many should be returned + * @return Followed projects ordered by last activity + */ + List getFollowedProjects(int userId, int count) throws BazaarException; + //endregion //region ProjectFollower @@ -364,6 +375,15 @@ public interface DALFacade { boolean isRequirementPublic(int requirementId) throws BazaarException; Statistic getStatisticsForRequirement(int userId, int requirementId, Calendar timestamp) throws BazaarException; + + /** + * Returns the count most recent active requirements followed by the user + * + * @param userId id of the follower + * @param count how many should be returned + * @return Followed requirements ordered by last activity + */ + List getFollowedRequirements(int userId, int count) throws BazaarException; //endregion //region Category @@ -417,6 +437,15 @@ public interface DALFacade { boolean isCategoryPublic(int categoryId) throws BazaarException; Statistic getStatisticsForCategory(int userId, int categoryId, Calendar timestamp) throws BazaarException; + + /** + * Returns the count most recent active categories followed by the user + * + * @param userId id of the follower + * @param count how many should be returned + * @return Followed categories ordered by last activity + */ + List getFollowedCategories(int userId, int count) throws BazaarException; //endregion //region CategoryFollower @@ -508,6 +537,7 @@ public interface DALFacade { /** * Updates a comment + * * @param comment comment to persist * @return the updated comment * @throws Exception @@ -681,4 +711,14 @@ public interface DALFacade { Feedback getFeedbackById(int feedbackId) throws Exception; // endregion feedback + + /** + * Aggregates the data for the dashboard + * + * @param userId Id of the user for their individual dashboard + * @param count Number of items per group + * @return + * @throws BazaarException + */ + Dashboard getDashboardData(int userId, int count) throws BazaarException; } diff --git a/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/DALFacadeImpl.java b/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/DALFacadeImpl.java index 6aba7867..a561a6b4 100644 --- a/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/DALFacadeImpl.java +++ b/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/DALFacadeImpl.java @@ -271,6 +271,13 @@ public PaginationResult listFollowersForProject(int projectId, Pageable pa return userRepository.findAllByFollowing(projectId, 0, 0, pageable); } + @Override + public List getFollowedProjects(int userId, int count) throws BazaarException { + projectRepository = (projectRepository != null) ? projectRepository : new ProjectRepositoryImpl(dslContext); + return projectRepository.getFollowedProjects(userId, count); + } + + @Override public List listRequirements(Pageable pageable) throws BazaarException { requirementRepository = (requirementRepository != null) ? requirementRepository : new RequirementRepositoryImpl(dslContext); @@ -395,6 +402,12 @@ public Statistic getStatisticsForRequirement(int userId, int requirementId, Cale return requirementRepository.getStatisticsForRequirement(userId, requirementId, timestamp.toLocalDateTime()); } + @Override + public List getFollowedRequirements(int userId, int count) throws BazaarException { + requirementRepository = (requirementRepository != null) ? requirementRepository : new RequirementRepositoryImpl(dslContext); + return requirementRepository.getFollowedRequirements(userId, count); + } + @Override public PaginationResult listCategoriesByProjectId(int projectId, Pageable pageable, int userId) throws BazaarException { categoryRepository = (categoryRepository != null) ? categoryRepository : new CategoryRepositoryImpl(dslContext); @@ -462,6 +475,12 @@ public Statistic getStatisticsForCategory(int userId, int categoryId, Calendar s return categoryRepository.getStatisticsForCategory(userId, categoryId, timestamp.toLocalDateTime()); } + @Override + public List getFollowedCategories(int userId, int count) throws BazaarException { + categoryRepository = (categoryRepository != null) ? categoryRepository : new CategoryRepositoryImpl(dslContext); + return categoryRepository.getFollowedCategories(userId, count); + } + @Override public PaginationResult listFollowersForCategory(int categoryId, Pageable pageable) throws BazaarException { userRepository = (userRepository != null) ? userRepository : new UserRepositoryImpl(dslContext); @@ -541,7 +560,7 @@ public Comment deleteCommentById(int commentId) throws Exception { comment.setDeleted(true); comment.setMessage("[This message has been deleted]"); commentRepository.update(comment); - } else{ + } else { commentRepository.delete(commentId); } return comment; @@ -742,6 +761,15 @@ public Feedback getFeedbackById(int feedbackId) throws Exception { return feedbackRepository.findById(feedbackId); } + @Override + public Dashboard getDashboardData(int userId, int count) throws BazaarException { + return Dashboard.builder() + .projects(getFollowedProjects(userId, count)) + .categories(getFollowedCategories(userId, count)) + .requirements(getFollowedRequirements(userId, count)) + .build(); + } + @Override public PaginationResult getProjectMembers(int projectId, Pageable pageable) throws BazaarException { roleRepository = (roleRepository != null) ? roleRepository : new RoleRepositoryImpl(dslContext); diff --git a/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/entities/Dashboard.java b/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/entities/Dashboard.java new file mode 100644 index 00000000..40ba5361 --- /dev/null +++ b/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/entities/Dashboard.java @@ -0,0 +1,34 @@ +package de.rwth.dbis.acis.bazaar.service.dal.entities; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import de.rwth.dbis.acis.bazaar.service.dal.helpers.UserVote; +import io.swagger.annotations.ApiModelProperty; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.extern.jackson.Jacksonized; + +import javax.validation.constraints.NotNull; +import java.util.List; + +@EqualsAndHashCode(callSuper = true) +@Data +@Jacksonized +@Builder(builderClassName = "Builder") +public class Dashboard extends EntityBase { + + @NotNull + private List projects; + + @NotNull + private List categories; + + @NotNull + private List requirements; + + @JsonIgnore + @Override + public int getId() { + return 0; + } +} diff --git a/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/helpers/PageInfo.java b/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/helpers/PageInfo.java index ee33c88c..5b90855d 100644 --- a/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/helpers/PageInfo.java +++ b/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/helpers/PageInfo.java @@ -40,7 +40,7 @@ public class PageInfo implements Pageable { private final List sorts; private final String search; private final List ids; - private final List embed; + private final List embed; public PageInfo(int pageNumber, int pageSize) { this(pageNumber, pageSize, new HashMap<>(), new ArrayList<>(), null, null); @@ -50,15 +50,21 @@ public PageInfo(int pageNumber, int pageSize) { public PageInfo(int pageNumber, int pageSize, Map filters) { this(pageNumber, pageSize, filters, new ArrayList<>(), null); } - public PageInfo(int pageNumber, int pageSize, Map filters, List sorts, String search){ - this(pageNumber, pageSize, filters, sorts, search,null); + + public PageInfo(int pageNumber, int pageSize, Map filters, List sorts) { + this(pageNumber, pageSize, filters, sorts, null); } - public PageInfo(int pageNumber, int pageSize, Map filters, List sorts, String search, List ids){ - this(pageNumber, pageSize, filters, sorts, search,ids, null); + + public PageInfo(int pageNumber, int pageSize, Map filters, List sorts, String search) { + this(pageNumber, pageSize, filters, sorts, search, null); } + public PageInfo(int pageNumber, int pageSize, Map filters, List sorts, String search, List ids) { + this(pageNumber, pageSize, filters, sorts, search, ids, null); + } - public PageInfo(int pageNumber, int pageSize, Map filters, List sorts, String search, List ids, List embed) { + + public PageInfo(int pageNumber, int pageSize, Map filters, List sorts, String search, List ids, List embed) { this.pageNumber = pageNumber; this.pageSize = pageSize; this.filters = filters; @@ -104,5 +110,7 @@ public List getEmbed() { } @Override - public String getSearch() {return search;} + public String getSearch() { + return search; + } } diff --git a/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/repositories/CategoryRepository.java b/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/repositories/CategoryRepository.java index 6d2605f6..e30d0bd5 100644 --- a/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/repositories/CategoryRepository.java +++ b/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/repositories/CategoryRepository.java @@ -47,4 +47,6 @@ public interface CategoryRepository extends Repository { boolean belongsToPublicProject(int id) throws BazaarException; Statistic getStatisticsForCategory(int userId, int categoryId, LocalDateTime timestamp) throws BazaarException; + + List getFollowedCategories(int userId, int count) throws BazaarException; } diff --git a/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/repositories/CategoryRepositoryImpl.java b/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/repositories/CategoryRepositoryImpl.java index 544fdc37..03a9039c 100644 --- a/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/repositories/CategoryRepositoryImpl.java +++ b/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/repositories/CategoryRepositoryImpl.java @@ -354,4 +354,30 @@ public Statistic getStatisticsForCategory(int userId, int categoryId, LocalDateT } return result; } + + @Override + public List getFollowedCategories(int userId, int count) throws BazaarException { + List categories = null; + try { + List categoryIds; + categoryIds = jooq.select() + .from(CATEGORY_FOLLOWER_MAP) + .where(CATEGORY_FOLLOWER_MAP.USER_ID.eq(userId)) + .fetch(CATEGORY_FOLLOWER_MAP.CATEGORY_ID); + + Condition filterCondition = transformer.getTableId().in(categoryIds); + + Pageable.SortField sortField = new Pageable.SortField("last_activity", "DESC"); + List sortList = new ArrayList<>(); + sortList.add(sortField); + + PageInfo filterPage = new PageInfo(0, count, new HashMap<>(), sortList); + + categories = getFilteredCategories(filterCondition, filterPage, userId).left; + + } catch (Exception e) { + ExceptionHandler.getInstance().convertAndThrowException(e, ExceptionLocation.REPOSITORY, ErrorCode.UNKNOWN); + } + return categories; + } } diff --git a/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/repositories/ProjectRepository.java b/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/repositories/ProjectRepository.java index 0bbda5a4..a6656d31 100644 --- a/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/repositories/ProjectRepository.java +++ b/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/repositories/ProjectRepository.java @@ -48,6 +48,5 @@ public interface ProjectRepository extends Repository { List listAllProjectIds(Pageable pageable, int userId) throws BazaarException; - - + List getFollowedProjects(int userId, int count) throws BazaarException; } diff --git a/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/repositories/ProjectRepositoryImpl.java b/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/repositories/ProjectRepositoryImpl.java index 6d49b141..952adc3d 100644 --- a/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/repositories/ProjectRepositoryImpl.java +++ b/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/repositories/ProjectRepositoryImpl.java @@ -254,11 +254,11 @@ public List listAllProjectIds(Pageable pageable, int userId) throws Baz projectIds = jooq.select() .from(PROJECT) .where(transformer.getFilterConditions(pageable.getFilters())) - .and(PROJECT.VISIBILITY.isTrue().or(PROJECT.LEADER_ID.equal(userId)) + .and(PROJECT.VISIBILITY.isTrue().or(PROJECT.LEADER_ID.equal(userId)) .and(transformer.getSearchCondition(pageable.getSearch()))) .orderBy(transformer.getSortFields(pageable.getSorts())) - // .limit(pageable.getPageSize()) - // .offset(pageable.getOffset()) + // .limit(pageable.getPageSize()) + // .offset(pageable.getOffset()) .fetch(PROJECT.ID); } catch (Exception e) { @@ -267,6 +267,32 @@ public List listAllProjectIds(Pageable pageable, int userId) throws Baz return projectIds; } + @Override + public List getFollowedProjects(int userId, int count) throws BazaarException { + List projects = null; + try { + List projectIds; + projectIds = jooq.select() + .from(PROJECT_FOLLOWER_MAP) + .where(PROJECT_FOLLOWER_MAP.USER_ID.eq(userId)) + .fetch(PROJECT_FOLLOWER_MAP.PROJECT_ID); + + Condition filterCondition = transformer.getTableId().in(projectIds); + + Pageable.SortField sortField = new Pageable.SortField("last_activity", "DESC"); + List sortList = new ArrayList<>(); + sortList.add(sortField); + + PageInfo filterPage = new PageInfo(0, count, new HashMap<>(), sortList); + + projects = getFilteredProjects(filterCondition, filterPage, userId).left; + + } catch (Exception e) { + ExceptionHandler.getInstance().convertAndThrowException(e, ExceptionLocation.REPOSITORY, ErrorCode.UNKNOWN); + } + return projects; + } + @Override public boolean belongsToPublicProject(int id) throws BazaarException { @@ -342,7 +368,8 @@ public Statistic getStatisticsForVisibleProjects(int userId, LocalDateTime times } @Override - public Statistic getStatisticsForProject(int userId, int projectId, LocalDateTime timestamp) throws BazaarException { + public Statistic getStatisticsForProject(int userId, int projectId, LocalDateTime timestamp) throws + BazaarException { Statistic result = null; try { // If you want to change something here, please know what you are doing! Its SQL and even worse JOOQ :-| diff --git a/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/repositories/RequirementRepository.java b/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/repositories/RequirementRepository.java index b4932938..1f8f4968 100644 --- a/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/repositories/RequirementRepository.java +++ b/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/repositories/RequirementRepository.java @@ -42,6 +42,7 @@ public interface RequirementRepository extends Repository { boolean belongsToPublicProject(int id) throws BazaarException; Requirement findById(int id, int userId) throws Exception; + Requirement findById(int id, int userId, List embed) throws Exception; void setRealized(int id, LocalDateTime realized) throws BazaarException; @@ -50,5 +51,5 @@ public interface RequirementRepository extends Repository { Statistic getStatisticsForRequirement(int userId, int requirementId, LocalDateTime timestamp) throws BazaarException; - + List getFollowedRequirements(int userId, int count) throws BazaarException; } diff --git a/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/repositories/RequirementRepositoryImpl.java b/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/repositories/RequirementRepositoryImpl.java index bc133715..bf375d18 100644 --- a/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/repositories/RequirementRepositoryImpl.java +++ b/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/repositories/RequirementRepositoryImpl.java @@ -22,7 +22,10 @@ import de.rwth.dbis.acis.bazaar.dal.jooq.tables.records.RequirementCategoryMapRecord; import de.rwth.dbis.acis.bazaar.dal.jooq.tables.records.RequirementRecord; -import de.rwth.dbis.acis.bazaar.service.dal.entities.*; +import de.rwth.dbis.acis.bazaar.service.dal.entities.Attachment; +import de.rwth.dbis.acis.bazaar.service.dal.entities.Requirement; +import de.rwth.dbis.acis.bazaar.service.dal.entities.Statistic; +import de.rwth.dbis.acis.bazaar.service.dal.entities.UserContext; import de.rwth.dbis.acis.bazaar.service.dal.helpers.*; import de.rwth.dbis.acis.bazaar.service.dal.transform.RequirementTransformer; import de.rwth.dbis.acis.bazaar.service.dal.transform.UserTransformer; @@ -233,7 +236,7 @@ private ImmutablePair, Integer> getFilteredRequirements(Collec } if (requirement.getNumberOfAttachments() > 0) { - AttachmentRepository attachmentRepository = new AttachmentRepositoryImpl(this.jooq); + AttachmentRepository attachmentRepository = new AttachmentRepositoryImpl(jooq); List attachmentList = attachmentRepository.findAllByRequirementId(requirement.getId(), new PageInfo(0, 1000, new HashMap<>())).getElements(); requirement.setAttachments(attachmentList); } @@ -314,8 +317,9 @@ public PaginationResult findAllByProject(int projectId, Pageable pa private UserVote transformToUserVoted(Integer userVotedInt) { UserVote userVoted; - if (userVotedInt == null) + if (userVotedInt == null) { return UserVote.NO_VOTE; + } switch (userVotedInt) { case 0: userVoted = UserVote.DOWN_VOTE; @@ -360,10 +364,12 @@ public boolean belongsToPublicProject(int id) throws BazaarException { } return false; } + @Override public Requirement findById(int id, int userId) throws Exception { return findById(id, userId, null); } + @Override public Requirement findById(int id, int userId, List embed) throws Exception { Requirement requirement = null; @@ -467,4 +473,30 @@ public Statistic getStatisticsForRequirement(int userId, int requirementId, Loca } return result; } + + @Override + public List getFollowedRequirements(int userId, int count) throws BazaarException { + List requirements = null; + try { + List requirementIds; + requirementIds = jooq.select() + .from(CATEGORY_FOLLOWER_MAP) + .where(CATEGORY_FOLLOWER_MAP.USER_ID.eq(userId)) + .fetch(CATEGORY_FOLLOWER_MAP.CATEGORY_ID); + + Condition filterCondition = transformer.getTableId().in(requirementIds); + + Pageable.SortField sortField = new Pageable.SortField("last_activity", "DESC"); + List sortList = new ArrayList<>(); + sortList.add(sortField); + + PageInfo filterPage = new PageInfo(0, count, new HashMap<>(), sortList); + + requirements = getFilteredRequirements(filterCondition, filterPage, userId).left; + + } catch (Exception e) { + ExceptionHandler.getInstance().convertAndThrowException(e, ExceptionLocation.REPOSITORY, ErrorCode.UNKNOWN); + } + return requirements; + } } diff --git a/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/resources/UsersResource.java b/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/resources/UsersResource.java index 2aa73908..cf01ab7a 100644 --- a/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/resources/UsersResource.java +++ b/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/resources/UsersResource.java @@ -3,10 +3,7 @@ import de.rwth.dbis.acis.bazaar.service.BazaarFunction; import de.rwth.dbis.acis.bazaar.service.BazaarService; import de.rwth.dbis.acis.bazaar.service.dal.DALFacade; -import de.rwth.dbis.acis.bazaar.service.dal.entities.Activity; -import de.rwth.dbis.acis.bazaar.service.dal.entities.EntityOverview; -import de.rwth.dbis.acis.bazaar.service.dal.entities.PrivilegeEnum; -import de.rwth.dbis.acis.bazaar.service.dal.entities.User; +import de.rwth.dbis.acis.bazaar.service.dal.entities.*; import de.rwth.dbis.acis.bazaar.service.dal.helpers.PageInfo; import de.rwth.dbis.acis.bazaar.service.dal.helpers.Pageable; import de.rwth.dbis.acis.bazaar.service.dal.helpers.PaginationResult; @@ -53,9 +50,8 @@ @Path("/users") public class UsersResource { - private BazaarService bazaarService; - private final L2pLogger logger = L2pLogger.getInstance(UsersResource.class.getName()); + private final BazaarService bazaarService; public UsersResource() throws Exception { bazaarService = (BazaarService) Context.getCurrent().getService(); @@ -98,7 +94,9 @@ public Response searchUser(@ApiParam(value = "Search filter", required = false) // Take Object for generic error handling Set> violations = bazaarService.validate(pageInfo); - if (violations.size() > 0) ExceptionHandler.getInstance().handleViolations(violations); + if (violations.size() > 0) { + ExceptionHandler.getInstance().handleViolations(violations); + } PaginationResult users = dalFacade.searchUsers(pageInfo); @@ -245,6 +243,62 @@ public Response getActiveUser() { } } + /** + * This method allows to retrieve the current users individual dashboard. + * + * @return Response with active user as a JSON object. + */ + @GET + @Path("/me/dashboard") + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "This method allows to retrieve the current users individual dashboard.") + @ApiResponses(value = { + @ApiResponse(code = HttpURLConnection.HTTP_OK, message = "Returns user dashboard data", response = Dashboard.class), + @ApiResponse(code = HttpURLConnection.HTTP_UNAUTHORIZED, message = "Unauthorized"), + @ApiResponse(code = HttpURLConnection.HTTP_INTERNAL_ERROR, message = "Internal server problems") + }) + public Response getUserDashboard() { + DALFacade dalFacade = null; + try { + Agent agent = Context.getCurrent().getMainAgent(); + String userId = agent.getIdentifier(); + String registrarErrors = bazaarService.notifyRegistrars(EnumSet.of(BazaarFunction.VALIDATION, BazaarFunction.USER_FIRST_LOGIN_HANDLING)); + if (registrarErrors != null) { + ExceptionHandler.getInstance().throwException(ExceptionLocation.BAZAARSERVICE, ErrorCode.UNKNOWN, registrarErrors); + } + dalFacade = bazaarService.getDBConnection(); + Integer internalUserId = dalFacade.getUserIdByLAS2PeerId(userId); + bazaarService.getNotificationDispatcher().dispatchNotification(LocalDateTime.now(), Activity.ActivityAction.RETRIEVE, MonitoringEvent.SERVICE_CUSTOM_MESSAGE_54, + internalUserId, Activity.DataType.USER, internalUserId); + + // Block anonymous user + if (internalUserId == 0) { + ExceptionHandler.getInstance().throwException(ExceptionLocation.BAZAARSERVICE, ErrorCode.AUTHORIZATION, Localization.getInstance().getResourceBundle().getString("error.authorization.comment.read")); + } + + Dashboard data = dalFacade.getDashboardData(internalUserId, 10); + + return Response.ok(data.toJSON()).build(); + } catch (BazaarException bex) { + if (bex.getErrorCode() == ErrorCode.AUTHORIZATION) { + return Response.status(Response.Status.UNAUTHORIZED).entity(ExceptionHandler.getInstance().toJSON(bex)).build(); + } else if (bex.getErrorCode() == ErrorCode.NOT_FOUND) { + return Response.status(Response.Status.NOT_FOUND).entity(ExceptionHandler.getInstance().toJSON(bex)).build(); + } else { + logger.warning(bex.getMessage()); + Context.get().monitorEvent(MonitoringEvent.SERVICE_ERROR, "Get active user"); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(ExceptionHandler.getInstance().toJSON(bex)).build(); + } + } catch (Exception ex) { + BazaarException bex = ExceptionHandler.getInstance().convert(ex, ExceptionLocation.BAZAARSERVICE, ErrorCode.UNKNOWN, ex.getMessage()); + logger.warning(bex.getMessage()); + Context.get().monitorEvent(MonitoringEvent.SERVICE_ERROR, "Get active user"); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(ExceptionHandler.getInstance().toJSON(bex)).build(); + } finally { + bazaarService.closeDBConnection(dalFacade); + } + } + /** * Allows to update a certain user. * @@ -275,7 +329,9 @@ public Response updateUser(@PathParam("userId") int userId, // Take Object for generic error handling Set> violations = bazaarService.validate(userToUpdate); - if (violations.size() > 0) ExceptionHandler.getInstance().handleViolations(violations); + if (violations.size() > 0) { + ExceptionHandler.getInstance().handleViolations(violations); + } dalFacade = bazaarService.getDBConnection(); Integer internalUserId = dalFacade.getUserIdByLAS2PeerId(agent.getIdentifier()); @@ -306,6 +362,7 @@ public Response updateUser(@PathParam("userId") int userId, bazaarService.closeDBConnection(dalFacade); } } + /** * This method returns an entityOverview for the logged in user * @@ -330,8 +387,8 @@ public Response getEntityOverview( @ApiParam(value = "Types of entities to include", required = true, allowMultiple = true, allowableValues = "projects,categories,requirements") @QueryParam("include") List include, @ApiParam(value = "Sort", required = false, allowMultiple = true, allowableValues = "name,date,last_activity,requirement,follower") @DefaultValue("date") @QueryParam("sort") List sort, @ApiParam(value = "SortDirection", allowableValues = "ASC,DESC") @QueryParam("sortDirection") String sortDirection, - @ApiParam(value = "Filter", required = false, allowMultiple = true, allowableValues = "created, following, developing") @DefaultValue("created") @QueryParam("filters") List filters){ - //Possibly allow filtertype "all"? + @ApiParam(value = "Filter", required = false, allowMultiple = true, allowableValues = "created, following, developing") @DefaultValue("created") @QueryParam("filters") List filters) { + //Possibly allow filtertype "all"? DALFacade dalFacade = null; try { String registrarErrors = bazaarService.notifyRegistrars(EnumSet.of(BazaarFunction.VALIDATION, BazaarFunction.USER_FIRST_LOGIN_HANDLING)); @@ -350,13 +407,13 @@ public Response getEntityOverview( Integer internalUserId = dalFacade.getUserIdByLAS2PeerId(userId); HashMap filterMap = new HashMap<>(); - for(String filterOption : filters) { - filterMap.put(filterOption,internalUserId.toString()); + for (String filterOption : filters) { + filterMap.put(filterOption, internalUserId.toString()); } PageInfo pageInfo = new PageInfo(0, 0, filterMap, sortList, search); - EntityOverview result = dalFacade.getEntitiesForUser(include, pageInfo, internalUserId); + EntityOverview result = dalFacade.getEntitiesForUser(include, pageInfo, internalUserId); // Wrong SERVICE_CUSTOM_MESSAGE_3 ? bazaarService.getNotificationDispatcher().dispatchNotification(LocalDateTime.now(), Activity.ActivityAction.RETRIEVE, MonitoringEvent.SERVICE_CUSTOM_MESSAGE_3, 0, Activity.DataType.USER, internalUserId); @@ -368,12 +425,12 @@ public Response getEntityOverview( return responseBuilder.build(); } catch (BazaarException bex) { logger.warning(bex.getMessage()); - Context.get().monitorEvent(MonitoringEvent.SERVICE_ERROR, "Get entityOverview failed" ); + Context.get().monitorEvent(MonitoringEvent.SERVICE_ERROR, "Get entityOverview failed"); return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(ExceptionHandler.getInstance().toJSON(bex)).build(); } catch (Exception ex) { BazaarException bex = ExceptionHandler.getInstance().convert(ex, ExceptionLocation.BAZAARSERVICE, ErrorCode.UNKNOWN, ex.getMessage()); logger.warning(bex.getMessage()); - Context.get().monitorEvent(MonitoringEvent.SERVICE_ERROR, "Get entityOverview failed" ); + Context.get().monitorEvent(MonitoringEvent.SERVICE_ERROR, "Get entityOverview failed"); return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(ExceptionHandler.getInstance().toJSON(bex)).build(); } finally { bazaarService.closeDBConnection(dalFacade); diff --git a/reqbaz/src/test/java/de/rwth/dbis/acis/bazaar/service/BazaarTest.java b/reqbaz/src/test/java/de/rwth/dbis/acis/bazaar/service/BazaarTest.java index 0daf2b95..4e260acf 100644 --- a/reqbaz/src/test/java/de/rwth/dbis/acis/bazaar/service/BazaarTest.java +++ b/reqbaz/src/test/java/de/rwth/dbis/acis/bazaar/service/BazaarTest.java @@ -51,6 +51,24 @@ public void testGetVersion() { } } + /** + * Test to get the statistics + */ + @Test + public void testStatistics() { + try { + MiniClient client = getClient(); + + ClientResponse result = client.sendRequest("GET", mainPath + "statistics", ""); + JsonObject response = JsonParser.parseString(result.getResponse()).getAsJsonObject(); + System.out.println(response.toString()); + assertTrue(response.isJsonObject()); + } catch (Exception e) { + e.printStackTrace(); + fail(e.toString()); + } + } + /** * Test to get a list of projects */ @@ -432,4 +450,27 @@ public void testUserJsonView() { fail(e.toString()); } } + + /** + * Test to get user dashboard + */ + @Test + public void testDashboard() { + try { + MiniClient client = getAdminClient(); + + ClientResponse result = client.sendRequest("GET", mainPath + "users/me/dashboard", ""); + System.out.println(result.toString()); + assertEquals(200, result.getHttpCode()); + + JsonObject response = JsonParser.parseString(result.getResponse()).getAsJsonObject(); + assertTrue(response.isJsonObject()); + assertTrue(response.has("projects")); + assertTrue(response.has("categories")); + assertTrue(response.has("requirements")); + } catch (Exception e) { + e.printStackTrace(); + fail(e.toString()); + } + } } From cea9ee3a6d21b09e4697d16e684691374b0b1105 Mon Sep 17 00:00:00 2001 From: Thore Date: Thu, 13 May 2021 19:07:34 +0200 Subject: [PATCH 2/3] Fix requirement query and authorization checks --- .../bazaar/service/dal/entities/User.java | 11 ++++++++--- .../RequirementRepositoryImpl.java | 6 +++--- .../service/exception/BazaarException.java | 13 ++++++++++--- .../service/resources/UsersResource.java | 19 ++++++++++++------- .../resources/i18n/Translation_de.properties | 1 + .../resources/i18n/Translation_en.properties | 2 +- 6 files changed, 35 insertions(+), 17 deletions(-) diff --git a/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/entities/User.java b/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/entities/User.java index 01e12131..45b81361 100644 --- a/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/entities/User.java +++ b/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/entities/User.java @@ -60,6 +60,7 @@ public class User extends EntityBase { @NotNull(message = "las2peerId can't be null") @Size(min = 1, max = 1000, message = "las2peerId must have between 1 and 1000 characters") + @JsonView(SerializerViews.Private.class) private String las2peerId; private String profileImage; @@ -107,11 +108,15 @@ public Boolean isPersonalizationEnabled() { @Override public boolean equals(Object o) { - if (o == this) return true; - if (!(o instanceof User)) return false; + if (o == this) { + return true; + } + if (!(o instanceof User)) { + return false; + } User other = (User) o; - return this.las2peerId.equals(other.las2peerId); + return las2peerId.equals(other.las2peerId); } @Override diff --git a/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/repositories/RequirementRepositoryImpl.java b/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/repositories/RequirementRepositoryImpl.java index bf375d18..b49e20dd 100644 --- a/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/repositories/RequirementRepositoryImpl.java +++ b/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/dal/repositories/RequirementRepositoryImpl.java @@ -480,9 +480,9 @@ public List getFollowedRequirements(int userId, int count) throws B try { List requirementIds; requirementIds = jooq.select() - .from(CATEGORY_FOLLOWER_MAP) - .where(CATEGORY_FOLLOWER_MAP.USER_ID.eq(userId)) - .fetch(CATEGORY_FOLLOWER_MAP.CATEGORY_ID); + .from(REQUIREMENT_FOLLOWER_MAP) + .where(REQUIREMENT_FOLLOWER_MAP.USER_ID.eq(userId)) + .fetch(REQUIREMENT_FOLLOWER_MAP.REQUIREMENT_ID); Condition filterCondition = transformer.getTableId().in(requirementIds); diff --git a/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/exception/BazaarException.java b/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/exception/BazaarException.java index aed39e4d..457ae3ca 100644 --- a/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/exception/BazaarException.java +++ b/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/exception/BazaarException.java @@ -20,6 +20,7 @@ package de.rwth.dbis.acis.bazaar.service.exception; +import com.fasterxml.jackson.annotation.JsonIgnore; import de.rwth.dbis.acis.bazaar.service.internalization.Localization; /** @@ -27,17 +28,16 @@ */ public class BazaarException extends Exception { + private final ExceptionLocation location; private String message; - private ErrorCode errorCode; - private final ExceptionLocation location; - protected BazaarException(ExceptionLocation location) { this.location = location; message = ""; } + @Override public String getMessage() { return message; } @@ -61,4 +61,11 @@ public int getExceptionCode() { public String getExceptionMessage() { return String.format(Localization.getInstance().getResourceBundle().getString("error.unknown_exception"), message, location.getMessage(), errorCode.getMessage(), getExceptionCode()); } + + @JsonIgnore + @Override + public StackTraceElement[] getStackTrace() { + return super.getStackTrace(); + } + } diff --git a/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/resources/UsersResource.java b/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/resources/UsersResource.java index cf01ab7a..0f116c87 100644 --- a/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/resources/UsersResource.java +++ b/reqbaz/src/main/java/de/rwth/dbis/acis/bazaar/service/resources/UsersResource.java @@ -216,6 +216,12 @@ public Response getActiveUser() { if (registrarErrors != null) { ExceptionHandler.getInstance().throwException(ExceptionLocation.BAZAARSERVICE, ErrorCode.UNKNOWN, registrarErrors); } + + // Block anonymous user + if (userId.equals("anonymous")) { + ExceptionHandler.getInstance().throwException(ExceptionLocation.BAZAARSERVICE, ErrorCode.AUTHORIZATION, Localization.getInstance().getResourceBundle().getString("error.authorization.user.read")); + } + dalFacade = bazaarService.getDBConnection(); Integer internalUserId = dalFacade.getUserIdByLAS2PeerId(userId); User user = dalFacade.getUserById(internalUserId); @@ -266,24 +272,23 @@ public Response getUserDashboard() { if (registrarErrors != null) { ExceptionHandler.getInstance().throwException(ExceptionLocation.BAZAARSERVICE, ErrorCode.UNKNOWN, registrarErrors); } + + // Block anonymous user + if (userId.equals("anonymous")) { + ExceptionHandler.getInstance().throwException(ExceptionLocation.BAZAARSERVICE, ErrorCode.AUTHORIZATION, Localization.getInstance().getResourceBundle().getString("error.authorization.user.read")); + } + dalFacade = bazaarService.getDBConnection(); Integer internalUserId = dalFacade.getUserIdByLAS2PeerId(userId); bazaarService.getNotificationDispatcher().dispatchNotification(LocalDateTime.now(), Activity.ActivityAction.RETRIEVE, MonitoringEvent.SERVICE_CUSTOM_MESSAGE_54, internalUserId, Activity.DataType.USER, internalUserId); - // Block anonymous user - if (internalUserId == 0) { - ExceptionHandler.getInstance().throwException(ExceptionLocation.BAZAARSERVICE, ErrorCode.AUTHORIZATION, Localization.getInstance().getResourceBundle().getString("error.authorization.comment.read")); - } - Dashboard data = dalFacade.getDashboardData(internalUserId, 10); return Response.ok(data.toJSON()).build(); } catch (BazaarException bex) { if (bex.getErrorCode() == ErrorCode.AUTHORIZATION) { return Response.status(Response.Status.UNAUTHORIZED).entity(ExceptionHandler.getInstance().toJSON(bex)).build(); - } else if (bex.getErrorCode() == ErrorCode.NOT_FOUND) { - return Response.status(Response.Status.NOT_FOUND).entity(ExceptionHandler.getInstance().toJSON(bex)).build(); } else { logger.warning(bex.getMessage()); Context.get().monitorEvent(MonitoringEvent.SERVICE_ERROR, "Get active user"); diff --git a/reqbaz/src/main/resources/i18n/Translation_de.properties b/reqbaz/src/main/resources/i18n/Translation_de.properties index 2f18fb70..48dcbb89 100644 --- a/reqbaz/src/main/resources/i18n/Translation_de.properties +++ b/reqbaz/src/main/resources/i18n/Translation_de.properties @@ -34,6 +34,7 @@ error.authorization.develop.create=Nur Projektmitglieder können sich als Entwic error.authorization.develop.delete=Nur Projektmitglieder können sich als Entwickler austragen. error.authorization.follow.create=Nur Projekmitglieder können ein Projekt folgen. error.authorization.follow.delete=Nur Projektmitglieder können ein Projekt nicht weiter folgen. +error.authorization.user.read=Nur authentifizierte Benutzer können diese Ressource einsehen. error.authorization.vote.create=Nur Projektmitglieder können abstimmen. error.authorization.vote.delete=Nur Projektmitglieder können ihre Abstimmung löschen. error.authorization.comment.read=Nur Projektmiglieder können Kommentare sehen. diff --git a/reqbaz/src/main/resources/i18n/Translation_en.properties b/reqbaz/src/main/resources/i18n/Translation_en.properties index e80b6e58..7bcdbdc7 100644 --- a/reqbaz/src/main/resources/i18n/Translation_en.properties +++ b/reqbaz/src/main/resources/i18n/Translation_en.properties @@ -34,6 +34,7 @@ error.authorization.develop.create=Only project members can register to develop error.authorization.develop.delete=Only project members can deregister from developing a requirement. error.authorization.follow.create=Only project members can register to follow a requirement. error.authorization.follow.delete=Only project members can deregister following a requirement. +error.authorization.user.read=Only logged in users can access this resource. error.authorization.vote.create=Only project members can vote. error.authorization.vote.delete=Only project members can delete vote. error.authorization.comment.read=Only project members can see comments. @@ -52,7 +53,6 @@ error.authorization.project.read=You do not have rights to read this project. error.authorization.project.modify=You do not have rights to modify this project. error.authorization.requirement.modify=Only the creator can modify requirements. error.resource.notfound=$s not found. - # email fields. english only at the moment. email.bodyText.greeting=Hello, email.bodyText.user=User From c6542035cbc28373b49c5e8a21fd088919113699 Mon Sep 17 00:00:00 2001 From: Thore Date: Thu, 13 May 2021 19:13:18 +0200 Subject: [PATCH 3/3] Update changelog --- CHANGELOG.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89c6ac2b..898ca704 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,9 +21,14 @@ the [GitHub Release Page](https://github.com/rwth-acis/RequirementsBazaar/releas - Categories, projects and requirements now have a `userContext` encapsuling the dynamic user related information ( permissions, votes, contribution) [#94](https://github.com/rwth-acis/RequirementsBazaar/pull/94). - If a user is a member of a project the respective role is now returned in the`usertRole` attribute of the - new `userContext` attribute [#94](https://github.com/rwth-acis/RequirementsBazaar/pull/94) [#96](https://github.com/rwth-acis/RequirementsBazaar/pull/96). + new `userContext` + attribute [#94](https://github.com/rwth-acis/RequirementsBazaar/pull/94) [#96](https://github.com/rwth-acis/RequirementsBazaar/pull/96) + . - Add a delete projects endpoint [#100](https://github.com/rwth-acis/RequirementsBazaar/pull/100). - Add an update comment endpoint [#100](https://github.com/rwth-acis/RequirementsBazaar/pull/100). +- Redacted comments now have a deleted flag [#103](https://github.com/rwth-acis/RequirementsBazaar/pull/103). +- Added a user dashboard listing the last 10 most recent active followed projects, categories and + requirements [#106](https://github.com/rwth-acis/RequirementsBazaar/pull/106). ### Changed @@ -54,9 +59,14 @@ the [GitHub Release Page](https://github.com/rwth-acis/RequirementsBazaar/releas - Requirements no longer return the category objects in the `categories` attribute but a list of category ids [#91](https://github.com/rwth-acis/RequirementsBazaar/pull/91). - Vote direction can no longer be provided as a query - parameter [#94](https://github.com/rwth-acis/RequirementsBazaar/pull/94) but instead as a direction object strictly defined by an enum [#96](https://github.com/rwth-acis/RequirementsBazaar/pull/96), + parameter [#94](https://github.com/rwth-acis/RequirementsBazaar/pull/94) but instead as a direction object strictly + defined by an enum [#96](https://github.com/rwth-acis/RequirementsBazaar/pull/96), - Moved user related information in categories, requirements and projects (isFollower/Developer/Contributor, userVoted) into the new `userContext` [#94](https://github.com/rwth-acis/RequirementsBazaar/pull/94). +- Comments with existing responses will no longer be deleted but + redacted [#103](https://github.com/rwth-acis/RequirementsBazaar/pull/103). +- Comments for a requirement are no longer paginated but instead return all + comments [#103](https://github.com/rwth-acis/RequirementsBazaar/pull/103). ### Removed