diff --git a/Procfile b/Procfile index 633091ba..c27d1ea1 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -web: target/universal/stage/bin/appcivist-core -Dhttp.port=${PORT} -Ddb.default.driver=org.postgresql.Driver -Ddb.default.url=${DATABASE_URL} -Dconfig.resource=heroku.conf +web: target/universal/stage/bin/appcivist-core -Dhttp.port=${PORT} -Ddb.default.driver=org.postgresql.Driver -Ddb.default.url=${DATABASE_URL} -Dconfig.resource=heroku.conf -Dlogger.file=conf/heroku.logback.xml diff --git a/SecurityAnnotations.md b/SecurityAnnotations.md new file mode 100644 index 00000000..787c16db --- /dev/null +++ b/SecurityAnnotations.md @@ -0,0 +1,31 @@ +# Security Annotations +Annotations to use in Controllers to manage access control + +*Restrict to Role Groups* +By using the following annotation on top of a Controller method, the corresponding endpoint is accessible only to +users that have the list of ROLES specified in the list + + ```java + @Restrict(@Group(GlobalData.USER_ROLE)) // only those with the USER_ROLE + @Restrict(@Group(GlobalData.USER_ROLE),@Group(GlobalData.ADMIN_ROLE)) // only those with the USER_ROLE or the ADMIN_ROLE + @Restrict(@Group(GlobalData.USER_ROLE,GlobalData.ADMIN_ROLE)) // only those with the USER_ROLE AND ADMIN_ROLE + ``` + +*Dynamic Access Control* +Refer to access rules that are more specific and are implemented by the DynamicResourceHandlers in the security package. +You can use them via the following annotation, where value indicates which of the dynamic handlers to use (as they +are defined in security.MyDynamicResourceHandler) : + + ```java + @Dynamic(value = "OnlyMe", meta = SecurityModelConstants.USER_RESOURCE_PATH) + ``` + +As of now, the following dynamic handlers are avaiable: + +* *MemberOfAssembly:* the user that sent the request is member of the assembly to which the resource belongs +* *CoordinatorOfGroup:* the user that sent the request is coordinator of the working group to which the resource belongs +* *CoordinatorOfAssembly:* the user that sent the request is member of the assembly to which the resource belongs +* *CanInviteToGroup:* the user has permission to invite others to the group +* *CanInviteToAssembly:* the user has permission to invete other to the assembly +* *OnlyMeAndAdmin:* the request will be successful only if the requested is under the user's resource space or if he/she is ADMIN +* *OnlyMe:* the request will be successful only if the requested is under the user's resource space \ No newline at end of file diff --git a/app/Global.java b/app/Global.java index ba7abc9e..833750b2 100644 --- a/app/Global.java +++ b/app/Global.java @@ -10,14 +10,25 @@ import play.Logger; import play.Play; import play.libs.Yaml; +import play.mvc.Call; import com.avaje.ebean.Ebean; +import com.feth.play.module.pa.PlayAuthenticate; +import com.feth.play.module.pa.PlayAuthenticate.Resolver; +import com.feth.play.module.pa.exceptions.AccessDeniedException; +import com.feth.play.module.pa.exceptions.AuthException; + +import controllers.routes; public class Global extends GlobalSettings { public void onStart(Application app) { Logger.info("Application has started"); + initializeData(app); + initializeAuthenticationResolver(); + } + private void initializeData(Application app) { /** * If the project configuration ask for the database to be clean, * rebuild it (for dev purposes only) @@ -31,10 +42,61 @@ public void onStart(Application app) { cleanDBAndRebuild(app); } - List dataLoadFiles = Play.application().configuration() - .getStringList("appcivist.db.initial-data.files"); + Boolean doNotLoadData = Play.application().configuration() + .getBoolean("appcivist.db.noInitialData"); + if(doNotLoadData==null || !doNotLoadData) { + List dataLoadFiles = Play.application().configuration() + .getStringList("appcivist.db.initial-data.files"); + + loadDataFiles(dataLoadFiles); + } + } + + private void initializeAuthenticationResolver() { + Logger.info("Initialiazing PlayAuthenticate Resolver"); + PlayAuthenticate.setResolver(new Resolver() { + + public Call login() { + // Your login page + return routes.Users.login(); + } + + public Call afterAuth() { + // The user will be redirected to this page after authentication + // if no original URL was saved + return routes.Application.index(); + } + + public Call afterLogout() { + return routes.Application.index(); + } - loadDataFiles(dataLoadFiles); + public Call auth(final String provider) { + // You can provide your own authentication implementation, + // however the default should be sufficient for most cases + return routes.AuthenticateLocal + .authenticate(provider); + } + + public Call askMerge() { + return routes.Users.askMerge(); + } + + public Call askLink() { + return routes.Users.askLink(); + } + + public Call onException(final AuthException e) { + if (e instanceof AccessDeniedException) { + return routes.Users + .oAuthDenied(((AccessDeniedException) e) + .getProviderKey()); + } + + // more custom problem handling here... + return super.onException(e); + } + }); } @SuppressWarnings("rawtypes") @@ -46,7 +108,7 @@ private void loadDataFiles(List dataLoadFiles) { for (String dataFile : dataLoadFiles) { InitialDataConfig fileConfig = InitialDataConfig .readByFileName(dataFile); - + Logger.info("---> AppCivist '" + dataFile + "' loading registry: "+fileConfig); if (fileConfig == null || !fileConfig.getLoaded()) { try { Logger.info("---> AppCivist: Loading '" + dataFile diff --git a/app/controllers/Assemblies.java b/app/controllers/Assemblies.java index bd59452a..4182e855 100644 --- a/app/controllers/Assemblies.java +++ b/app/controllers/Assemblies.java @@ -26,6 +26,8 @@ import com.feth.play.module.pa.PlayAuthenticate; import com.wordnik.swagger.annotations.Api; +import com.wordnik.swagger.annotations.ApiImplicitParam; +import com.wordnik.swagger.annotations.ApiImplicitParams; import com.wordnik.swagger.annotations.ApiOperation; import com.wordnik.swagger.annotations.ApiResponse; import com.wordnik.swagger.annotations.ApiResponses; @@ -39,16 +41,33 @@ public class Assemblies extends Controller { public static final Form ASSEMBLY_FORM = form(Assembly.class); public static final Form MEMBERSHIP_FORM = form(TransferMembership.class); +// @ApiOperation(httpMethod = "PUT", response = TransferResponseStatus.class, produces = "application/json", value = "Update user information", notes = "Updates user information") +// @ApiResponses(value = { @ApiResponse(code = 404, message = "User not found", response=TransferResponseStatus.class) }) +// @ApiImplicitParams({ +// @ApiImplicitParam(name="uid", value="User's ID", dataType="Long", paramType="path"), +// @ApiImplicitParam(name="SESSION_KEY", value="User's session authentication key", dataType="String", paramType="header"), +// @ApiImplicitParam(name="user", value="User's updated information", dataType="models.User", paramType = "body") +// }) + /** * Return the full list of assemblies * * @return models.AssemblyCollection */ - @ApiOperation(httpMethod = "GET", response = Assembly.class, produces = "application/json", value = "Get list of assemblies", notes = "Get the full list of assemblies. Only availabe to ADMINS") - @Restrict({ @Group(GlobalData.ADMIN_ROLE) }) + @ApiOperation(httpMethod = "GET", response = Assembly.class, responseContainer = "List", produces = "application/json", value = "Get list of assemblies based on query", notes = "Get the full list of assemblies. Only availabe to ADMINS") + @ApiResponses(value = { + @ApiResponse(code = 404, message = "No assembly found", response=TransferResponseStatus.class) + }) + @ApiImplicitParams({ + @ApiImplicitParam(name="query", value="Search query string (keywords in title)", dataType="String", paramType="query"), + @ApiImplicitParam(name="filter", value="Special filters. 'summary' returns only summarized info of assemblies, 'featured' returns a list of marks featured assemblies and 'nearby' limits the query to assemblies that are nearby of the user location, ", dataType="String", paramType="query", allowableValues="featured,nearby,summary,random"), + @ApiImplicitParam(name="SESSION_KEY", value="User's session authentication key", dataType="String", paramType="header") +// @ApiImplicitParam(name="user", value="User's updated information", dataType="models.User", paramType = "body") + }) + @Restrict({ @Group(GlobalData.USER_ROLE) }) public static Result findAssemblies(String query, String filter) { if (query!=null && !query.isEmpty()) return searchAssemblies(query); - else if (filter!=null && !filter.isEmpty()) return findAssembliesWithFilter(filter); + else if (filter!=null && !filter.isEmpty()) return findAssembliesWithFilter(query,filter); else return ok(Json.toJson(Assembly.findAll())); } @@ -59,6 +78,7 @@ public static Result findAssemblies(String query, String filter) { */ @Restrict({ @Group(GlobalData.USER_ROLE) }) public static Result searchAssemblies(String query) { + // TODO: use also the filter List a = Assembly.findBySimilarName(query); if (a!=null) return ok(Json.toJson(a)); else return notFound(Json.toJson(TransferResponseStatus.noDataMessage("No assemblies with a title resembling query", ""))); @@ -71,12 +91,18 @@ public static Result searchAssemblies(String query) { * @return models.AssemblyCollection */ @Restrict({ @Group(GlobalData.USER_ROLE) }) - public static Result findAssembliesWithFilter(String filter) { + public static Result findAssembliesWithFilter(String query, String filter) { + List a = null; if (filter.equals("featured")) { - List a = Assembly.findFeaturedAssemblies(); - if (a!=null) return ok(Json.toJson(a)); - else return notFound(Json.toJson(TransferResponseStatus.noDataMessage("No featured assemblies", ""))); - } else return notFound(Json.toJson(TransferResponseStatus.noDataMessage("Not implemented yet", ""))); + a = Assembly.findFeaturedAssemblies(query); + } else if (filter.equals("random")) { + a = Assembly.findRandomAssemblies(query); +// TODO: } else if (filter.equals("nearby")) { return Assembly.findNearbyAssemblies(); +// TODO: } else if (filter.equals("summary")) { return Assembly.findAssembliesSummaries(query); + } else return internalServerError(Json.toJson(TransferResponseStatus.noDataMessage("Filter '"+filter+"' is not supported yet", ""))); + + if (a!=null) return ok(Json.toJson(a)); + else return notFound(Json.toJson(TransferResponseStatus.noDataMessage("No "+filter+" assemblies", ""))); } @ApiOperation(response = Assembly.class, produces = "application/json", value = "Create a new assembly") diff --git a/app/controllers/Campaigns.java b/app/controllers/Campaigns.java index ec2bc12b..59e7e998 100644 --- a/app/controllers/Campaigns.java +++ b/app/controllers/Campaigns.java @@ -5,11 +5,23 @@ import com.feth.play.module.pa.PlayAuthenticate; import com.wordnik.swagger.annotations.Api; +import com.wordnik.swagger.annotations.ApiImplicitParam; +import com.wordnik.swagger.annotations.ApiImplicitParams; import com.wordnik.swagger.annotations.ApiOperation; +import com.wordnik.swagger.annotations.ApiResponse; +import com.wordnik.swagger.annotations.ApiResponses; +import enums.AppcivistNotificationTypes; +import enums.AppcivistResourceTypes; import http.Headers; import models.Assembly; import models.Campaign; +import models.CampaignPhase; +import models.CampaignPhaseMilestone; +import models.Contribution; +import models.Membership; +import models.MembershipAssembly; +import models.ResourceSpace; import models.User; import play.Logger; import play.data.Form; @@ -22,8 +34,14 @@ import utils.GlobalData; import models.transfer.TransferMembership; import models.transfer.TransferResponseStatus; +import models.transfer.TransferUpdate; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; import java.util.List; +import java.util.UUID; import static play.data.Form.form; @@ -125,4 +143,87 @@ public static Result createCampaign(Long aid) { return ok(Json.toJson(responseBody)); } } + + @ApiOperation(httpMethod = "GET", response = TransferUpdate.class, responseContainer="List", produces = "application/json", value = "Update user information", notes = "Updates user information") + @ApiResponses(value = { @ApiResponse(code = 404, message = "User not found", response=TransferResponseStatus.class) }) + @ApiImplicitParams({ + //@ApiImplicitParam(name="user", value="user", dataType="String", defaultValue="user", paramType = "path"), + @ApiImplicitParam(name="uuid", value="Assembly's UUID", dataType="java.util.UUID", paramType="path"), + @ApiImplicitParam(name="SESSION_KEY", value="User's session authentication key", dataType="String", paramType="header"), + @ApiImplicitParam(name="filter", value="Filter value", dataType="String", paramType="query", allowableValues="ongoing,past,future,all", defaultValue="ongoing") + }) + @Dynamic(value = "OnlyMe", meta = SecurityModelConstants.USER_RESOURCE_PATH) + public static Result campaignsByAssembly(UUID uuid, String filter) { + if (filter==null || filter.equals("ongoing")) { + return ongoingCampaignsByAssembly(uuid); + } else if (filter.equals("past")) { + return internalServerError(Json.toJson(new TransferResponseStatus("Not implemented"))); + } else if (filter.equals("future")) { + return internalServerError(Json.toJson(new TransferResponseStatus("Not implemented"))); + } else { + Assembly a = Assembly.readByUUID(uuid); + List campaigns = a.getResources().getCampaigns(); + if (!campaigns.isEmpty()) return ok(Json.toJson(campaigns)); + else + return notFound(Json.toJson(new TransferResponseStatus("No ongoing campaigns"))); + } + } + + @ApiOperation(httpMethod = "GET", response = Campaign.class, responseContainer="List", produces = "application/json", value = "Update user information", notes = "Updates user information") + @ApiResponses(value = { @ApiResponse(code = 404, message = "No Campaign Found", response=TransferResponseStatus.class) }) + @ApiImplicitParams({ + //@ApiImplicitParam(name="user", value="user", dataType="String", defaultValue="user", paramType = "path"), + @ApiImplicitParam(name="uuid", value="User's UUID", dataType="java.util.UUID", paramType="path"), + @ApiImplicitParam(name="SESSION_KEY", value="User's session authentication key", dataType="String", paramType="header"), + @ApiImplicitParam(name="filter", value="Filter value", dataType="String", paramType="query", allowableValues="ongoing,past,future,all", defaultValue="ongoing") + }) + @Dynamic(value = "OnlyMe", meta = SecurityModelConstants.USER_RESOURCE_PATH) + public static Result campaignsByUser(UUID uuid, String filter) { + if (filter==null || filter.equals("ongoing")) { + return ongoingCampaignsByUser(uuid); + } else if (filter.equals("past")) { + return internalServerError(Json.toJson(new TransferResponseStatus("Not implemented"))); + } else if (filter.equals("future")) { + return internalServerError(Json.toJson(new TransferResponseStatus("Not implemented"))); + } else { + User u = User.findByUUID(uuid); + List assemblyMemberships = Membership.findByUser(u, "ASSEMBLY"); + List campaigns = new ArrayList(); + for (Membership membership : assemblyMemberships) { + Assembly a = ((MembershipAssembly) membership).getAssembly(); + campaigns.addAll(a.getResources().getCampaigns()); + } + if (!campaigns.isEmpty()) return ok(Json.toJson(campaigns)); + else + return notFound(Json.toJson(new TransferResponseStatus("No ongoing campaigns"))); + } + } + + private static Result ongoingCampaignsByUser(UUID uuid) { + User u = User.findByUUID(uuid); + List assemblyMemberships = Membership.findByUser(u, "ASSEMBLY"); + List ongoingCampaigns = new ArrayList(); + + for (Membership membership : assemblyMemberships) { + Assembly a = ((MembershipAssembly) membership).getAssembly(); + ongoingCampaigns.addAll(Campaign.extractOngoingCampaignsFromAssembly(a)); + } + + if (!ongoingCampaigns.isEmpty()) return ok(Json.toJson(ongoingCampaigns)); + else + return notFound(Json.toJson(new TransferResponseStatus("No ongoing campaigns"))); + } + + + + + + private static Result ongoingCampaignsByAssembly(UUID uuid) { + Assembly a = Assembly.readByUUID(uuid); + List ongoingCampaigns = new ArrayList(); + ongoingCampaigns.addAll(Campaign.extractOngoingCampaignsFromAssembly(a)); + if (!ongoingCampaigns.isEmpty()) return ok(Json.toJson(ongoingCampaigns)); + else + return notFound(Json.toJson(new TransferResponseStatus("No ongoing campaigns"))); + } } diff --git a/app/controllers/Memberships.java b/app/controllers/Memberships.java index 556fbe1f..58748e83 100644 --- a/app/controllers/Memberships.java +++ b/app/controllers/Memberships.java @@ -4,11 +4,12 @@ import http.Headers; import java.util.List; +import java.util.UUID; import models.Assembly; +import models.Membership; import models.MembershipAssembly; import models.MembershipGroup; -import models.Membership; import models.SecurityRole; import models.TokenAction; import models.TokenAction.Type; @@ -16,6 +17,7 @@ import models.WorkingGroup; import models.transfer.TransferMembership; import models.transfer.TransferResponseStatus; +import play.Logger; import play.data.Form; import play.i18n.Messages; import play.libs.Json; @@ -24,17 +26,21 @@ import play.mvc.Security; import play.mvc.With; import providers.MyUsernamePasswordAuthProvider; +import security.SecurityModelConstants; import utils.GlobalData; -import play.Logger; +import be.objectify.deadbolt.java.actions.Dynamic; import be.objectify.deadbolt.java.actions.Group; import be.objectify.deadbolt.java.actions.Restrict; import com.feth.play.module.pa.PlayAuthenticate; import com.wordnik.swagger.annotations.Api; +import com.wordnik.swagger.annotations.ApiImplicitParam; +import com.wordnik.swagger.annotations.ApiImplicitParams; import com.wordnik.swagger.annotations.ApiOperation; +import com.wordnik.swagger.annotations.ApiResponse; +import com.wordnik.swagger.annotations.ApiResponses; import enums.MembershipCreationTypes; -import enums.ManagementTypes; import enums.MembershipStatus; import enums.MyRoles; import enums.ResponseStatus; @@ -359,6 +365,46 @@ public static Result verifyMembership(Long id, String token) { return ok(Json.toJson(Messages.get("playauthenticate.verify_email.success", email))); } + + @ApiOperation(httpMethod = "GET", response = Membership.class, responseContainer = "List", produces = "application/json", value = "Update user information", notes = "Updates user information") + @ApiResponses(value = { @ApiResponse(code = 404, message = "User or Memberships not found", response=TransferResponseStatus.class) }) + @ApiImplicitParams({ + @ApiImplicitParam(name="uuid", value="User's UUID", dataType="java.util.UUID", paramType="path"), + @ApiImplicitParam(name="SESSION_KEY", value="User's session authentication key", dataType="String", paramType="header"), + @ApiImplicitParam(name="type", value="type of membership requeste", dataType="String", paramType = "query", allowableValues ="assembly,group") + }) + @Dynamic(value = "OnlyMeAndAdmin", meta = SecurityModelConstants.USER_RESOURCE_PATH) + public static Result findMembershipByUser(UUID uuid, String type) { + List memberships = null; + User u = User.findByUUID(uuid); + if (u == null) + return notFound(Json.toJson(new TransferResponseStatus( + ResponseStatus.NOTAVAILABLE, "User with UUID = " + uuid + + " not found"))); + + // TODO: include UUID already in the Membership model + String membershipType = ""; + if(type!=null && !type.isEmpty()) { + switch (type.toLowerCase()) { + case "assembly": + membershipType = "ASSEMBLY"; + break; + case "group": + membershipType = "GROUP"; + break; + } + } + memberships = Membership.findByUser(u,membershipType); + if (memberships == null || memberships.isEmpty()) + return notFound(Json.toJson(new TransferResponseStatus( + ResponseStatus.NOTAVAILABLE, "No memberships for user with UUID = " + uuid))); + else + return ok(Json.toJson(memberships)); + + } + + + /**************************************************************************************************************** * Not exposed methods ****************************************************************************************************************/ diff --git a/app/controllers/Notifications.java b/app/controllers/Notifications.java new file mode 100644 index 00000000..b14cb2a2 --- /dev/null +++ b/app/controllers/Notifications.java @@ -0,0 +1,235 @@ +package controllers; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import models.Assembly; +import models.Campaign; +import models.CampaignPhase; +import models.CampaignPhaseMilestone; +import models.Contribution; +import models.Membership; +import models.MembershipAssembly; +import models.ResourceSpace; +import models.User; +import models.WorkingGroup; +import models.transfer.TransferResponseStatus; +import models.transfer.TransferUpdate; +import be.objectify.deadbolt.java.actions.Dynamic; + +import com.wordnik.swagger.annotations.Api; +import com.wordnik.swagger.annotations.ApiImplicitParam; +import com.wordnik.swagger.annotations.ApiImplicitParams; +import com.wordnik.swagger.annotations.ApiOperation; +import com.wordnik.swagger.annotations.ApiResponse; +import com.wordnik.swagger.annotations.ApiResponses; + +import enums.AppcivistNotificationTypes; +import enums.AppcivistResourceTypes; +import play.*; +import play.i18n.Messages; +import play.libs.Json; +import play.mvc.*; +import security.SecurityModelConstants; +import views.html.*; +import http.Headers; + +/** + * Temporal controller to manage a mockup version of the notification server bus + * TODO: replace this controller (or adapt) to use a notification queue like RabbitMQ + * + * @author cdparra + * + */ +@Api(value="/notification") +@With(Headers.class) +@SuppressWarnings("unused") +public class Notifications extends Controller { + + final private static String NOTIFICATION_TITLE_ASSEMBLY_UPDATE = "notification.assembly.update"; + final private static String NOTIFICATION_TITLE_GROUP_UPDATE = "notification.group.update"; + final private static String NOTIFICATION_TITLE_CONTRIBUTION_UPDATE = "notification.contribution.update"; + final private static String NOTIFICATION_TITLE_CAMPAIGN_UPDATE = "notification.campaign.update"; + final private static String NOTIFICATION_TITLE_MILESTONE_UPDATE = "notification.campaign.update.milestone"; + final private static String NOTIFICATION_TITLE_MESSAGE_NEW = "notification.message.new"; + final private static String NOTIFICATION_TITLE_MESSAGE_REPLY = "notification.message.reply"; + final private static String NOTIFICATION_TITLE_MESSAGE_GROUP_NEW = "notification.message.new.group"; + final private static String NOTIFICATION_TITLE_MESSAGE_GROUP_REPLY = "notification.message.reply.group"; + final private static String NOTIFICATION_TITLE_MESSAGE_ASSEMBLY_NEW = "notification.message.new.assembly"; + final private static String NOTIFICATION_TITLE_MESSAGE_ASSEMBLY_REPLY = "notification.message.reply.assembly"; + + final private static String NOTIFICATION_DESCRIPTION_ASSEMBLY_FORUM_CONTRIBUTION = "notification.description.assembly.forum.contribution"; + final private static String NOTIFICATION_DESCRIPTION_GROUP_FORUM_CONTRIBUTION = "notification.description.group.forum.contribution"; + final private static String NOTIFICATION_DESCRIPTION_CONTRIBUTION_COMMENT = "notification.description.contribution.comment"; + final private static String NOTIFICATION_DESCRIPTION_CAMPAIGN_CONTRIBUTION = "notification.description.campaign.contribution"; + final private static String NOTIFICATION_DESCRIPTION_UPCOMING_MILESTONE = "notification.description.campaign.upcoming.milestone"; + + + + + + /** + * userInbox is the method called by the route GET /user/{uuid}/inbox + * it returns a list of TransferUpdate containing the latest news from User's assemblies, groups, and contributions + * @param userUUID + * @return + */ + @ApiOperation(httpMethod = "GET", response = TransferUpdate.class, responseContainer="List", produces = "application/json", value = "Update user information", notes = "Updates user information") + @ApiResponses(value = { @ApiResponse(code = 404, message = "User not found", response=TransferResponseStatus.class) }) + @ApiImplicitParams({ + //@ApiImplicitParam(name="user", value="user", dataType="String", defaultValue="user", paramType = "path"), + @ApiImplicitParam(name="uuid", value="User's UUID", dataType="Long", paramType="path"), + @ApiImplicitParam(name="SESSION_KEY", value="User's session authentication key", dataType="String", paramType="header") + }) + @Dynamic(value = "OnlyMe", meta = SecurityModelConstants.USER_RESOURCE_PATH) + public static Result userInbox(UUID userUUID) { + // 0. Obtain User + User u = User.findByUUID(userUUID); + // 1. Get a list of Assemblies to which the User belongs + List myAssemblyMemberships = Membership.findByUser(u, "ASSEMBLY"); + // 2. Get a list of Working Groups to which the User belongs + List myGroupMemberships = Membership.findByUser(u, "GROUP"); + // 3. Get a list of Contributions by the user + List myContribs = Contribution.readByCreator(u); + + List updates = new ArrayList(); + + // 4. Process AssemblyMemberships to get + // 4.1. New Assembly Forum Posts + // 4.2. Current Ongoing Campaigns Upcoming Milestones + // 4.3. Current Ongoing Campaigns Latest Contribution + updates = processMyAssemblies(u, updates, myAssemblyMemberships); + + // 5. Process GroupMemberships to get + // 5.1. New Group Forum Posts + // 5.2. New comments related to Group Contributions + //TODO: updates = processMyGroups(u, updates, myGroupMemberships); + + // 6. Process Contributions to get comments on them + //TODO: updates = processMyContributions(u, updates, myContribs); + if (!updates.isEmpty()) return ok(Json.toJson(updates)); + else + return notFound(Json.toJson(new TransferResponseStatus("No updates"))); + } + + private static List processMyAssemblies(User u, + List updates, List myAssemblyMemberships) { + + for (Membership membership : myAssemblyMemberships) { + Assembly a = ((MembershipAssembly) membership).getAssembly(); + + // 4.1. New Assembly Forum Posts + ResourceSpace aForum = a.getForum(); + List posts = null; + Contribution latestForumPost = null; + if (aForum !=null) posts = aForum.getContributions(); + if (posts != null && !posts.isEmpty()) latestForumPost = posts.get(posts.size()-1); + if (latestForumPost != null) + updates.add(TransferUpdate.getInstance( + AppcivistNotificationTypes.ASSEMBLY_UPDATE, + AppcivistResourceTypes.CONTRIBUTION_COMMENT, + AppcivistResourceTypes.ASSEMBLY, + NOTIFICATION_TITLE_ASSEMBLY_UPDATE, + NOTIFICATION_DESCRIPTION_ASSEMBLY_FORUM_CONTRIBUTION, u + .getName(), u.getLanguage(), a.getAssemblyId(), + a.getUuid(), a.getName(), latestForumPost + .getContributionId(), + latestForumPost.getUuid(), latestForumPost.getTitle(), + latestForumPost.getText(), latestForumPost.getAuthor() + .getName(), latestForumPost.getCreation())); + + + // 4.2. Current Ongoing Campaigns Upcoming Milestones + ResourceSpace resources = a.getResources(); + List campaigns = null; + if (resources !=null) campaigns = resources.getCampaigns(); + if (campaigns != null && !campaigns.isEmpty()) { + for (Campaign c : campaigns) { + List phases = c.getPhases(); + if (phases != null && !phases.isEmpty()) { + for (CampaignPhase p : phases) { + Calendar today = Calendar.getInstance(); + if (p.getEndDate().after(today.getTime())) { + // 4.2. Current Ongoing Campaigns Upcoming Milestones + List milestones = p.getMilestones(); + if (milestones!=null && !milestones.isEmpty()) { + for (CampaignPhaseMilestone m : milestones) { + Date mStart = m.getStart(); + Calendar cal = Calendar.getInstance(); + cal.setTime(mStart); // Now use today date. + cal.add(Calendar.DATE, m.getDays()); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + if(cal.getTime().after(today.getTime())) + updates.add(TransferUpdate.getInstance( + AppcivistNotificationTypes.UPCOMING_MILESTONE, + AppcivistResourceTypes.CAMPAIGN_PHASE, + AppcivistResourceTypes.CAMPAIGN, + NOTIFICATION_TITLE_MILESTONE_UPDATE, + NOTIFICATION_DESCRIPTION_UPCOMING_MILESTONE, + u.getName(), + u.getLanguage(), + c.getCampaignId(), + c.getUuid(), + c.getTitle(), + m.getCampaignPhaseMilestoneId(), + m.getUuid(), m.getTitle(), + sdf.format(cal.getTime()), + a.getName(), m + .getCreation())); + } + } + } + + // TODO: 4.3. Current Ongoing Campaigns Latest Contribution + } + } + + } + } + } + return updates; + } + + private static List processMyGroups(User u, + List updates, List myGroupMemberships) { + for (Membership membership : myGroupMemberships) { + WorkingGroup g = membership.getTargetGroup(); + + // 4.1. New Group Forum Posts + // TODO +// ResourceSpace gForum = g.getResources().get; +// List posts = null; +// Contribution latestForumPost = null; +// if (gForum !=null) posts = gForum.getContributions(); +// if (posts != null && !posts.isEmpty()) latestForumPost = posts.get(0); +// if (latestForumPost != null) +// updates.add(TransferUpdate.getInstance( +// AppcivistNotificationTypes.ASSEMBLY_UPDATE, +// AppcivistResourceTypes.CONTRIBUTION_COMMENT, +// AppcivistResourceTypes.ASSEMBLY, +// NOTIFICATION_TITLE_ASSEMBLY_UPDATE, +// NOTIFICATION_DESCRIPTION_ASSEMBLY_FORUM_CONTRIBUTION, u +// .getName(), u.getLanguage(), g.getAssemblyId(), +// g.getUuid(), g.getName(), latestForumPost +// .getContributionId(), +// latestForumPost.getUuid(), latestForumPost.getTitle(), +// latestForumPost.getText(), latestForumPost.getAuthor() +// .getName(), latestForumPost.getCreation())); + } + + return updates; + } + + private static List processMyContributions(User u, + List updates, List myContribs) { + // TODO + + return null; + } + + +} diff --git a/app/controllers/Users.java b/app/controllers/Users.java index ed9d2ffc..f0663be7 100644 --- a/app/controllers/Users.java +++ b/app/controllers/Users.java @@ -8,7 +8,6 @@ import models.TokenAction; import models.TokenAction.Type; -import models.Assembly; import models.User; import models.UserProfile; import models.transfer.TransferResponseStatus; @@ -23,7 +22,6 @@ import play.mvc.Http.Session; import play.mvc.Result; import play.mvc.With; -import views.html.*; import providers.MyLoginUsernamePasswordAuthUser; import providers.MyUsernamePasswordAuthProvider; import providers.MyUsernamePasswordAuthProvider.MyIdentity; @@ -32,6 +30,12 @@ import providers.MyUsernamePasswordAuthUser; import security.SecurityModelConstants; import utils.GlobalData; +import views.html.ask_link; +import views.html.ask_merge; +import views.html.link; +import views.html.password_change; +import views.html.profile; +import views.html.unverified; import be.objectify.deadbolt.java.actions.Dynamic; import be.objectify.deadbolt.java.actions.Group; import be.objectify.deadbolt.java.actions.Restrict; @@ -41,16 +45,16 @@ import com.feth.play.module.pa.user.AuthUser; import com.wordnik.swagger.annotations.Api; import com.wordnik.swagger.annotations.ApiImplicitParam; +import com.wordnik.swagger.annotations.ApiImplicitParams; import com.wordnik.swagger.annotations.ApiOperation; import com.wordnik.swagger.annotations.ApiParam; import com.wordnik.swagger.annotations.ApiResponse; import com.wordnik.swagger.annotations.ApiResponses; -import enums.MyRoles; import enums.ResponseStatus; @Api(value = "/user", description = "User Management operations, including authentication of users, " - + "merging/linking of accounts reading information about users") + + "merging/linking of accounts and reading information about user profiles") @With(Headers.class) public class Users extends Controller { public static final Form USER_FORM = form(User.class); @@ -135,6 +139,10 @@ public void setToken(String token) { * Read-Only Endpoints ***************************************************************************************************/ @ApiOperation(httpMethod = "GET", response = User.class, responseContainer = "List", produces = "application/json", value = "Get list of users", notes = "Get the full list of users. Only availabe to ADMINS") + @ApiResponses(value = { @ApiResponse(code = 400, message = "Request has errors", response = TransferResponseStatus.class) }) + @ApiImplicitParams({ + @ApiImplicitParam(name="SESSION_KEY", value = "User's auth key", dataType="String", paramType="header") + }) @Restrict({ @Group(GlobalData.ADMIN_ROLE) }) public static Result getUsers() { List users = User.findAll(); @@ -142,7 +150,7 @@ public static Result getUsers() { } @ApiOperation(httpMethod = "GET", response = User.class, produces = "application/json", value = "Get list of users", notes = "Get the full list of users. Only availabe to ADMINS") - @Dynamic(value = "OnlyMe", meta = SecurityModelConstants.USER_RESOURCE_PATH) + @Dynamic(value = "OnlyMeAndAdmin", meta = SecurityModelConstants.USER_RESOURCE_PATH) public static Result getUser( @ApiParam(value = "ID of the user", required = true) Long id) { Logger.info("Obtaining user with id = " + id); @@ -151,21 +159,26 @@ public static Result getUser( } @ApiOperation(nickname="loggedin", httpMethod = "GET", response = User.class, produces = "application/json", value = "Get session user", notes = "Get session user currently loggedin, as available in HTTP session") - @Dynamic(value = "OnlyMe", meta = SecurityModelConstants.USER_RESOURCE_PATH) + @Dynamic(value = "OnlyMeAndAdmin", meta = SecurityModelConstants.USER_RESOURCE_PATH) public static Result getCurrentUser(Long uid) { final User localUser = getLocalUser(session()); - if (localUser != null) + if (localUser != null) { + Logger.debug("Loggedin user: "+Json.toJson(localUser)); return ok(Json.toJson(localUser)); + } else return notFound(Json.toJson(TransferResponseStatus.noDataMessage( "No user logged in with session in this client", ""))); } - @ApiOperation(httpMethod = "GET", response = User.class, produces = "application/json, text/html", value = "Get session user", notes = "Get session user currently loggedin, as available in HTTP session") + @ApiOperation(httpMethod = "GET", response = UserProfile.class, produces = "application/json, text/html", value = "Get session user's profile", notes = "Get session user currently loggedin, as available in HTTP session") @ApiResponses(value = { @ApiResponse(code = 404, message = "User not found", response=TransferResponseStatus.class) }) - @ApiImplicitParam(name="profile", value="profile", paramType="path") + @ApiImplicitParams({ + @ApiImplicitParam(name="uid", paramType="path", dataType = "Long", value = "User's numerical ID"), + //@ApiImplicitParam(name="profile", value="profile", paramType="path") + }) @Dynamic(value = "OnlyMe", meta = SecurityModelConstants.USER_RESOURCE_PATH) - public static Result profile(@ApiParam(value="User id",required=true) Long uid) { + public static Result profile(Long uid) { final UserProfile userProfile = UserProfile.readByUserId(uid); if (userProfile!=null) { if(request().getHeader("Content-Type") !=null && request().getHeader("Content-Type").equals("text/html")) @@ -187,6 +200,9 @@ public static Result profile(@ApiParam(value="User id",required=true) Long uid) ***************************************************************************************************/ @ApiOperation(nickname="signup", httpMethod = "POST", response = User.class, produces = "application/json", value = "Creates a new unverified user with an email and a password. Sends a verification email.") @ApiResponses(value = { @ApiResponse(code = 400, message = "Request has errors", response = TransferResponseStatus.class) }) + @ApiImplicitParams({ + @ApiImplicitParam(name="signup_form", value = "User's signup form", dataType="providers.MySignup", paramType="body") + }) public static Result doSignup() { com.feth.play.module.pa.controllers.Authenticate.noCache(response()); final Form filledForm = MyUsernamePasswordAuthProvider.SIGNUP_FORM @@ -203,10 +219,17 @@ public static Result doSignup() { return MyUsernamePasswordAuthProvider.handleSignup(ctx()); } + public static Result login() { + return ok("Not implemented yet"); + } + @ApiOperation(nickname="login", httpMethod = "POST", response = User.class, produces = "application/json", value = "Creates a new session key for the requesting user, if the system authenticates him/her.") @ApiResponses(value = { @ApiResponse(code = 404, message = "User not found", response = TransferResponseStatus.class), @ApiResponse(code = 400, message = "Request has errors", response = TransferResponseStatus.class) }) + @ApiImplicitParams({ + @ApiImplicitParam(name="login_form", value="User's Login Credentials", dataType="providers.MyLogin", paramType="body") + }) public static Result doLogin() throws InstantiationException, IllegalAccessException { com.feth.play.module.pa.controllers.Authenticate.noCache(response()); @@ -227,7 +250,11 @@ public static Result doLogin() throws InstantiationException, } - @ApiOperation(nickname="logout", httpMethod = "POST", response = User.class, produces = "text/html", value = "Expires the session key of the requesting user") + @ApiOperation(nickname="logout", httpMethod = "POST", response = User.class, consumes = "application/json", value = "Expires the session key of the requesting user") + @ApiImplicitParams({ + @ApiImplicitParam(name="SESSION_KEY", value="User's session authentication key", dataType="String", paramType="header"), + @ApiImplicitParam(name="user_body", value="Logout body must be empty JSON", dataType="String", paramType="body") + }) @Dynamic(value = "OnlyMe", meta = SecurityModelConstants.USER_RESOURCE_PATH) public static Result doLogout() { // TODO: modify to return HTML or JSON instead of redirect @@ -240,8 +267,13 @@ public static Result doLogout() { ***************************************************************************************************/ @ApiOperation(httpMethod = "PUT", response = TransferResponseStatus.class, produces = "application/json", value = "Update user information", notes = "Updates user information") @ApiResponses(value = { @ApiResponse(code = 404, message = "User not found", response=TransferResponseStatus.class) }) + @ApiImplicitParams({ + @ApiImplicitParam(name="uid", value="User's ID", dataType="Long", paramType="path"), + @ApiImplicitParam(name="SESSION_KEY", value="User's session authentication key", dataType="String", paramType="header"), + @ApiImplicitParam(name="user", value="User's updated information", dataType="models.User", paramType = "body") + }) @Dynamic(value = "OnlyMe", meta = SecurityModelConstants.USER_RESOURCE_PATH) - public static Result updateUser(@ApiParam(value="userId", required=true) Long uid) { + public static Result updateUser(Long uid) { final Form updatedUserForm = USER_FORM.bindFromRequest(); if (updatedUserForm.hasErrors()) return badRequest(Json.toJson(TransferResponseStatus.badMessage( @@ -263,8 +295,13 @@ public static Result updateUser(@ApiParam(value="userId", required=true) Long ui @ApiOperation(nickname="profile", httpMethod = "PUT", response = TransferResponseStatus.class, produces = "application/json", value = "Update user information", notes = "Updates user information") @ApiResponses(value = { @ApiResponse(code = 404, message = "User not found", response=TransferResponseStatus.class) }) + @ApiImplicitParams({ + @ApiImplicitParam(name="uid", value="User's ID", dataType="Long", paramType="path"), + @ApiImplicitParam(name="SESSION_KEY", value="User's session authentication key", dataType="String", paramType="header"), + @ApiImplicitParam(name="user", value="User's updated profile information", dataType="models.UserProfile", paramType = "body") + }) @Dynamic(value = "OnlyMe", meta = SecurityModelConstants.USER_RESOURCE_PATH) - public static Result updateUserProfile(@ApiParam(value="userId", required=true) Long uid) { + public static Result updateUserProfile(Long uid) { final Form updatedUserForm = USER_PROFILE_FORM.bindFromRequest(); if (updatedUserForm.hasErrors()) return badRequest(Json.toJson(TransferResponseStatus.badMessage( @@ -291,14 +328,15 @@ public static Result updateUserProfile(@ApiParam(value="userId", required=true) ***************************************************************************************************/ @ApiOperation(httpMethod = "DELETE", response = TransferResponseStatus.class, produces = "application/json", value = "Soft delete of an user", notes = "Soft delete of an user by simply deactivating it") @ApiResponses(value = { @ApiResponse(code = 404, message = "User not found", response = TransferResponseStatus.class) }) - @Dynamic(value = "OnlyMe", meta = SecurityModelConstants.USER_RESOURCE_PATH) - public static Result deleteUser( - @ApiParam(value = "ID of the user", required = true) Long id) { + @ApiImplicitParams({ + @ApiImplicitParam(name="uid", value="User's ID", dataType="Long", paramType="path"), + @ApiImplicitParam(name="SESSION_KEY", value="User's session authentication key", dataType="String", paramType="header") + }) + @Dynamic(value = "OnlyMeAndAdmin", meta = SecurityModelConstants.USER_RESOURCE_PATH) + public static Result deleteUser(Long id) { Logger.info("Obtaining user with id = " + id); User u = User.findByUserId(id); - TransferResponseStatus responseBody = new TransferResponseStatus(); - if (u != null) { u.setActive(false); u.update(); @@ -307,17 +345,19 @@ public static Result deleteUser( responseBody.setNewResourceURL(routes.Users + "/" + u.getUserId()); return ok(Json.toJson(responseBody)); } else { - responseBody.setStatusMessage("User " + u.getUserId() - + " not found"); + responseBody.setStatusMessage("User "+id+ " not found"); return notFound(Json.toJson(responseBody)); } } @ApiOperation(httpMethod = "DELETE", response = TransferResponseStatus.class, produces = "application/json", value = "Delete a user", notes = "Delete a user, but not his/her contributions") @ApiResponses(value = { @ApiResponse(code = 404, message = "User not found", response = TransferResponseStatus.class) }) - @Dynamic(value = "OnlyMe", meta = SecurityModelConstants.USER_RESOURCE_PATH) - public static Result deleteUserForce( - @ApiParam(value = "ID of the user", required = true) Long id) { + @ApiImplicitParams({ + @ApiImplicitParam(name="uid", value="User's ID", dataType="Long", paramType="path"), + @ApiImplicitParam(name="SESSION_KEY", value="User's session authentication key", dataType="String", paramType="header") + }) + @Dynamic(value = "OnlyMeAndAdmin", meta = SecurityModelConstants.USER_RESOURCE_PATH) + public static Result deleteUserForce(Long id) { Logger.info("Obtaining user with id = " + id); User u = User.findByUserId(id); @@ -341,8 +381,10 @@ public static Result deleteUserForce( ***************************************************************************************************/ @ApiOperation(nickname="verify", httpMethod = "GET", response = TransferResponseStatus.class, produces = "application/json", value = "Verify invitation token") @ApiResponses(value = { @ApiResponse(code = 400, message = "Error in Request", response = TransferResponseStatus.class) }) - public static Result verify( - @ApiParam(name = "token", required = true) final String token) { + @ApiImplicitParams({ + @ApiImplicitParam(name="token", value="Verification token", dataType="String", paramType="path") + }) + public static Result verify(final String token) { com.feth.play.module.pa.controllers.Authenticate.noCache(response()); final TokenAction ta = tokenIsValid(token, Type.EMAIL_VERIFICATION); if (ta == null) { @@ -370,13 +412,19 @@ public static Result verify( * TODO: document with swagger annotations */ @ApiOperation(nickname="link", httpMethod = "GET", produces = "text/html", value = "Returns a form to link external auth provider accounts") + @ApiImplicitParams({ + @ApiImplicitParam(name="SESSION_KEY", value="User's session authentication key", dataType="String", paramType="header"), + }) public static Result link() { com.feth.play.module.pa.controllers.Authenticate.noCache(response()); return ok(link.render()); } - - + @ApiOperation(httpMethod = "GET", value = "Gets page for email verification") + @ApiResponses(value = { @ApiResponse(code = 404, message = "User not found", response=TransferResponseStatus.class) }) + @ApiImplicitParams({ + @ApiImplicitParam(name="SESSION_KEY", value="User's session authentication key", dataType="String", paramType="header"), + }) @Dynamic(value = "OnlyMe", meta = SecurityModelConstants.USER_RESOURCE_PATH) public static Result verifyEmail() { com.feth.play.module.pa.controllers.Authenticate.noCache(response()); @@ -400,6 +448,10 @@ public static Result verifyEmail() { } + @ApiOperation(httpMethod = "GET", produces = "application/html", value = "Gets form to update password") + @ApiImplicitParams({ + @ApiImplicitParam(name="SESSION_KEY", value="User's session authentication key", dataType="String", paramType="header"), + }) @Restrict(@Group(GlobalData.USER_ROLE)) public static Result changePassword() { com.feth.play.module.pa.controllers.Authenticate.noCache(response()); @@ -411,7 +463,12 @@ public static Result changePassword() { return ok(password_change.render(PASSWORD_CHANGE_FORM)); } } - + + @ApiOperation(httpMethod = "POST", produces = "application/html", value = "Changes user's password") + @ApiImplicitParams({ + @ApiImplicitParam(name="SESSION_KEY", value="User's session authentication key", dataType="String", paramType="header"), + @ApiImplicitParam(name="password_change_form", value="User's updated password form", dataType="controllers.Users.PassworChange", paramType = "body") + }) @Restrict(@Group(GlobalData.USER_ROLE)) public static Result doChangePassword() { com.feth.play.module.pa.controllers.Authenticate.noCache(response()); @@ -524,7 +581,6 @@ public static Result unverified() { } - public static Result forgotPassword(final String email) { com.feth.play.module.pa.controllers.Authenticate.noCache(response()); @SuppressWarnings("unused") @@ -705,4 +761,9 @@ public static Result oAuthDenied(final String providerKey) { return redirect(routes.Application.index()); } + public static Result redirectAfterLogout() { + return ok("You have been logout!. If you were trying to login, there was a session already from your browser for another user."); + } + + } diff --git a/app/enums/AppcivistNotificationTypes.java b/app/enums/AppcivistNotificationTypes.java new file mode 100644 index 00000000..78f19006 --- /dev/null +++ b/app/enums/AppcivistNotificationTypes.java @@ -0,0 +1,12 @@ +package enums; + +public enum AppcivistNotificationTypes { + UPCOMING_MILESTONE, + ASSEMBLY_UPDATE, + CAMPAIGN_UPDATE, + WORKING_GROUP_UPDATE, + CONTRIBUTION_UPDATE, + PRIVATE_MESSAGE, + FOLLOWED_ASSEMBLY_UPDATE, + FOLLOWED_CAMPAIGN_UPDATE +} diff --git a/app/enums/AppcivistResourceTypes.java b/app/enums/AppcivistResourceTypes.java new file mode 100644 index 00000000..1e9311c6 --- /dev/null +++ b/app/enums/AppcivistResourceTypes.java @@ -0,0 +1,27 @@ +package enums; + +public enum AppcivistResourceTypes { + CONTRIBUTION_IDEA, // Contributions are resources that are introduced in the system by single users + CONTRIBUTION_QUESTION, + CONTRIBUTION_COMMENT, + CONTRIBUTION_ISSUE, + CONTRIBUTION_PROPOSAL, // Proposals are created and edited by Working Groups + CONTRIBUTION_DISCUSSION, // Discussions are created and edited by Assemblies or Working Groups + ELECTION, // Discussions are created and edited by Assemblies + BALLOT, // Ballots are created by Assemblies + VOTE, // Votes are created by single Users + PROPOSAL_TEMPLATE, // Templates are created and edited by Assemblies + EXTERNAL, // External resources can be anything that has a URL + ASSEMBLY, + CAMPAIGN, + CAMPAIGN_PHASE, + WORKING_GROUP, + + PICTURE, + VIDEO, + PAD, + TEXT, + WEBPAGE, + FILE + +} diff --git a/app/models/Assembly.java b/app/models/Assembly.java index 26fda42a..321b4c97 100644 --- a/app/models/Assembly.java +++ b/app/models/Assembly.java @@ -17,6 +17,7 @@ import play.data.validation.Constraints.MaxLength; import utils.GlobalData; +import com.avaje.ebean.Query; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; @@ -324,10 +325,29 @@ public void setDefaultValues() { } public static List findBySimilarName(String query) { - return find.where().like("name", "%" + query + "%").findList(); + return find.where().ilike("name", "%" + query + "%").findList(); } - public static List findFeaturedAssemblies() { - return find.setMaxRows(6).orderBy("creation").findList(); + public static List findFeaturedAssemblies(String query) { + Query q = find.setMaxRows(6).orderBy("creation"); + q = addQueryCriteria("name",query, q); + return q.findList(); + } + + public static List findRandomAssemblies(String query) { + Query q = find.setMaxRows(6).orderBy("random()"); + q = addQueryCriteria("name",query, q); + return q.findList(); + } + + private static Query addQueryCriteria(String property, String query, Query q) { + if(query!=null && !query.isEmpty()) { + q = q.where().ilike(property, "%"+query+"%").query(); + } + return q; + } + + public static Assembly readByUUID(UUID assemblyUUID) { + return find.where().eq("uuid", assemblyUUID).findUnique(); } } diff --git a/app/models/AssemblyProfile.java b/app/models/AssemblyProfile.java index dee72052..2d59a04b 100644 --- a/app/models/AssemblyProfile.java +++ b/app/models/AssemblyProfile.java @@ -34,7 +34,7 @@ public class AssemblyProfile extends AppCivistBaseModel { @Enumerated(EnumType.STRING) private SupportedMembershipRegistration supportedMembership = SupportedMembershipRegistration.INVITATION_AND_REQUEST; // OPEN, INVITATION, REQUEST, INVITATION_AND_REQUEST @Enumerated(EnumType.STRING) - private ManagementTypes managementType = ManagementTypes.OPEN; // assemblies are coordinated by default + private ManagementTypes managementType = ManagementTypes.OPEN; // assemblies are OPEN by default private Visibility visibility = Visibility.PUBLIC; private String icon = GlobalData.APPCIVIST_ASSEMBLY_DEFAULT_ICON; // a small icon to represent the assembly private String cover = GlobalData.APPCIVIST_ASSEMBLY_DEFAULT_COVER; // cover picture of the assembly, to appear on the top of its page diff --git a/app/models/Campaign.java b/app/models/Campaign.java index f9107bc2..11b45b50 100644 --- a/app/models/Campaign.java +++ b/app/models/Campaign.java @@ -1,6 +1,7 @@ package models; import java.util.ArrayList; +import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.UUID; @@ -307,5 +308,26 @@ private void populateDefaultPhases(List defaultPhaseDefinitions } } + public static List extractOngoingCampaignsFromAssembly(Assembly a) { + List ongoingCampaigns = new ArrayList(); + ResourceSpace resources = a.getResources(); + List campaigns = null; + if (resources !=null) campaigns = resources.getCampaigns(); + if (campaigns != null && !campaigns.isEmpty()) { + for (Campaign c : campaigns) { + List phases = c.getPhases(); + if (phases != null && !phases.isEmpty()) { + for (CampaignPhase p : phases) { + Calendar today = Calendar.getInstance(); + if (p.getStartDate().before(today.getTime()) && p.getEndDate().after(today.getTime())) { + ongoingCampaigns.add(c); + break; + } + } + } + } + } + return ongoingCampaigns; + } } diff --git a/app/models/Config.java b/app/models/Config.java index 60c57b98..3f8f3abb 100644 --- a/app/models/Config.java +++ b/app/models/Config.java @@ -13,10 +13,13 @@ import javax.persistence.Transient; import com.avaje.ebean.ExpressionList; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; import enums.ConfigTargets; @Entity +@JsonInclude(Include.NON_EMPTY) public class Config extends AppCivistBaseModel { @Id diff --git a/app/models/Contribution.java b/app/models/Contribution.java index 05fdfb33..f391796c 100644 --- a/app/models/Contribution.java +++ b/app/models/Contribution.java @@ -11,7 +11,6 @@ import com.fasterxml.jackson.annotation.JsonManagedReference; import enums.ContributionTypes; -import models.location.Geo; import models.location.Location; @Entity @@ -27,6 +26,8 @@ public class Contribution extends AppCivistBaseModel { private String text; @Enumerated(EnumType.STRING) private ContributionTypes type = ContributionTypes.COMMENT; + + @ManyToOne(cascade=CascadeType.ALL) private User author; @ManyToMany(cascade = CascadeType.ALL) @@ -38,7 +39,7 @@ public class Contribution extends AppCivistBaseModel { @ManyToMany(cascade = CascadeType.ALL) private List contributionCategories = new ArrayList(); - @OneToMany(cascade = CascadeType.ALL) + @ManyToMany(cascade = CascadeType.ALL) private List attachments = new ArrayList(); @OneToOne @@ -308,4 +309,8 @@ public static List readListByAssemblyAndType(Long assemblyId, public static void deleteContributionByIdAndType(Long contributionId,ContributionTypes cType) { find.where().eq("contributionId", contributionId).eq("type", cType).findUnique().delete(); } + + public static List readByCreator(User u) { + return find.where().eq("author",u).findList(); + } } diff --git a/app/models/ContributionProposal.java b/app/models/ContributionProposal.java index 84fe9cf0..8596cd2e 100644 --- a/app/models/ContributionProposal.java +++ b/app/models/ContributionProposal.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.List; +import javax.persistence.CascadeType; import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; import javax.persistence.ManyToMany; @@ -12,10 +13,10 @@ @DiscriminatorValue("PROPOSAL") public class ContributionProposal extends Contribution { - @OneToOne + @OneToOne(cascade=CascadeType.ALL) private ResourcePad proposalPad; - - @OneToOne + + @OneToOne(cascade=CascadeType.ALL) private ResourcePad proposalTemplate; @ManyToMany diff --git a/app/models/Membership.java b/app/models/Membership.java index df28c60c..436dc48e 100644 --- a/app/models/Membership.java +++ b/app/models/Membership.java @@ -17,14 +17,18 @@ import javax.persistence.ManyToMany; import javax.persistence.ManyToOne; -import models.TokenAction.Type; +import com.avaje.ebean.Query; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import models.TokenAction.Type; import enums.ManagementTypes; import enums.MembershipStatus; @Entity @Inheritance(strategy = InheritanceType.JOINED) @DiscriminatorColumn(name = "MEMBERSHIP_TYPE") +@JsonInclude(Include.NON_EMPTY) public class Membership extends AppCivistBaseModel { @Id @GeneratedValue @@ -242,4 +246,12 @@ public static void verify(Long id, User targetUser) { TokenAction.deleteByUser(targetUser, Type.MEMBERSHIP_INVITATION); } + public static List findByUser(User u, String membershipType) { + Query q = find.where().eq("user",u).query(); + if (!membershipType.isEmpty()) + q = q.where().eq("membershipType", membershipType).query(); + List membs = q.findList(); + return membs; + } + } diff --git a/app/models/MembershipGroup.java b/app/models/MembershipGroup.java index 88f06067..f79b14ff 100644 --- a/app/models/MembershipGroup.java +++ b/app/models/MembershipGroup.java @@ -7,10 +7,14 @@ import javax.persistence.Entity; import javax.persistence.ManyToOne; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + import enums.MembershipStatus; @Entity @DiscriminatorValue("GROUP") +@JsonInclude(Include.NON_EMPTY) public class MembershipGroup extends Membership { @ManyToOne(cascade=CascadeType.ALL) diff --git a/app/models/Resource.java b/app/models/Resource.java index eba6900f..cbd81211 100644 --- a/app/models/Resource.java +++ b/app/models/Resource.java @@ -29,7 +29,8 @@ public class Resource extends AppCivistBaseModel { private URL url; private User creator; private Location location; - @Column(name = "RESOURCE_TYPE") + + @Column(name = "RESOURCE_TYPE", insertable = false, updatable = false) @Enumerated(EnumType.STRING) private ResourceTypes resourceType; @@ -92,9 +93,9 @@ public ResourceTypes getResourceType() { return resourceType; } - public void setResourceType(ResourceTypes resourceType) { - this.resourceType = resourceType; - } +// public void setResourceType(ResourceTypes resourceType) { +// this.resourceType = resourceType; +// } /* * Basic Data operations diff --git a/app/models/ResourceSpace.java b/app/models/ResourceSpace.java index 8ce46120..9883a9d2 100644 --- a/app/models/ResourceSpace.java +++ b/app/models/ResourceSpace.java @@ -9,30 +9,18 @@ import javax.persistence.EnumType; import javax.persistence.Enumerated; import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; import javax.persistence.Id; -import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; -import javax.persistence.OneToMany; -import javax.persistence.OneToOne; import javax.validation.constraints.Size; -import models.location.Location; -import play.data.validation.Constraints.MaxLength; -import utils.GlobalData; - -import com.avaje.ebean.annotation.Formula; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonManagedReference; import enums.CampaignTypesEnum; -import enums.ManagementTypes; import enums.ResourceSpaceTypes; -import enums.SupportedMembershipRegistration; -import enums.Visibility; @Entity @JsonInclude(Include.NON_EMPTY) diff --git a/app/models/User.java b/app/models/User.java index f45d790e..145ca0e1 100644 --- a/app/models/User.java +++ b/app/models/User.java @@ -13,6 +13,7 @@ import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinTable; @@ -64,7 +65,7 @@ public class User extends Model implements Subject { @Column(name = "email_verified") private Boolean emailVerified; @Column(name = "profile_pic") - @OneToOne + @OneToOne(fetch=FetchType.LAZY, cascade=CascadeType.ALL) @JsonIgnoreProperties({"creator", "resourceId", "location", "resourceType"}) private ResourcePicture profilePic; @Column @@ -108,8 +109,7 @@ public class User extends Model implements Subject { /** * Static finder property */ - public static Finder find = new Finder( - Long.class, User.class); + public static Finder find = new Finder<>(User.class); public User(){ super(); @@ -501,7 +501,7 @@ public static User createFromAuthUser(final AuthUser authUser) throws HashGenera * 7. Generate the username * TODO add username to the signup form */ - user.setUsername(models.User.generateUsername(user.getEmail())); + user.setUsername(user.getEmail()); /* * 8. Set language of user @@ -532,16 +532,17 @@ private static String getDefaultProfilePictureURL(String email) throws HashGener private static ResourcePicture getDefaultProfilePictureResource(User user) throws HashGenerationException, MalformedURLException { String picture = getDefaultProfilePictureURL(user.getEmail()); - String large = picture="&s=128"; - String medium = picture="&s=64"; - String thumbnail = picture="&s=32"; + String large = picture+"&s=128"; + String medium = picture+"&s=64"; + String thumbnail = picture+"&s=32"; ResourcePicture profilePicResource = new ResourcePicture(user, new URL(picture), new URL(large), new URL(medium), new URL(thumbnail)); return profilePicResource; } public void addRole(SecurityRole role) { this.roles.add(role); } - + + @SuppressWarnings("unused") private static String generateUsername(String email) { String newUsername = email.split("@")[0]; int count = models.User.usernameExists(newUsername); @@ -591,4 +592,9 @@ public static User findByUserName(String userName) .findUnique(); } + + public static User findByUUID(UUID uuid) { + return find.where().eq("uuid", uuid).findUnique(); + } + } diff --git a/app/models/WorkingGroup.java b/app/models/WorkingGroup.java index 80c61dbb..fe15eaac 100644 --- a/app/models/WorkingGroup.java +++ b/app/models/WorkingGroup.java @@ -1,19 +1,30 @@ package models; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToMany; +import javax.persistence.OneToOne; + import com.avaje.ebean.ExpressionList; import com.avaje.ebean.annotation.Formula; -import com.fasterxml.jackson.annotation.JsonManagedReference; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; import enums.ManagementTypes; import enums.ResourceSpaceTypes; -import javax.persistence.*; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - @Entity +@JsonInclude(Include.NON_EMPTY) public class WorkingGroup extends AppCivistBaseModel { @Id @GeneratedValue @@ -27,10 +38,9 @@ public class WorkingGroup extends AppCivistBaseModel { private ManagementTypes managementType = ManagementTypes.OPEN; private User creator; - @Formula(select="select c from config c where c.targetUuid=${ta}.uuid") - private List workingGroupConfigs = new ArrayList(); - - +// @Formula(select="select c from config c where c.targetUuid=${ta}.uuid") +// private List workingGroupConfigs = new ArrayList(); + @ManyToMany(cascade=CascadeType.ALL) // @JoinTable(name="working_groups_assembly", // joinColumns = { @@ -41,7 +51,18 @@ public class WorkingGroup extends AppCivistBaseModel { // }) private List assemblies = new ArrayList(); - @OneToOne(fetch=FetchType.LAZY,cascade = CascadeType.ALL) + /** + * The group resource space contains its configurations, themes, associated campaigns + */ + @OneToOne(fetch = FetchType.LAZY, cascade=CascadeType.ALL) + //@JoinColumn(name="resource_uuid", unique= true, nullable=true, insertable=true, updatable=true, referencedColumnName="uuid") + // @JoinTable( + // name="assembly_resource_space", + // joinColumns= + // @JoinColumn(name="assemblyId", referencedColumnName="assembly_id"), + // inverseJoinColumns= + // @JoinColumn(name="uuid", referencedColumnName="resource_space")) + @JsonIgnoreProperties({ "uuid" }) private ResourceSpace resources; // TODO: think about how to make Assemblies, Groups, Users, Contributions, and Proposals; @@ -103,13 +124,18 @@ public void setCreator(User creator) { this.creator = creator; } - public List getWorkingGroupConfigs() { - return workingGroupConfigs; - } - - public void setWorkingGroupConfigs(List workingGroupConfigs) { - this.workingGroupConfigs = workingGroupConfigs; - } +// public List getWorkingGroupConfigs() { +// if (workingGroupConfigs == null) { +// workingGroupConfigs = new ArrayList(); +// } +// return workingGroupConfigs; +// } +// +// public void setWorkingGroupConfigs(List workingGroupConfigs) { +// if (workingGroupConfigs == null) +// workingGroupConfigs = new ArrayList(); +// this.workingGroupConfigs = workingGroupConfigs; +// } public Long getGroupId() { return groupId; diff --git a/app/models/transfer/TransferUpdate.java b/app/models/transfer/TransferUpdate.java new file mode 100644 index 00000000..e26224fa --- /dev/null +++ b/app/models/transfer/TransferUpdate.java @@ -0,0 +1,163 @@ +package models.transfer; + +import java.util.Date; +import java.util.UUID; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +import play.i18n.Messages; +import enums.AppcivistNotificationTypes; +import enums.AppcivistResourceTypes; + +@JsonInclude(Include.NON_EMPTY) +public class TransferUpdate { + private AppcivistNotificationTypes type; + private AppcivistResourceTypes resourceType; + private AppcivistResourceTypes containerType; + private String title; + private String text; + private String resourceSummary; + private Long resourceId; + private UUID resourceUUID; + private Long containerId; + private UUID containerUUID; + private Date date; + + public TransferUpdate() { + super(); + } + + public TransferUpdate(AppcivistNotificationTypes type, + AppcivistResourceTypes resourceType, AppcivistResourceTypes containerType, String title, String text, + String resourceSummary, Long resourceId, UUID resourceUUID, Long containerId, UUID containerUUID, + Date date) { + super(); + this.type = type; + this.resourceType = resourceType; + this.setContainerType(containerType); + this.title = title; + this.text = text; + this.resourceSummary = resourceSummary; + this.resourceId = resourceId; + this.resourceUUID = resourceUUID; + this.setContainerId(containerId); + this.setContainerUUID(containerUUID); + this.date = date; + } + + public AppcivistNotificationTypes getType() { + return type; + } + public void setType(AppcivistNotificationTypes type) { + this.type = type; + } + public AppcivistResourceTypes getResourceType() { + return resourceType; + } + public void setResourceType(AppcivistResourceTypes resourceType) { + this.resourceType = resourceType; + } + public AppcivistResourceTypes getContainerType() { + return containerType; + } + + public void setContainerType(AppcivistResourceTypes containerType) { + this.containerType = containerType; + } + + public String getTitle() { + return title; + } + public void setTitle(String title) { + this.title = title; + } + public String getText() { + return text; + } + public void setText(String text) { + this.text = text; + } + public String getResourceSummary() { + return resourceSummary; + } + public void setResourceSummary(String resourceSummary) { + this.resourceSummary = resourceSummary; + } + public Long getResourceId() { + return resourceId; + } + public void setResourceId(Long resourceId) { + this.resourceId = resourceId; + } + public UUID getResourceUUID() { + return resourceUUID; + } + public void setResourceUUID(UUID resourceUUID) { + this.resourceUUID = resourceUUID; + } + public Long getContainerId() { + return containerId; + } + + public void setContainerId(Long containerId) { + this.containerId = containerId; + } + + public UUID getContainerUUID() { + return containerUUID; + } + + public void setContainerUUID(UUID containerUUID) { + this.containerUUID = containerUUID; + } + + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } + + + + + public static TransferUpdate getInstance( + final AppcivistNotificationTypes updateType, + final AppcivistResourceTypes resourceType, + final AppcivistResourceTypes containerType, + final String titleMessageKey, + final String descriptionMessageKey, + final String userName, + final String userLang, + final Long containerId, + final UUID containerUUID, + final String containerName, + final Long resourceId, + final UUID resourceUUID, + final String resourceTitle, + final String resourceText, + final String resourceAuthor, + final Date resourceCreationDate) { + + String title = Messages.get(userLang, titleMessageKey, ""); + String desc = ""; + if(updateType.equals(AppcivistNotificationTypes.UPCOMING_MILESTONE)) + desc = Messages.get(userLang, descriptionMessageKey, resourceAuthor, containerName); + else + desc = Messages.get(userLang, descriptionMessageKey, resourceTitle, resourceText, containerName); + + return new TransferUpdate( + updateType, + resourceType, + containerType, + title, + desc, + (resourceTitle+"/n"+resourceText).substring(0, 256), + resourceId, resourceUUID, + containerId, containerUUID, + resourceCreationDate); + + } +} diff --git a/app/providers/MyIdentity.java b/app/providers/MyIdentity.java new file mode 100644 index 00000000..90bdabe5 --- /dev/null +++ b/app/providers/MyIdentity.java @@ -0,0 +1,6 @@ +package providers; +public class MyIdentity extends MyUsernamePasswordAuthProvider.MyIdentity { + public MyIdentity() { + super(); + } +} \ No newline at end of file diff --git a/app/providers/MyLogin.java b/app/providers/MyLogin.java new file mode 100644 index 00000000..9d9b4629 --- /dev/null +++ b/app/providers/MyLogin.java @@ -0,0 +1,13 @@ +package providers; +/** + * Dummy class for documentation purposes + * Swagger automatic REST documentation cannot read nested static classes, so we create these proper classes extending them to reference in documentation + * @author cdparra + * + */ +public class MyLogin extends providers.MyUsernamePasswordAuthProvider.MyLogin { + public MyLogin() { + super(); + } +} + diff --git a/app/providers/MySignup.java b/app/providers/MySignup.java new file mode 100644 index 00000000..b2e5fc13 --- /dev/null +++ b/app/providers/MySignup.java @@ -0,0 +1,12 @@ +package providers; +/** + * Dummy class for documentation purposes + * Swagger automatic REST documentation cannot read nested static classes, so we create these proper classes extending them to reference in documentation + * @author cdparra + * + */ +public class MySignup extends providers.MyUsernamePasswordAuthProvider.MySignup { + public MySignup() { + super(); + } +} \ No newline at end of file diff --git a/app/providers/MyUsernamePasswordAuthProvider.java b/app/providers/MyUsernamePasswordAuthProvider.java index 198acfe5..4fc1ad7f 100644 --- a/app/providers/MyUsernamePasswordAuthProvider.java +++ b/app/providers/MyUsernamePasswordAuthProvider.java @@ -110,11 +110,7 @@ public static class MySignup extends MyLogin { @Required @MinLength(5) private String repeatPassword; - - public String name; - - private Long userId; - + public String name; private String lang; public String validate() { @@ -139,14 +135,6 @@ public String getRepeatPassword() { public void setRepeatPassword(String repeatPassword) { this.repeatPassword = repeatPassword; } - - public Long getUserId() { - return userId; - } - - public void setUserId(Long userId) { - this.userId = userId; - } public void setLang(String lang) { this.lang=lang; diff --git a/app/providers/MyUsernamePasswordAuthUser.java b/app/providers/MyUsernamePasswordAuthUser.java index 5a63d82a..02d677ef 100644 --- a/app/providers/MyUsernamePasswordAuthUser.java +++ b/app/providers/MyUsernamePasswordAuthUser.java @@ -1,6 +1,5 @@ package providers; -import models.ResourcePicture; import providers.MyUsernamePasswordAuthProvider.MySignup; import com.feth.play.module.pa.providers.password.UsernamePasswordAuthUser; @@ -16,14 +15,17 @@ public class MyUsernamePasswordAuthUser extends UsernamePasswordAuthUser */ private static final long serialVersionUID = 1L; private final String name; - private Long userId; private String picture; - + + /** + * Added userId to send in response after login or signup + */ + private Long userId; + private String lang; public MyUsernamePasswordAuthUser(final MySignup signup) { super(signup.password, signup.email); this.name = signup.name; - this.userId = signup.getUserId(); } /** @@ -36,7 +38,6 @@ public MyUsernamePasswordAuthUser(final MySignup signup) { public MyUsernamePasswordAuthUser(final String name, final Long userId, final String email, final String password) { super(password, email); this.name = name; - this.userId = userId; } /** @@ -46,7 +47,6 @@ public MyUsernamePasswordAuthUser(final String name, final Long userId, final St public MyUsernamePasswordAuthUser(final String password) { super(password, null); this.name = null; - this.userId = null; } /** @@ -56,7 +56,6 @@ public MyUsernamePasswordAuthUser(final String password) { public MyUsernamePasswordAuthUser(String name,Long userId, String picture, String email, String password) { super(password, email); this.name = null; - this.userId = null; this.picture=picture; } @@ -65,16 +64,26 @@ public String getName() { return name; } + @Override + public String getPicture() { + return this.picture; + } + public Long getUserId() { return userId; } - - public void setUserId (Long userId) { - this.userId=userId; + + public void setUserId(Long userId) { + this.userId = userId; } - @Override - public String getPicture() { - return this.picture; + public String getLang() { + return lang; + } + + public void setLang(String lang) { + this.lang = lang; } + + } diff --git a/app/security/MyDynamicResourceHandler.java b/app/security/MyDynamicResourceHandler.java index 3ceaacc5..68cf22c1 100644 --- a/app/security/MyDynamicResourceHandler.java +++ b/app/security/MyDynamicResourceHandler.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.UUID; import org.apache.commons.lang3.StringUtils; @@ -48,7 +49,8 @@ public F.Promise checkPermission( String s, HANDLERS.put("AssemblyMemberIsExpert", Optional.of(new AssemblyDynamicResourceHandler())); HANDLERS.put("CanInviteToGroup", Optional.of(new GroupDynamicResourceHandler())); HANDLERS.put("CanInviteToAssembly", Optional.of(new AssemblyDynamicResourceHandler())); - HANDLERS.put("OnlyMe", Optional.of(new OnlyMeDynamicResourceHandler())); + HANDLERS.put("OnlyMe", Optional.of(new OnlyMeDynamicResourceHandler())); + HANDLERS.put("OnlyMeAndAdmin", Optional.of(new OnlyMeAndAdminDynamicResourceHandler())); } @Override @@ -91,6 +93,17 @@ public static Long getIdFromPath(String path, String id_from){ String id = StringUtils.substringAfter(path, id_from); if(StringUtils.contains(id, "/")) id = id.split("/")[0]; - return Long.parseLong(id); + try { + return Long.parseLong(id); + } catch (Exception e) { + return new Long(-1); + } + } + + public static UUID getUUIDFromPath(String path, String id_from){ + String id = StringUtils.substringAfter(path, id_from); + if(StringUtils.contains(id, "/")) + id = id.split("/")[0]; + return UUID.fromString(id); } } diff --git a/app/security/OnlyMeAndAdminDynamicResourceHandler.java b/app/security/OnlyMeAndAdminDynamicResourceHandler.java new file mode 100644 index 00000000..3780b8f4 --- /dev/null +++ b/app/security/OnlyMeAndAdminDynamicResourceHandler.java @@ -0,0 +1,72 @@ +package security; + +import java.util.UUID; + +import models.User; +import play.Logger; +import play.libs.F; +import play.libs.F.Promise; +import play.mvc.Http.Context; +import be.objectify.deadbolt.core.DeadboltAnalyzer; +import be.objectify.deadbolt.core.models.Subject; +import be.objectify.deadbolt.java.AbstractDynamicResourceHandler; +import be.objectify.deadbolt.java.DeadboltHandler; + +public class OnlyMeAndAdminDynamicResourceHandler extends AbstractDynamicResourceHandler { + + @Override + public Promise checkPermission(String permissionValue, + DeadboltHandler deadboltHandler, Context ctx) { + // + return super.checkPermission(permissionValue, deadboltHandler, ctx); + } + + /** + * Allow access to a user resource only if himself is requesting + */ + @Override + public Promise isAllowed(String name, String meta, + DeadboltHandler deadboltHandler, Context context) { + + return deadboltHandler.getSubject(context) + .map( subjectOption -> { + final boolean[] allowed = {false}; + if (new DeadboltAnalyzer().hasRole(subjectOption, "ADMIN")) { + allowed[0] = true; + } else { + subjectOption.ifPresent(subject -> { + String path = context.request().path(); + Long requestedResourceId = MyDynamicResourceHandler.getIdFromPath(path, meta); + UUID requestedResourceUUID = null; + if(requestedResourceId<0) { + requestedResourceUUID = MyDynamicResourceHandler.getUUIDFromPath(path, meta); + } + User u = User.findByUserName(subject.getIdentifier()); + Long requestorId = u.getUserId(); + UUID requestorUUID = u.getUuid(); + + Logger.debug("Checking relationship of '"+meta+"' and user"); + + // TODO: check permission of user on resource + + Logger.debug("--> userId = "+requestorId); + Logger.debug("--> userUUID = "+requestorUUID); + Logger.debug("--> requestedResourceId = "+requestedResourceId); + Logger.debug("--> requestedResourceUUID = "+requestedResourceUUID); + Logger.debug("--> type of resource= "+meta); + Logger.debug("Checking for path "+meta+requestedResourceId); + + Long requestedId = MyDynamicResourceHandler.getIdFromPath(path, SecurityModelConstants.USER_RESOURCE_PATH); + UUID requestedUUID = null; + if(requestedId<0) { + requestedUUID = MyDynamicResourceHandler.getUUIDFromPath(path, SecurityModelConstants.USER_RESOURCE_PATH); + allowed[0] = requestorUUID == requestedUUID; + } else { + allowed[0] = requestorId == requestedId; + } + }); + } + return allowed[0]; + }); + } +} diff --git a/app/security/OnlyMeDynamicResourceHandler.java b/app/security/OnlyMeDynamicResourceHandler.java index 7bc8ea47..4ba07360 100644 --- a/app/security/OnlyMeDynamicResourceHandler.java +++ b/app/security/OnlyMeDynamicResourceHandler.java @@ -1,12 +1,11 @@ package security; +import java.util.UUID; + import models.User; import play.Logger; -import play.libs.F; import play.libs.F.Promise; import play.mvc.Http.Context; -import be.objectify.deadbolt.core.DeadboltAnalyzer; -import be.objectify.deadbolt.core.models.Subject; import be.objectify.deadbolt.java.AbstractDynamicResourceHandler; import be.objectify.deadbolt.java.DeadboltHandler; @@ -28,25 +27,36 @@ public Promise isAllowed(String name, String meta, return deadboltHandler.getSubject(context) .map( subjectOption -> { - final boolean[] allowed = {false}; - if (new DeadboltAnalyzer().hasRole(subjectOption, "ADMIN")) { - allowed[0] = true; - } else { - subjectOption.ifPresent(subject -> { - String path = context.request().path(); - Long requestedResourceId = MyDynamicResourceHandler.getIdFromPath(path, meta); - User u = User.findByUserName(subject.getIdentifier()); - Long requestorId = u.getUserId(); - Logger.debug("Checking relationship of..."); - Logger.debug("--> userId = "+requestorId); - Logger.debug("--> requestedResourceId = "+requestedResourceId); - Logger.debug("--> type of resource= "+meta); - Logger.debug("Checking for path "+meta+requestedResourceId); - Long requestedId = MyDynamicResourceHandler.getIdFromPath(path, SecurityModelConstants.USER_RESOURCE_PATH); + final boolean[] allowed = {false}; + subjectOption.ifPresent(subject -> { + String path = context.request().path(); + Long requestedResourceId = MyDynamicResourceHandler.getIdFromPath(path, meta); + UUID requestedResourceUUID = null; + if(requestedResourceId<0) { + requestedResourceUUID = MyDynamicResourceHandler.getUUIDFromPath(path, meta); + } + User u = User.findByUserName(subject.getIdentifier()); + Long requestorId = u.getUserId(); + UUID requestorUUID = u.getUuid(); + + Logger.debug("Checking relationship of..."); + Logger.debug("--> userId = "+requestorId); + Logger.debug("--> userUUID = "+requestorUUID); + Logger.debug("--> requestedResourceId = "+requestedResourceId); + Logger.debug("--> requestedResourceUUID = "+requestedResourceUUID); + Logger.debug("--> type of resource= "+meta); + Logger.debug("Checking for path "+meta+requestedResourceId); + + Long requestedId = MyDynamicResourceHandler.getIdFromPath(path, SecurityModelConstants.USER_RESOURCE_PATH); + UUID requestedUUID = null; + if(requestedId<0) { + requestedUUID = MyDynamicResourceHandler.getUUIDFromPath(path, SecurityModelConstants.USER_RESOURCE_PATH); + allowed[0] = requestorUUID.equals(requestedUUID); + } else { allowed[0] = requestorId == requestedId; - }); - } - return allowed[0]; + } + }); + return allowed[0]; }); } } diff --git a/app/service/PlayAuthenticateLocal.java b/app/service/PlayAuthenticateLocal.java index 20a6b62a..5b9f6355 100644 --- a/app/service/PlayAuthenticateLocal.java +++ b/app/service/PlayAuthenticateLocal.java @@ -14,6 +14,7 @@ import play.mvc.Http.Session; import play.mvc.Result; import providers.MyUsernamePasswordAuthProvider; +import scala.reflect.internal.Trees.Super; import com.feth.play.module.pa.PlayAuthenticate; import com.feth.play.module.pa.exceptions.AuthException; @@ -32,7 +33,7 @@ public class PlayAuthenticateLocal extends PlayAuthenticate { public static final String PROVIDER_KEY = "pa.p.id"; public static final String EXPIRES_KEY = "pa.u.exp"; public static final String SESSION_KEY_STRING = "SESSION_KEY"; -// public static final String ORIGINAL_URL = "pa.url.orig"; + private static final String SETTING_KEY_AFTER_LOGOUT_FALLBACK = "afterLogoutFallback"; // public static final String SESSION_ID_KEY = "pa.s.id"; /** @@ -202,6 +203,7 @@ public static Result handleAuthentication(final String provider, // if isLoggedIn is false here, then the local user has // been deleted/deactivated // so kill the session + // TODO: REDIRECT TO THE ORIGIN URL logout(session); oldUser = null; } @@ -312,7 +314,19 @@ public static Result handleAuthentication(final String provider, } } } - + +// public static Result logout(final Session session) { +// session.remove(USER_KEY); +// session.remove(PROVIDER_KEY); +// session.remove(EXPIRES_KEY); +// +// +// Call c = getResolver().afterLogout(); +// String fallback = SETTING_KEY_AFTER_LOGOUT_FALLBACK; +// +// return Controller.redirect(getUrl(c,fallback)); +// } + private static AuthUser signupUser(final AuthUser u) throws AuthException { final AuthUser loginUser; final Object id = getUserService().save(u); @@ -324,4 +338,28 @@ private static AuthUser signupUser(final AuthUser u) throws AuthException { loginUser = u; return loginUser; } + +// private static String getUrl(final Call c, final String settingFallback) { +// // this can be null if the user did not correctly define the +// // resolver +// if (c != null) { +// return c.url(); +// } else { +// // go to root instead, but log this +// Logger.warn("Resolver did not contain information about where to go - redirecting to /"); +// final String afterAuthFallback = getConfiguration().getString( +// settingFallback); +// if (afterAuthFallback != null && !afterAuthFallback.equals("")) { +// return afterAuthFallback; +// } +// // Not even the config setting was there or valid...meh +// Logger.error("Config setting '" + settingFallback +// + "' was not present!"); +// return "/"; +// } +// } + + + + } diff --git a/build.sbt b/build.sbt index b9de91d4..c2f3190b 100644 --- a/build.sbt +++ b/build.sbt @@ -33,7 +33,8 @@ libraryDependencies ++= Seq( "be.objectify" %% "deadbolt-java" % "2.4.0" withSources() withJavadoc(), "com.wordnik" %% "swagger-core" % "1.3.12" withSources() withJavadoc(), "javax.ws.rs" % "javax.ws.rs-api" % "2.0.1" withSources() withJavadoc(), - "pl.matisoft" %% "swagger-play24" % "1.4" withSources() withJavadoc() + "pl.matisoft" %% "swagger-play24" % "1.4" withSources() withJavadoc(), + "net.sf.dozer" % "dozer" % "5.5.1" withSources() withJavadoc() // The official old swagger play installation. Uncomment when it works with our current play version (2.4.x) //"com.wordnik" %% "swagger-play2" % "1.3.11" withSources() withJavadoc() // Another Implementation of swagger-play2 @@ -56,4 +57,9 @@ resolvers += Resolver.bintrayRepo("markusjura", "maven") // Enable Java Ebean lazy val myProject = (project in file(".")) - .enablePlugins(PlayJava, PlayEbean) \ No newline at end of file + .enablePlugins(PlayJava, PlayEbean) + +// Eclipse configurations +EclipseKeys.preTasks := Seq(compile in Compile) +EclipseKeys.projectFlavor := EclipseProjectFlavor.Java // Java project. Don't expect Scala IDE +EclipseKeys.createSrc := EclipseCreateSrc.ValueSet(EclipseCreateSrc.ManagedClasses, EclipseCreateSrc.ManagedResources) // Use .class files instead of generated .scala files for views and routes diff --git a/conf/evolutions/default/1.sql b/conf/evolutions/default/1.sql index 67b907a6..6ab387c8 100644 --- a/conf/evolutions/default/1.sql +++ b/conf/evolutions/default/1.sql @@ -173,6 +173,7 @@ create table contribution ( uuid varchar(40), title varchar(255), text varchar(255), + author_user_id bigint, assembly_assembly_id bigint, location_location_id bigint, stats_contribution_statistics_id bigint, @@ -396,7 +397,6 @@ create table required_phase_configuration ( create table resource ( resource_type varchar(31) not null, resource_id bigserial not null, - contribution_contribution_id bigint not null, creation timestamp, last_update timestamp, lang varchar(255), @@ -684,6 +684,12 @@ create table contribution_theme ( constraint pk_contribution_theme primary key (contribution_contribution_id, theme_theme_id)) ; +create table contribution_resource ( + contribution_contribution_id bigint not null, + resource_resource_id bigint not null, + constraint pk_contribution_resource primary key (contribution_contribution_id, resource_resource_id)) +; + create table contribution_hashtag ( contribution_contribution_id bigint not null, hashtag_hashtag_id bigint not null, @@ -801,48 +807,48 @@ alter table campaign_phase_milestone add constraint fk_campaign_phase_milestone_ create index ix_campaign_phase_milestone_c_13 on campaign_phase_milestone (campaign_phase_phase_id); alter table config add constraint fk_config_definition_14 foreign key (definition_uuid) references config_definition (uuid); create index ix_config_definition_14 on config (definition_uuid); -alter table contribution add constraint fk_contribution_assembly_15 foreign key (assembly_assembly_id) references assembly (assembly_id); -create index ix_contribution_assembly_15 on contribution (assembly_assembly_id); -alter table contribution add constraint fk_contribution_location_16 foreign key (location_location_id) references location (location_id); -create index ix_contribution_location_16 on contribution (location_location_id); -alter table contribution add constraint fk_contribution_stats_17 foreign key (stats_contribution_statistics_id) references contribution_statistics (contribution_statistics_id); -create index ix_contribution_stats_17 on contribution (stats_contribution_statistics_id); -alter table contribution add constraint fk_contribution_proposalPad_18 foreign key (proposal_pad_resource_id) references resource (resource_id); -create index ix_contribution_proposalPad_18 on contribution (proposal_pad_resource_id); -alter table contribution add constraint fk_contribution_proposalTempl_19 foreign key (proposal_template_resource_id) references resource (resource_id); -create index ix_contribution_proposalTempl_19 on contribution (proposal_template_resource_id); -alter table geometry add constraint fk_geometry_geo_20 foreign key (geo_location_id) references geo (location_id); -create index ix_geometry_geo_20 on geometry (geo_location_id); -alter table Linked_Account add constraint fk_Linked_Account_user_21 foreign key (user_id) references appcivist_user (user_id); -create index ix_Linked_Account_user_21 on Linked_Account (user_id); -alter table membership add constraint fk_membership_creator_22 foreign key (creator_user_id) references appcivist_user (user_id); -create index ix_membership_creator_22 on membership (creator_user_id); -alter table membership add constraint fk_membership_user_23 foreign key (user_user_id) references appcivist_user (user_id); -create index ix_membership_user_23 on membership (user_user_id); -alter table membership add constraint fk_membership_assembly_24 foreign key (assembly_assembly_id) references assembly (assembly_id); -create index ix_membership_assembly_24 on membership (assembly_assembly_id); -alter table membership add constraint fk_membership_workingGroup_25 foreign key (working_group_group_id) references working_group (group_id); -create index ix_membership_workingGroup_25 on membership (working_group_group_id); -alter table message add constraint fk_message_targetUser_26 foreign key (target_user_user_id) references appcivist_user (user_id); -create index ix_message_targetUser_26 on message (target_user_user_id); -alter table message add constraint fk_message_targetWorkingGroup_27 foreign key (target_working_group_group_id) references working_group (group_id); -create index ix_message_targetWorkingGroup_27 on message (target_working_group_group_id); -alter table message add constraint fk_message_targetAssembly_28 foreign key (target_assembly_assembly_id) references assembly (assembly_id); -create index ix_message_targetAssembly_28 on message (target_assembly_assembly_id); -alter table properties add constraint fk_properties_geo_29 foreign key (geo_location_id) references geo (location_id); -create index ix_properties_geo_29 on properties (geo_location_id); -alter table required_campaign_configuration add constraint fk_required_campaign_configur_30 foreign key (campaign_type_campaign_type_id) references campaign_type (campaign_type_id); -create index ix_required_campaign_configur_30 on required_campaign_configuration (campaign_type_campaign_type_id); -alter table required_campaign_configuration add constraint fk_required_campaign_configur_31 foreign key (config_definition_uuid) references config_definition (uuid); -create index ix_required_campaign_configur_31 on required_campaign_configuration (config_definition_uuid); -alter table required_campaign_phase_milestone add constraint fk_required_campaign_phase_mi_32 foreign key (phase_definition_phase_definition_id) references phase_definition (phase_definition_id); -create index ix_required_campaign_phase_mi_32 on required_campaign_phase_milestone (phase_definition_phase_definition_id); -alter table required_phase_configuration add constraint fk_required_phase_configurati_33 foreign key (phase_definition_phase_definition_id) references phase_definition (phase_definition_id); -create index ix_required_phase_configurati_33 on required_phase_configuration (phase_definition_phase_definition_id); -alter table required_phase_configuration add constraint fk_required_phase_configurati_34 foreign key (config_definition_uuid) references config_definition (uuid); -create index ix_required_phase_configurati_34 on required_phase_configuration (config_definition_uuid); -alter table resource add constraint fk_resource_contribution_35 foreign key (contribution_contribution_id) references contribution (contribution_id); -create index ix_resource_contribution_35 on resource (contribution_contribution_id); +alter table contribution add constraint fk_contribution_author_15 foreign key (author_user_id) references appcivist_user (user_id); +create index ix_contribution_author_15 on contribution (author_user_id); +alter table contribution add constraint fk_contribution_assembly_16 foreign key (assembly_assembly_id) references assembly (assembly_id); +create index ix_contribution_assembly_16 on contribution (assembly_assembly_id); +alter table contribution add constraint fk_contribution_location_17 foreign key (location_location_id) references location (location_id); +create index ix_contribution_location_17 on contribution (location_location_id); +alter table contribution add constraint fk_contribution_stats_18 foreign key (stats_contribution_statistics_id) references contribution_statistics (contribution_statistics_id); +create index ix_contribution_stats_18 on contribution (stats_contribution_statistics_id); +alter table contribution add constraint fk_contribution_proposalPad_19 foreign key (proposal_pad_resource_id) references resource (resource_id); +create index ix_contribution_proposalPad_19 on contribution (proposal_pad_resource_id); +alter table contribution add constraint fk_contribution_proposalTempl_20 foreign key (proposal_template_resource_id) references resource (resource_id); +create index ix_contribution_proposalTempl_20 on contribution (proposal_template_resource_id); +alter table geometry add constraint fk_geometry_geo_21 foreign key (geo_location_id) references geo (location_id); +create index ix_geometry_geo_21 on geometry (geo_location_id); +alter table Linked_Account add constraint fk_Linked_Account_user_22 foreign key (user_id) references appcivist_user (user_id); +create index ix_Linked_Account_user_22 on Linked_Account (user_id); +alter table membership add constraint fk_membership_creator_23 foreign key (creator_user_id) references appcivist_user (user_id); +create index ix_membership_creator_23 on membership (creator_user_id); +alter table membership add constraint fk_membership_user_24 foreign key (user_user_id) references appcivist_user (user_id); +create index ix_membership_user_24 on membership (user_user_id); +alter table membership add constraint fk_membership_assembly_25 foreign key (assembly_assembly_id) references assembly (assembly_id); +create index ix_membership_assembly_25 on membership (assembly_assembly_id); +alter table membership add constraint fk_membership_workingGroup_26 foreign key (working_group_group_id) references working_group (group_id); +create index ix_membership_workingGroup_26 on membership (working_group_group_id); +alter table message add constraint fk_message_targetUser_27 foreign key (target_user_user_id) references appcivist_user (user_id); +create index ix_message_targetUser_27 on message (target_user_user_id); +alter table message add constraint fk_message_targetWorkingGroup_28 foreign key (target_working_group_group_id) references working_group (group_id); +create index ix_message_targetWorkingGroup_28 on message (target_working_group_group_id); +alter table message add constraint fk_message_targetAssembly_29 foreign key (target_assembly_assembly_id) references assembly (assembly_id); +create index ix_message_targetAssembly_29 on message (target_assembly_assembly_id); +alter table properties add constraint fk_properties_geo_30 foreign key (geo_location_id) references geo (location_id); +create index ix_properties_geo_30 on properties (geo_location_id); +alter table required_campaign_configuration add constraint fk_required_campaign_configur_31 foreign key (campaign_type_campaign_type_id) references campaign_type (campaign_type_id); +create index ix_required_campaign_configur_31 on required_campaign_configuration (campaign_type_campaign_type_id); +alter table required_campaign_configuration add constraint fk_required_campaign_configur_32 foreign key (config_definition_uuid) references config_definition (uuid); +create index ix_required_campaign_configur_32 on required_campaign_configuration (config_definition_uuid); +alter table required_campaign_phase_milestone add constraint fk_required_campaign_phase_mi_33 foreign key (phase_definition_phase_definition_id) references phase_definition (phase_definition_id); +create index ix_required_campaign_phase_mi_33 on required_campaign_phase_milestone (phase_definition_phase_definition_id); +alter table required_phase_configuration add constraint fk_required_phase_configurati_34 foreign key (phase_definition_phase_definition_id) references phase_definition (phase_definition_id); +create index ix_required_phase_configurati_34 on required_phase_configuration (phase_definition_phase_definition_id); +alter table required_phase_configuration add constraint fk_required_phase_configurati_35 foreign key (config_definition_uuid) references config_definition (uuid); +create index ix_required_phase_configurati_35 on required_phase_configuration (config_definition_uuid); alter table service add constraint fk_service_assembly_36 foreign key (assembly_assembly_id) references service_assembly (assembly_id); create index ix_service_assembly_36 on service (assembly_assembly_id); alter table service add constraint fk_service_serviceDefinition_37 foreign key (service_definition_service_definition_id) references service_definition (service_definition_id); @@ -910,6 +916,10 @@ alter table contribution_theme add constraint fk_contribution_theme_contrib_01 f alter table contribution_theme add constraint fk_contribution_theme_theme_02 foreign key (theme_theme_id) references theme (theme_id); +alter table contribution_resource add constraint fk_contribution_resource_cont_01 foreign key (contribution_contribution_id) references contribution (contribution_id); + +alter table contribution_resource add constraint fk_contribution_resource_reso_02 foreign key (resource_resource_id) references resource (resource_id); + alter table contribution_hashtag add constraint fk_contribution_hashtag_contr_01 foreign key (contribution_contribution_id) references contribution (contribution_id); alter table contribution_hashtag add constraint fk_contribution_hashtag_hasht_02 foreign key (hashtag_hashtag_id) references hashtag (hashtag_id); @@ -1000,6 +1010,8 @@ drop table if exists contribution_appcivist_user cascade; drop table if exists contribution_theme cascade; +drop table if exists contribution_resource cascade; + drop table if exists contribution_hashtag cascade; drop table if exists contribution_connection cascade; diff --git a/conf/heroku.logback.xml b/conf/heroku.logback.xml new file mode 100644 index 00000000..d353ab6c --- /dev/null +++ b/conf/heroku.logback.xml @@ -0,0 +1,44 @@ + + + + + + + + ${application.home}/logs/application.log + + %date [%level] from %logger in %thread - %message%n%xException + + + + + + %coloredLevel %logger{15} - %message%n%xException{10} + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/conf/messages.en b/conf/messages.en index 0d2fe672..57f09a55 100644 --- a/conf/messages.en +++ b/conf/messages.en @@ -149,29 +149,21 @@ playauthenticate.restricted.secrets=Secrets, everywhere! ### --- play-authenticate END - ### --- AppCivist Messages groups.creation.success = The working group {0} has been created with success by {1} groups.creation.error = The working group was not created. There was a problem with the request: {0} - assemblies.creation.success = The assembly {0} has been created with success by {1} assemblies.creation.error = The assembly was not created. There was a problem with the request: {0} - roles.creation.success = The role {0} has been created with success by {1} roles.creation.error = The role was not created. There was a problem with the request: {0} - configs.creation.success = The config {0} has been created with success by {1} configs.creation.error = The config was not created. There was a problem with the request: {0} - campaign.creation.success = The campaign {0} has been created with success by {1} campaign.creation.error = The campaign was not created. There was a problem with the request: {0} - campaign.phase.creation.success = The campaign phase {0} has been created with success by {1} campaign.phase.creation.error = The campaign phase was not created. There was a problem with the request: {0} - contribution.creation.success = The contribution {0} has been created with success by {1} contribution.creation.error = The contribution was not created. There was a problem with the request: {0} - membership.invitation.creation.success = The invitation to membership {0} has been created with success by {1} membership.invitation.creation.error = The invitation to membership was not created. There was a problem with the request: {0} membership.invitation.creation.unauthorized = The membership could not be created because user is not authorized @@ -179,3 +171,22 @@ membership.invitation.email.message = Membership invitation membership.invitation.email.subject=Invitation to join an AppCivist {0} membership.request.email.subject=Request to join an AppCivist {0} membership.confirmation.email.subject=Welcome to this AppCivist {0} + +notification.title.assembly.update=Assembly Update +notification.title.group.update=Working Group Update +notification.title.contribution.update=Contribution Update +notification.title.campaign.update=Campaign Update +notification.title.campaign.update.milestone=Upcoming Milestone +notification.title.message.new=New Message +notification.title.message.reply=New Reply +notification.title.message.new.group=New Working Group Message +notification.title.message.reply.group=New Working Group Reply +notification.title.message.new.assembly=New Assembly Message +notification.title.message.reply.assembly=New Assembly Reply + +notification.description.assembly.forum.contribution={0} posted a new contribution in the forum of assembly '{1}' +notification.description.group.forum.contribution={0} posted a new contribution in the forum of working group '{1}' +notification.description.contribution.comment={0} commented your contribution '{1}' +notification.description.campaign.contribution={0} posted a new contribution in campaign '{1}' +notification.description.campaign.upcoming.milestone={0} due date approaching ({1}) for campaign {3} + diff --git a/conf/messages.es b/conf/messages.es index 8fc4d02b..3d390c21 100644 --- a/conf/messages.es +++ b/conf/messages.es @@ -1,11 +1,5 @@ # Override default Play's validation messages -# --- reminiscens -reminiscens.birth.headline = Nacimiento -reminiscens.birth.text = La historia de mi nacimiento -reminiscens.context.person.title=Colección contextual para el recuerdo para -reminiscens.context.person.subtitle=Colección de contenido histórico para la estimulación de los recuerdos para - # --- Constraints constraint.required=Required constraint.min=Minimum value: {0} @@ -150,7 +144,6 @@ playauthenticate.index.intro_2=This is a template for a simple application with playauthenticate.index.intro_3=Check the main navigation above for simple page examples including supported authentication features. playauthenticate.index.heading=Heading playauthenticate.index.details=View details - # play-authenticate - sample: Restricted page playauthenticate.restricted.secrets=Secrets, everywhere! @@ -159,23 +152,33 @@ playauthenticate.restricted.secrets=Secrets, everywhere! ### --- AppCivist Messages groups.creation.success = El grupo de trabajo {0} ha sido creado con exito por {1} groups.creation.error = El grupo de trabajo no fue creado. Hubo un problema con la peticion: {0} - assemblies.creation.success = La asamblea {0} ha sido creada con exito por {1} assemblies.creation.error = La asamblea no fue creada. Hubo un problema con la peticion: {0} - roles.creation.success = El rol {0} ha sido creado con exito por {1} roles.creation.error = El rol no fue creado. Hubo un problema con la peticion: {0} - config.creation.success = La config {0} ha sido creada con exito por {1} config.creation.error = La config no fue creada. Hubo un problema con la peticion: {0} - campaign.creation.success = La campaña {0} ha sido creada con exito por {1} campaign.creation.error = La campaña no fue creada. Hubo un problema con la peticion: {0} - campaign.phase.creation.success = La fase de campaña {0} ha sido creada con exito por {1} campaign.phase.creation.error = La fase de campaña no fue creada. Hubo un problema con la peticion: {0} - membership.invitation.creation.success = La invitacion a la membresia {0} ha sido creada con exito por {1} membership.invitation.creation.error = La invitacion a la membresia no fue creada. Hubo un problema con la peticion: {0} - membership.invitation.email.message = Invitacion para membresia +membership.invitation.creation.unauthorized = La membresía no pudo ser creada porque el usuario no está autorizado +membership.invitation.email.subject=Invitación para unirse a AppCivist {0} +membership.request.email.subject=Petición para unirse a AppCivist {0} +membership.confirmation.email.subject=Bienvenido a AppCivist {0} + +notification.title.assembly.update=Nuevo en Asamblea +notification.title.group.update=Nuevo en Grupo de Trabajo +notification.title.contribution.update=Nuevo en Contribución +notification.title.campaign.update=Nuevo en Campaña +notification.title.campaign.update.milestone=Próximos Plazos +notification.title.message.new=Nuevo Mensaje +notification.title.message.reply=Nueva Respuesta +notification.title.message.new.group=Nuevo Mensaje para Grupo de Trabajo +notification.title.message.reply.group=Nueva Respuesta para Grupo de Trabajo +notification.title.message.new.assembly=Nuevo Mensaje para Asamblea +notification.title.message.reply.assembly=Nueva Respuesta para Asamblea + diff --git a/conf/messages.fr b/conf/messages.fr new file mode 100644 index 00000000..a4feeff4 --- /dev/null +++ b/conf/messages.fr @@ -0,0 +1,171 @@ +# Override default Play's validation messages + +# --- Constraints +constraint.required=Required +constraint.min=Minimum value: {0} +constraint.max=Maximum value: {0} +constraint.minLength=Minimum length: {0} +constraint.maxLength=Maximum length: {0} +constraint.email=Email + +# --- Formats +format.date=Date (''{0}'') +format.numeric=Numeric +format.real=Real + +# --- Errors +error.invalid=Invalid value +error.required=This field is required +error.number=Numeric value expected +error.real=Real number value expected +error.min=Must be greater or equal to {0} +error.max=Must be less or equal to {0} +error.minLength=Minimum length is {0} +error.maxLength=Maximum length is {0} +error.email=Valid email required +error.pattern=Must satisfy {0} + +### --- play-authenticate START + +# play-authenticate: Initial translations + +playauthenticate.accounts.link.success=Account linked successfully +playauthenticate.accounts.merge.success=Accounts merged successfully + +playauthenticate.verify_email.error.already_validated=Your e-mail has already been validated. +playauthenticate.verify_email.error.set_email_first=You need to set an e-mail address first. +playauthenticate.verify_email.message.instructions_sent=Instructions on how to verify your e-mail address have been sent to {0}. +playauthenticate.verify_email.success=E-mail address ({0}) successfully verified. + +playauthenticate.reset_password.message.instructions_sent=Instructions on how to reset your password have been sent to {0}. +playauthenticate.reset_password.message.email_not_verified=Your account has not been verified, yet. An e-mail including instructions on how to verify it has been sent out. Retry resetting your password afterwards. +playauthenticate.reset_password.message.no_password_account=Your user has not yet been set up for password usage. +playauthenticate.reset_password.message.success.auto_login=Your password has been reset. +playauthenticate.reset_password.message.success.manual_login=Your password has been reset. Please now log in using your new password. + +playauthenticate.change_password.error.passwords_not_same=Passwords do not match. +playauthenticate.change_password.success=Password has been changed successfully. + +playauthenticate.password.signup.error.passwords_not_same=Passwords do not match. +playauthenticate.password.login.unknown_user_or_pw=Unknown user or password. + +playauthenticate.password.verify_signup.subject=Reminiscens: Completa la tua registrazione +playauthenticate.password.verify_email.subject=Reminiscens: Conferma il tuo indirizzo email +playauthenticate.password.reset_email.subject=Reminiscens: Come ricuperare la tua password + +# play-authenticate: Additional translations + +playauthenticate.login.email.placeholder=Your e-mail address +playauthenticate.login.password.placeholder=Choose a password +playauthenticate.login.password.repeat=Repeat chosen password +playauthenticate.login.title=Login +playauthenticate.login.password.placeholder=Password +playauthenticate.login.now=Login now +playauthenticate.login.forgot.password=Forgot your password? +playauthenticate.login.oauth=or log in using one of the following providers: + +playauthenticate.signup.title=Signup +playauthenticate.signup.name=Your name +playauthenticate.signup.now=Sign up now +playauthenticate.signup.oauth=or sign up using one of the following providers: + +playauthenticate.verify.account.title=E-mail verification required +playauthenticate.verify.account.before=Before setting a password, you need to +playauthenticate.verify.account.first=first verify your e-mail address + +playauthenticate.change.password.title=Change your password here +playauthenticate.change.password.cta=Change my password + +playauthenticate.merge.accounts.title=Merge accounts +playauthenticate.merge.accounts.question=Do you want to merge your current account ({0}) with this account: {1}? +playauthenticate.merge.accounts.true=Yes, merge these two accounts +playauthenticate.merge.accounts.false=No, exit my current session and log in as a new user +playauthenticate.merge.accounts.ok=OK + +playauthenticate.link.account.title=Link account +playauthenticate.link.account.question=Link ({0}) with your user? +playauthenticate.link.account.true=Yes, link this account +playauthenticate.link.account.false=No, log out and create a new user with this account +playauthenticate.link.account.ok=OK + +# play-authenticate: Signup folder translations + +playauthenticate.verify.email.title=Verify your e-mail +playauthenticate.verify.email.requirement=Before you can use PlayAuthenticate, you first need to verify your e-mail address. +playauthenticate.verify.email.cta=An e-mail has been sent to the registered address. Please follow the embedded link to activate your account. + +playauthenticate.password.reset.title=Reset password +playauthenticate.password.reset.cta=Reset my password + +playauthenticate.password.forgot.title=Forgot password +playauthenticate.password.forgot.cta=Send reset instructions + +playauthenticate.oauth.access.denied.title=OAuth access denied +playauthenticate.oauth.access.denied.explanation=If you want to use PlayAuthenticate with OAuth, you must accept the connection. +playauthenticate.oauth.access.denied.alternative=If you rather not like to do this, you can also +playauthenticate.oauth.access.denied.alternative.cta=sign up with a username and password instead + +playauthenticate.token.error.title=Token error +playauthenticate.token.error.message=The given token has either expired or does not exist. + +playauthenticate.user.exists.title=User exists +playauthenticate.user.exists.message=This user already exists. + +# play-authenticate: Navigation +playauthenticate.navigation.profile=Profile +playauthenticate.navigation.link_more=Link more providers +playauthenticate.navigation.logout=Sign out +playauthenticate.navigation.login=Log in +playauthenticate.navigation.home=Home +playauthenticate.navigation.restricted=Restricted page +playauthenticate.navigation.signup=Sign up + +# play-authenticate: Handler +playauthenticate.handler.loginfirst=You need to log in first, to view ''{0}'' + +# play-authenticate: Profile +playauthenticate.profile.title=User profile +playauthenticate.profile.mail=Your name is {0} and your email address is {1}! +playauthenticate.profile.unverified=unverified - click to verify +playauthenticate.profile.verified=verified +playauthenticate.profile.providers_many=There are {0} providers linked with your account: +playauthenticate.profile.providers_one = There is one provider linked with your account: +playauthenticate.profile.logged=You are currently logged in with: +playauthenticate.profile.session=Your user ID is {0} and your session will expire on {1} +playauthenticate.profile.session_endless=Your user ID is {0} and your session will not expire, as it is endless +playauthenticate.profile.password_change=Change/set a password for your account + +# play-authenticate - sample: Index page +playauthenticate.index.title=Welcome to Play Authenticate +playauthenticate.index.intro=Play Authenticate sample app +playauthenticate.index.intro_2=This is a template for a simple application with authentication. +playauthenticate.index.intro_3=Check the main navigation above for simple page examples including supported authentication features. +playauthenticate.index.heading=Heading +playauthenticate.index.details=View details + +# play-authenticate - sample: Restricted page +playauthenticate.restricted.secrets=Secrets, everywhere! + + +### --- play-authenticate END + +### --- AppCivist Messages +groups.creation.success = El grupo de trabajo {0} ha sido creado con exito por {1} +groups.creation.error = El grupo de trabajo no fue creado. Hubo un problema con la peticion: {0} +assemblies.creation.success = La asamblea {0} ha sido creada con exito por {1} +assemblies.creation.error = La asamblea no fue creada. Hubo un problema con la peticion: {0} +roles.creation.success = El rol {0} ha sido creado con exito por {1} +roles.creation.error = El rol no fue creado. Hubo un problema con la peticion: {0} +config.creation.success = La config {0} ha sido creada con exito por {1} +config.creation.error = La config no fue creada. Hubo un problema con la peticion: {0} +campaign.creation.success = La campaña {0} ha sido creada con exito por {1} +campaign.creation.error = La campaña no fue creada. Hubo un problema con la peticion: {0} +campaign.phase.creation.success = La fase de campaña {0} ha sido creada con exito por {1} +campaign.phase.creation.error = La fase de campaña no fue creada. Hubo un problema con la peticion: {0} +membership.invitation.creation.success = La invitacion a la membresia {0} ha sido creada con exito por {1} +membership.invitation.creation.error = La invitacion a la membresia no fue creada. Hubo un problema con la peticion: {0} +membership.invitation.email.message = Invitacion para membresia +membership.invitation.creation.unauthorized = La membresía no pudo ser creada porque el usuario no está autorizado +membership.invitation.email.subject=Invitación para unirse a AppCivist {0} +membership.request.email.subject=Petición para unirse a AppCivist {0} +membership.confirmation.email.subject=Bienvenido a AppCivist {0} diff --git a/conf/messages.it b/conf/messages.it index 40c2a7d2..a4feeff4 100644 --- a/conf/messages.it +++ b/conf/messages.it @@ -1,11 +1,5 @@ # Override default Play's validation messages -# --- reminiscens -reminiscens.birth.headline = Nascita -reminiscens.birth.text = La storia della mia nascita -reminiscens.context.person.title=Collezione contestuale per i riccordi di -reminiscens.context.person.subtitle=Collezione di contenuto storico per la stimulazione dei riccordi di - # --- Constraints constraint.required=Required constraint.min=Minimum value: {0} @@ -152,4 +146,26 @@ playauthenticate.index.details=View details # play-authenticate - sample: Restricted page playauthenticate.restricted.secrets=Secrets, everywhere! + ### --- play-authenticate END + +### --- AppCivist Messages +groups.creation.success = El grupo de trabajo {0} ha sido creado con exito por {1} +groups.creation.error = El grupo de trabajo no fue creado. Hubo un problema con la peticion: {0} +assemblies.creation.success = La asamblea {0} ha sido creada con exito por {1} +assemblies.creation.error = La asamblea no fue creada. Hubo un problema con la peticion: {0} +roles.creation.success = El rol {0} ha sido creado con exito por {1} +roles.creation.error = El rol no fue creado. Hubo un problema con la peticion: {0} +config.creation.success = La config {0} ha sido creada con exito por {1} +config.creation.error = La config no fue creada. Hubo un problema con la peticion: {0} +campaign.creation.success = La campaña {0} ha sido creada con exito por {1} +campaign.creation.error = La campaña no fue creada. Hubo un problema con la peticion: {0} +campaign.phase.creation.success = La fase de campaña {0} ha sido creada con exito por {1} +campaign.phase.creation.error = La fase de campaña no fue creada. Hubo un problema con la peticion: {0} +membership.invitation.creation.success = La invitacion a la membresia {0} ha sido creada con exito por {1} +membership.invitation.creation.error = La invitacion a la membresia no fue creada. Hubo un problema con la peticion: {0} +membership.invitation.email.message = Invitacion para membresia +membership.invitation.creation.unauthorized = La membresía no pudo ser creada porque el usuario no está autorizado +membership.invitation.email.subject=Invitación para unirse a AppCivist {0} +membership.request.email.subject=Petición para unirse a AppCivist {0} +membership.confirmation.email.subject=Bienvenido a AppCivist {0} diff --git a/conf/play-authenticate/mine.conf b/conf/play-authenticate/mine.conf index 5280e460..ebcbd1c0 100644 --- a/conf/play-authenticate/mine.conf +++ b/conf/play-authenticate/mine.conf @@ -72,6 +72,7 @@ play-authenticate { # Whether to directly log in after the password reset (true) # or send the user to the login page (false) loginAfterPasswordReset=true + afterLogoutFallback="/api/user/logout/redirect" } } diff --git a/conf/routes b/conf/routes index fe871be7..f1408bd2 100644 --- a/conf/routes +++ b/conf/routes @@ -22,8 +22,9 @@ GET /public/*file controllers.Assets.v GET /api/doc controllers.Application.swaggerDocs() GET /api/doc.json @pl.matisoft.swagger.ApiHelpController.getResources() GET /api/doc.json/user @pl.matisoft.swagger.ApiHelpController.getResource(path="/user") +GET /api/doc.json/notification @pl.matisoft.swagger.ApiHelpController.getResource(path="/notification") GET /api/doc.json/assembly @pl.matisoft.swagger.ApiHelpController.getResource(path="/assembly") -GET /api/doc.json/group @pl.matisoft.swagger.ApiHelpController.getResource(path="/group") +#GET /api/doc.json/group @pl.matisoft.swagger.ApiHelpControllreer.getResource(path="/group") GET /api/doc.json/membership @pl.matisoft.swagger.ApiHelpController.getResource(path="/membership") GET /api/doc.json/campaign @pl.matisoft.swagger.ApiHelpController.getResource(path="/campaign") @@ -31,17 +32,23 @@ GET /api/doc.json/campaign @pl.matisoft.swagger # User and Authentication Management based on play-authenticate # TODO: cleanup, remove what is not used and keep what's important GET /api/user controllers.Users.getUsers() -GET /api/user/:uid controllers.Users.getUser(uid: Long) -GET /api/user/:uid/loggedin controllers.Users.getCurrentUser(uid: Long) -GET /api/user/:uid/profile controllers.Users.profile(uid: Long) -POST /api/user/signup controllers.Users.doSignup() -POST /api/user/login controllers.Users.doLogin() -POST /api/user/logout controllers.Users.doLogout() -PUT /api/user/:uid controllers.Users.updateUser(uid: Long) -PUT /api/user/:uid/profile controllers.Users.updateUserProfile(uid: Long) -DELETE /api/user/:uid controllers.Users.deleteUser(uid: Long) -DELETE /api/user/:uid/force controllers.Users.deleteUserForce(uid: Long) -GET /api/user/verify/:token controllers.Users.verify(token: String) +GET /api/user/:uid controllers.Users.getUser(uid: Long) +GET /api/user/:uid/loggedin controllers.Users.getCurrentUser(uid: Long) +GET /api/user/:uid/profile controllers.Users.profile(uid: Long) +POST /api/user/signup controllers.Users.doSignup() +POST /api/user/login controllers.Users.doLogin() +POST /api/user/logout controllers.Users.doLogout() +PUT /api/user/:uid controllers.Users.updateUser(uid: Long) +PUT /api/user/:uid/profile controllers.Users.updateUserProfile(uid: Long) +DELETE /api/user/:uid controllers.Users.deleteUser(uid: Long) +DELETE /api/user/:uid/force controllers.Users.deleteUserForce(uid: Long) +GET /api/user/verify/:token controllers.Users.verify(token: String) +GET /api/user/:uuid/campaign controllers.Campaigns.campaignsByUser(uuid: java.util.UUID, filter ?="ongoing") + +##################################################################################### +# Notification Server +# TODO: integrate a proper message queue manager like rabbitmq +GET /api/notification/user/:uuid controllers.Notifications.userInbox(uuid: java.util.UUID) ##################################################################################### # Assemblies API @@ -68,6 +75,7 @@ GET /api/assembly/:aid/group/:id/membership/:status controllers.WorkingG # Invitations and Requests to join a Group and/or and Assembly # For invitations membership, let's create a random 64 char token POST /api/membership controllers.Memberships.createMembership() +GET /api/membership/user/:uuid controllers.Memberships.findMembershipByUser(uuid: java.util.UUID, type ?="") GET /api/membership/:id controllers.Memberships.readMembership(id: Long) GET /api/membership/:id/role controllers.Memberships.readMembershipRoles(id: Long) POST /api/membership/:id/role controllers.Memberships.addMembershipRole(id: Long) @@ -99,6 +107,8 @@ GET /api/assembly/:aid/campaign/:cid controllers.Campaign PUT /api/assembly/:aid/campaign/:cid controllers.Campaigns.updateCampaign(aid: Long, cid: Long) POST /api/assembly/:aid/campaign controllers.Campaigns.createCampaign(aid: Long) DELETE /api/assembly/:aid/campaign/:cid controllers.Campaigns.deleteCampaign(aid: Long, cid: Long) +GET /api/assembly/:uuid/campaign controllers.Campaigns.campaignsByAssembly(uuid: java.util.UUID, filter?="ongoing") + ##################################################################################### # CampaignPhase API @@ -164,6 +174,7 @@ GET /api/user/link controllers.Users.li GET /api/user/unverified controllers.Users.unverified() GET /api/user/exists controllers.Users.exists() GET /api/user/login/denied controllers.Users.onLoginUserNotFound() +GET /api/user/login controllers.Users.login() GET /api/user/authenticate/:provider/denied controllers.Users.oAuthDenied(provider: String) GET /api/user/authenticated controllers.Restricted.index() GET /api/user/login/:id controllers.Restricted.id(id: String) @@ -179,6 +190,7 @@ GET /api/user/merge controllers.Users.as POST /api/user/merge controllers.Users.doMerge GET /api/user/password/forgot controllers.Users.forgotPassword(email: String ?= "") POST /api/user/password/forgot controllers.Users.doForgotPassword +GET /api/user/logout/redirect controllers.Users.redirectAfterLogout ##################################################################################### # Composition Engine endpoints diff --git a/project/plugins.sbt b/project/plugins.sbt index e467fccc..fe6c028b 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -33,3 +33,4 @@ addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "4.0.0") addSbtPlugin("com.typesafe.sbt" % "sbt-play-ebean" % "1.0.0") addSbtPlugin("com.typesafe.sbt" % "sbt-play-enhancer" % "1.1.0") + diff --git a/public/images/belleville-small.jpg b/public/images/belleville-small.jpg deleted file mode 100644 index eda67f1c..00000000 Binary files a/public/images/belleville-small.jpg and /dev/null differ