diff --git a/AgentDiscoveries-Backend/pom.xml b/AgentDiscoveries-Backend/pom.xml index 9f57adb9..f28a8fae 100644 --- a/AgentDiscoveries-Backend/pom.xml +++ b/AgentDiscoveries-Backend/pom.xml @@ -203,8 +203,22 @@ guava 24.0-jre + + org.glassfish.jersey.core + jersey-client + 2.26 + + + org.glassfish.jersey.inject + jersey-hk2 + 2.26 + + + org.glassfish.jersey.media + jersey-media-json-jackson + 2.26 + - diff --git a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/AgentDiscoveriesApplication.java b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/AgentDiscoveriesApplication.java index 82003916..3c2ba2c7 100644 --- a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/AgentDiscoveriesApplication.java +++ b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/AgentDiscoveriesApplication.java @@ -44,6 +44,7 @@ public class AgentDiscoveriesApplication implements Runnable { @Inject MessageProcessorRoutes messageProcessorRoutes; @Inject ExternalReportRoutes externalReportRoutes; @Inject PictureRoutes pictureRoutes; + @Inject ForumMessageRoutes forumMessageRoutes; @Override public void run() { @@ -55,7 +56,6 @@ public void run() { // Serve the static assets from the frontend project staticFileLocation("/frontend"); - // Setup of all the routes path("/v1", () -> { // Endpoint used to get an authorisation token @@ -63,7 +63,8 @@ public void run() { path("/api", () -> { before("/*", tokenRoutes::validateToken); - get("/checktoken", (req, res) -> "Token is valid"); + + get("/checktoken", (req, res) -> "Token is valid", responseTransformer); path("/legacy", () -> { before("/*", (request, response) -> response.type("text/plain")); @@ -75,6 +76,7 @@ public void run() { path("/reports/locationstatuses", () -> reportsRouteGroup(locationStatusReportsRoutes)); path("/reports/regionsummaries", () -> reportsRouteGroup(regionSummaryReportsRoutes)); path("/external", this::externalRouteGroup); + path("/forum", this::forumRouteGroup); setupBasicEntityCrudRoutes("/locations", locationsRoutes); get("/locations", locationsRoutes::readEntities, responseTransformer); @@ -83,6 +85,8 @@ public void run() { post("/decodemessage", messageProcessorRoutes::decodeMessage, responseTransformer); post("/encodemessage", messageProcessorRoutes::encodeMessage, responseTransformer); + get("/mostwanted", (req, res) -> agentsRoutes.mostWanted(req, res), responseTransformer); + // API endpoint to initiate shutdown put("/operations/shutdown", this::shutdown); }); @@ -103,6 +107,12 @@ public void run() { get("/healthcheck", (req, res) -> "Server started okay!"); } + private void forumRouteGroup() { + get("", (req, res) -> forumMessageRoutes.readForum(req, res), responseTransformer ); + post("/add", (req, res) -> forumMessageRoutes.createForumMessage(req, res), responseTransformer); + + } + private void executivesSummaryGroup() { post("/generate", executiveSummaryRoutes::readExecutiveSummary); } @@ -118,6 +128,7 @@ private void agentsRouteGroup() { get("/:id", (req, res) -> agentsRoutes.readAgent(req, res, idParamAsInt(req)), responseTransformer); put("/:id", (req, res) -> agentsRoutes.updateAgent(req, res, idParamAsInt(req)), responseTransformer); put("/editcallsign/:id", (req, res) -> agentsRoutes.editCallSign(req, res, idParamAsInt(req)), responseTransformer); + put("/mostwanted/:id", (req, res) -> agentsRoutes.editCallSign(req, res, idParamAsInt(req)), responseTransformer); delete("/:id", (req, res) -> agentsRoutes.deleteAgent(req, res, idParamAsInt(req)), responseTransformer); get("", (req, res) -> agentsRoutes.readAgents(req, res), responseTransformer); } @@ -133,6 +144,7 @@ private void regionsRouteGroup() { private void reportsRouteGroup(ReportsRoutesBase reportsRoutes) { post("", reportsRoutes::createReport, responseTransformer); get("/:id", (req, res) -> reportsRoutes.readReport(req, res, idParamAsInt(req)), responseTransformer); + put("/:id", (req, res) -> reportsRoutes.updateReport(req, res, idParamAsInt(req)), responseTransformer); delete("/:id", (req, res) -> reportsRoutes.deleteReport(req, res, idParamAsInt(req)), responseTransformer); get("", reportsRoutes::searchReports, responseTransformer); } diff --git a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/core/CustomisedGsonBuilder.java b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/core/CustomisedGsonBuilder.java index 8a10b5ad..140e3fa8 100644 --- a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/core/CustomisedGsonBuilder.java +++ b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/core/CustomisedGsonBuilder.java @@ -7,9 +7,11 @@ import org.softwire.training.api.models.UserApiModel; import java.io.IOException; import java.time.LocalDate; +import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; +import java.util.TimeZone; public class CustomisedGsonBuilder { @@ -73,6 +75,7 @@ private static class ZonedDateTimeAdapter extends TypeAdapter { @Override public void write(JsonWriter jsonWriter, ZonedDateTime localDateTime) throws IOException { + if (localDateTime == null) { jsonWriter.nullValue(); } else { diff --git a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/core/PermissionsVerifier.java b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/core/PermissionsVerifier.java index 56831cce..814c5ac6 100644 --- a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/core/PermissionsVerifier.java +++ b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/core/PermissionsVerifier.java @@ -28,9 +28,14 @@ public void verifyIsAdminOrRelevantUser(Request req, int relevantUserId) { } public void verifyIsAdminOrRelevantAgent(Request req, int relevantAgentId) { + verifyUser(req, user -> user.isAdmin() || (user.getAgentId() != null && user.getAgentId() == relevantAgentId)); } + public void verifyisAgentorAdmin(Request req) { + verifyUser(req, user -> user.isAdmin() || (user.getAgentId() != null)); + } + public void verifyIsAgent(Request req) { verifyUser(req, user -> user.getAgentId() != null); } diff --git a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/models/LocationStatusReportApiModel.java b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/models/LocationStatusReportApiModel.java index e8791f7f..4ca048e2 100644 --- a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/models/LocationStatusReportApiModel.java +++ b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/models/LocationStatusReportApiModel.java @@ -18,7 +18,6 @@ public void setReportTitle(String reportTitle) { this.reportTitle = reportTitle; } - public int getLocationId() { return locationId; } diff --git a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/models/MostWantedApiModel.java b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/models/MostWantedApiModel.java new file mode 100644 index 00000000..672d2b64 --- /dev/null +++ b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/models/MostWantedApiModel.java @@ -0,0 +1,22 @@ +package org.softwire.training.api.models; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class MostWantedApiModel { + String title; + String description; + MostWantedSuspectImage[] images; + + public String getTitle() { + return title; + } + + public String getDescription() { + return description; + } + + public MostWantedSuspectImage[] getImages() { + return images; + } +} diff --git a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/models/MostWantedSuspectImage.java b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/models/MostWantedSuspectImage.java new file mode 100644 index 00000000..474cad54 --- /dev/null +++ b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/models/MostWantedSuspectImage.java @@ -0,0 +1,12 @@ +package org.softwire.training.api.models; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class MostWantedSuspectImage { + String original; + + public String getOriginal() { + return original; + } +} diff --git a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/models/MostWantedWrapper.java b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/models/MostWantedWrapper.java new file mode 100644 index 00000000..0e65ce9b --- /dev/null +++ b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/models/MostWantedWrapper.java @@ -0,0 +1,7 @@ +package org.softwire.training.api.models; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class MostWantedWrapper { + public MostWantedApiModel[] items; +} diff --git a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/models/ReportApiModelBase.java b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/models/ReportApiModelBase.java index 6fe70b72..2887d896 100644 --- a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/models/ReportApiModelBase.java +++ b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/models/ReportApiModelBase.java @@ -5,7 +5,7 @@ public class ReportApiModelBase { private int reportId; - private byte status; + private int status; private ZonedDateTime reportTime; private String reportBody; private int agentId; @@ -18,11 +18,11 @@ public void setReportId(int reportId) { this.reportId = reportId; } - public byte getStatus() { + public int getStatus() { return status; } - public void setStatus(byte status) { + public void setStatus(int status) { this.status = status; } diff --git a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/routes/v1/AgentsRoutes.java b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/routes/v1/AgentsRoutes.java index 22598484..5ffaecb5 100644 --- a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/routes/v1/AgentsRoutes.java +++ b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/routes/v1/AgentsRoutes.java @@ -1,20 +1,20 @@ package org.softwire.training.api.routes.v1; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; import org.apache.commons.lang3.StringUtils; +import org.glassfish.jersey.jackson.JacksonFeature; import org.softwire.training.api.core.JsonRequestUtils; import org.softwire.training.api.core.PermissionsVerifier; -import org.softwire.training.api.models.ErrorCode; -import org.softwire.training.api.models.FailedRequestException; +import org.softwire.training.api.models.*; import org.softwire.training.db.daos.AgentsDao; import org.softwire.training.models.Agent; import spark.Request; import spark.Response; - import javax.inject.Inject; +import javax.ws.rs.core.MediaType; import java.util.List; -import java.time.LocalDate; import java.util.NoSuchElementException; -import java.util.Optional; public class AgentsRoutes { @@ -54,11 +54,27 @@ public List readAgents(Request req, Response res) { public Agent updateAgent(Request req, Response res, int id) { permissionsVerifier.verifyIsAdminOrRelevantAgent(req, id); + try{ + Agent agent = JsonRequestUtils.readBodyAsType(req, Agent.class); + } catch (Exception e){ + throw new FailedRequestException(ErrorCode.INVALID_INPUT, "Rank is too high (max 11 digits)."); + } + Agent agent = JsonRequestUtils.readBodyAsType(req, Agent.class); + + if (agent.getFirstName().length() > 20){ + throw new FailedRequestException(ErrorCode.INVALID_INPUT, "first name too long (max 20 characters)"); + } + + if (agent.getLastName().length() > 20){ + throw new FailedRequestException(ErrorCode.INVALID_INPUT, "Last name too long (max 20 characters)"); + } + agent.setAgentId(id); agentsDao.updateAgent(agent); return agent; + } public Object deleteAgent(Request req, Response res, int id) { @@ -82,4 +98,14 @@ public Agent editCallSign (Request req, Response res, int agentID) { agentsDao.updateAgent(agent); return agent; } + + public MostWantedApiModel[] mostWanted (Request req, Response res) { + Client client = ClientBuilder.newBuilder().register(JacksonFeature.class).hostnameVerifier((s1, s2) -> true).build(); + MostWantedWrapper mostwanted = client + .target("https://api.fbi.gov/@wanted?pageSize=10&page=1&sort_on=modified&sort_order=desc&person_classification=main&status=na") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(MostWantedWrapper.class); + + return mostwanted.items; + } } diff --git a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/routes/v1/ForumMessageRoutes.java b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/routes/v1/ForumMessageRoutes.java new file mode 100644 index 00000000..2df713ef --- /dev/null +++ b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/routes/v1/ForumMessageRoutes.java @@ -0,0 +1,55 @@ +package org.softwire.training.api.routes.v1; + +import org.softwire.training.api.core.JsonRequestUtils; +import org.softwire.training.api.core.PermissionsVerifier; +import org.softwire.training.api.models.ErrorCode; +import org.softwire.training.api.models.FailedRequestException; +import org.softwire.training.db.daos.ForumMessageDao; +import org.softwire.training.models.ForumMessage; +import org.softwire.training.models.User; +import spark.Request; +import spark.Response; + +import javax.inject.Inject; +import java.util.List; + +public class ForumMessageRoutes { + + private final ForumMessageDao forumMessageDao; + private final PermissionsVerifier permissionsVerifier; + + @Inject + public ForumMessageRoutes(ForumMessageDao forumMessageDao, PermissionsVerifier permissionsVerifier) { + this.forumMessageDao = forumMessageDao; + this.permissionsVerifier = permissionsVerifier; + } + + public List readForum(Request req, Response res) { + permissionsVerifier.verifyisAgentorAdmin(req); + return forumMessageDao.getForumMessages(); + } + + public ForumMessage createForumMessage(Request req, Response res) { + ForumMessage forumMessageModel = JsonRequestUtils.readBodyAsType(req, ForumMessage.class); + permissionsVerifier.verifyisAgentorAdmin(req); + + User user = new User(); + user.setUserId(req.attribute("user_id")); + forumMessageModel.setUser(user); + + int MessageId = forumMessageDao.createForum(forumMessageModel); + forumMessageModel.setMessageId(MessageId); + + // Create requests should return 201 + res.status(201); + + return forumMessageModel; + } + + public ForumMessage readForum(Request req, Response res, int id) { + permissionsVerifier.verifyisAgentorAdmin(req); + return forumMessageDao.getForumMessage(id) + .orElseThrow(() -> new FailedRequestException(ErrorCode.NOT_FOUND, "Forum not found")); + } + +} \ No newline at end of file diff --git a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/routes/v1/LocationStatusReportsRoutes.java b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/routes/v1/LocationStatusReportsRoutes.java index bfbfbad9..b993d7c5 100644 --- a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/routes/v1/LocationStatusReportsRoutes.java +++ b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/routes/v1/LocationStatusReportsRoutes.java @@ -44,9 +44,14 @@ protected LocationStatusReport validateThenMap(LocationStatusReportApiModel apiM LocalDateTime reportTimeUtc = LocalDateTime.now(ZoneOffset.UTC); LocationStatusReport model = new LocationStatusReport(); + model.setStatus(apiModel.getStatus()); + + if (model.getStatus() > 100 || model.getStatus() <= 0){ + throw new FailedRequestException(ErrorCode.UNKNOWN_ERROR, "Status code must be between 1 and 100."); + } + model.setAgentId(apiModel.getAgentId()); model.setLocationId(apiModel.getLocationId()); - model.setStatus(apiModel.getStatus()); model.setReportTime(reportTimeUtc); model.setReportBody(apiModel.getReportBody()); model.setReportTitle(apiModel.getReportTitle()); @@ -105,6 +110,9 @@ protected List parseSearchCriteria(Request req) { searchCriteria.add(new ToTimeSearchCriterion(ZonedDateTime.parse(queryMap.get("toTime").value()))); } + if (!isNullOrEmpty(queryMap.get("agentId").value())) { + searchCriteria.add(new AgentIdSearchCriterion(queryMap.get("agentId").integerValue())); + } return searchCriteria; } } diff --git a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/routes/v1/LocationsRoutes.java b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/routes/v1/LocationsRoutes.java index e5ce865d..71ba363f 100644 --- a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/routes/v1/LocationsRoutes.java +++ b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/routes/v1/LocationsRoutes.java @@ -1,11 +1,13 @@ package org.softwire.training.api.routes.v1; +import org.eclipse.jetty.server.Authentication; import org.softwire.training.api.core.JsonRequestUtils; import org.softwire.training.api.core.PermissionsVerifier; import org.softwire.training.api.models.ErrorCode; import org.softwire.training.api.models.FailedRequestException; import org.softwire.training.db.daos.LocationsDao; import org.softwire.training.models.Location; +import org.softwire.training.db.daos.RegionsDao; import spark.Request; import spark.Response; import spark.utils.StringUtils; @@ -14,16 +16,19 @@ import java.time.DateTimeException; import java.time.ZoneId; import java.util.List; +import java.util.Optional; public class LocationsRoutes implements EntityCRUDRoutes { private final LocationsDao locationsDao; private final PermissionsVerifier permissionsVerifier; + private final RegionsDao regionsDao; @Inject - public LocationsRoutes(LocationsDao locationsDao, PermissionsVerifier permissionsVerifier) { + public LocationsRoutes(LocationsDao locationsDao, PermissionsVerifier permissionsVerifier, RegionsDao regionsDao) { this.locationsDao = locationsDao; this.permissionsVerifier = permissionsVerifier; + this.regionsDao = regionsDao; } @Override @@ -60,6 +65,7 @@ public List readEntities(Request req, Response res){ @Override public Location updateEntity(Request req, Response res, int id) { + Location location = JsonRequestUtils.readBodyAsType(req, Location.class); if (location.getLocationId() != id && location.getLocationId() != 0) { @@ -76,6 +82,10 @@ public Location updateEntity(Request req, Response res, int id) { private void validateLocationModel(Location location) { + if(regionsDao.getRegion(location.getRegionId()).equals(Optional.empty())){ + throw new FailedRequestException(ErrorCode.INVALID_INPUT, "this regionId doesn't exist, try again"); + } + if (location.getSiteName().length() > 20) { throw new FailedRequestException(ErrorCode.INVALID_INPUT, "site name is too long (max 20 chars)."); } diff --git a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/routes/v1/RegionsRoutes.java b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/routes/v1/RegionsRoutes.java index 85705715..8eee7744 100644 --- a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/routes/v1/RegionsRoutes.java +++ b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/routes/v1/RegionsRoutes.java @@ -33,6 +33,10 @@ public Region createRegion(Request req, Response res) { throw new FailedRequestException(ErrorCode.INVALID_INPUT, "regionId cannot be specified on create"); } + if (region.getName().length() > 50) { + throw new FailedRequestException(ErrorCode.INVALID_INPUT, "Region name too long (50 characters max)."); + } + int newRegionId = regionsDao.createRegion(region); // Create requests should return 201 diff --git a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/routes/v1/ReportsRoutesBase.java b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/routes/v1/ReportsRoutesBase.java index 1dfa3bc2..75663a8f 100644 --- a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/routes/v1/ReportsRoutesBase.java +++ b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/routes/v1/ReportsRoutesBase.java @@ -8,11 +8,14 @@ import org.softwire.training.db.daos.ReportsDao; import org.softwire.training.db.daos.UsersDao; import org.softwire.training.db.daos.searchcriteria.ReportSearchCriterion; +import org.softwire.training.models.LocationStatusReport; import org.softwire.training.models.ReportBase; import spark.Request; import spark.Response; import spark.utils.StringUtils; +import java.time.LocalDateTime; +import java.time.ZoneOffset; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -79,7 +82,15 @@ public T createReport(Request req, Response res) { } public T readReport(Request req, Response res, int id) { - permissionsVerifier.verifyAdminPermission(req); + + Integer userId = req.attribute("user_id"); + + if(userId == null){ + permissionsVerifier.verifyAdminPermission(req); + } + else { + permissionsVerifier.verifyIsAdminOrRelevantUser(req, userId); + } return mapToApiModel(reportsDao.getReport(id) .orElseThrow(() -> new FailedRequestException(ErrorCode.NOT_FOUND, "Report not found"))); } @@ -98,11 +109,27 @@ public Object deleteReport(Request req, Response res, int id) throws Exception { } public List searchReports(Request req, Response res) { - permissionsVerifier.verifyAdminPermission(req); + + Integer agentId = req.queryMap().get("agentId").integerValue(); + + if(agentId == null){ + permissionsVerifier.verifyAdminPermission(req); + } + else { + permissionsVerifier.verifyIsAdminOrRelevantAgent(req, agentId); + } return reportsDao.searchReports(parseSearchCriteria(req)) .stream() .map(this::mapToApiModel) .collect(Collectors.toList()); } + + public LocationStatusReport updateReport(Request req, Response res, int id) { + LocationStatusReport location = JsonRequestUtils.readBodyAsType(req, LocationStatusReport.class); + location.setReportTime(LocalDateTime.now(ZoneOffset.UTC)); + reportsDao.updateReport(location); + return location; + } + } diff --git a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/routes/v1/UsersRoutes.java b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/routes/v1/UsersRoutes.java index ab25bae5..f5191ac2 100644 --- a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/routes/v1/UsersRoutes.java +++ b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/api/routes/v1/UsersRoutes.java @@ -14,6 +14,7 @@ import spark.utils.StringUtils; import javax.inject.Inject; +import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -41,6 +42,11 @@ public UserApiModel createEntity(Request req, Response res) { throw new FailedRequestException(ErrorCode.INVALID_INPUT, "userId cannot be specified on create"); } + if (userApiModel.getUsername().length() > 20){ + throw new FailedRequestException(ErrorCode.INVALID_INPUT, "Username cannot be greater than 20 characters."); + + } + User user = new User( userApiModel.getUsername(), passwordHasher.hashPassword(userApiModel.getPassword()), diff --git a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/db/daos/DaoHelper.java b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/db/daos/DaoHelper.java index 6c1604c7..ba2c7da7 100644 --- a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/db/daos/DaoHelper.java +++ b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/db/daos/DaoHelper.java @@ -1,63 +1,74 @@ -package org.softwire.training.db.daos; - -import javax.persistence.EntityManager; -import javax.persistence.EntityManagerFactory; -import java.util.List; -import java.util.Optional; - -class DaoHelper { - - EntityManagerFactory entityManagerFactory; - - DaoHelper(EntityManagerFactory entityManagerFactory) { - this.entityManagerFactory = entityManagerFactory; - } - - Optional getEntity(Class entityClass, int id) { - - EntityManager em = entityManagerFactory.createEntityManager(); - em.getTransaction().begin(); - T entity = em.find(entityClass, id); - em.getTransaction().commit(); - em.close(); - return Optional.ofNullable(entity); - } - - List getEntities(Class entityClass) { - EntityManager em = entityManagerFactory.createEntityManager(); - em.getTransaction().begin(); - - List results = em.createQuery("FROM " + entityClass.getSimpleName(), entityClass).getResultList(); - em.getTransaction().commit(); - em.close(); - return results; - } - - void createEntity(T entity) { - EntityManager em = entityManagerFactory.createEntityManager(); - em.getTransaction().begin(); - em.persist(entity); - em.flush(); - em.getTransaction().commit(); - em.close(); - } - - void deleteEntity(Class entityClass, int id) { - EntityManager em = entityManagerFactory.createEntityManager(); - em.getTransaction().begin(); - T entity = em.find(entityClass, id); - if (entity != null) { - em.remove(entity); - } - em.getTransaction().commit(); - em.close(); - } - - void updateEntity(T entity) { - EntityManager em = entityManagerFactory.createEntityManager(); - em.getTransaction().begin(); - em.merge(entity); - em.getTransaction().commit(); - em.close(); - } -} +package org.softwire.training.db.daos; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import java.util.List; +import java.util.Optional; + +class DaoHelper { + + EntityManagerFactory entityManagerFactory; + + DaoHelper(EntityManagerFactory entityManagerFactory) { + this.entityManagerFactory = entityManagerFactory; + } + + + Optional getEntity(Class entityClass, int id) { + + EntityManager em = entityManagerFactory.createEntityManager(); + em.getTransaction().begin(); + T entity = em.find(entityClass, id); + em.getTransaction().commit(); + em.close(); + return Optional.ofNullable(entity); + } + + List getNrOfEntities(Class entityClass, int limit) { + EntityManager em = entityManagerFactory.createEntityManager(); + em.getTransaction().begin(); + + List results = em.createQuery("FROM " + entityClass.getSimpleName() + " ORDER BY MessageId DESC", entityClass).setMaxResults(limit).getResultList(); + em.getTransaction().commit(); + em.close(); + return results; + } + + List getEntities(Class entityClass) { + EntityManager em = entityManagerFactory.createEntityManager(); + em.getTransaction().begin(); + + List results = em.createQuery("FROM " + entityClass.getSimpleName(), entityClass).getResultList(); + em.getTransaction().commit(); + em.close(); + return results; + } + + void createEntity(T entity) { + EntityManager em = entityManagerFactory.createEntityManager(); + em.getTransaction().begin(); + em.persist(entity); + em.flush(); + em.getTransaction().commit(); + em.close(); + } + + void deleteEntity(Class entityClass, int id) { + EntityManager em = entityManagerFactory.createEntityManager(); + em.getTransaction().begin(); + T entity = em.find(entityClass, id); + if (entity != null) { + em.remove(entity); + } + em.getTransaction().commit(); + em.close(); + } + + void updateEntity(T entity) { + EntityManager em = entityManagerFactory.createEntityManager(); + em.getTransaction().begin(); + em.merge(entity); + em.getTransaction().commit(); + em.close(); + } +} diff --git a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/db/daos/ForumMessageDao.java b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/db/daos/ForumMessageDao.java new file mode 100644 index 00000000..909f221d --- /dev/null +++ b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/db/daos/ForumMessageDao.java @@ -0,0 +1,31 @@ +package org.softwire.training.db.daos; + +import org.softwire.training.models.ForumMessage; + +import javax.inject.Inject; +import javax.persistence.EntityManagerFactory; +import java.util.List; +import java.util.Optional; + +public class ForumMessageDao { + + private DaoHelper helper; + + @Inject + public ForumMessageDao(EntityManagerFactory entityManagerFactory) { + this.helper = new DaoHelper<>(entityManagerFactory); + } + + public Optional getForumMessage(int MessageId) { return helper.getEntity(ForumMessage.class, MessageId);} + + public List getForumMessages() { + List messages = helper.getNrOfEntities(ForumMessage.class, 10); + return messages; + } + + public int createForum(ForumMessage forumMessage) { + helper.createEntity(forumMessage); + return forumMessage.getMessageId(); + } + +} diff --git a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/db/daos/LocationReportsDao.java b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/db/daos/LocationReportsDao.java index 89daeb58..f15ff227 100644 --- a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/db/daos/LocationReportsDao.java +++ b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/db/daos/LocationReportsDao.java @@ -55,4 +55,8 @@ public List searchReports(List sear return results; } + + public void updateReport(LocationStatusReport report) { + helper.updateEntity(report); + } } diff --git a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/db/daos/RegionSummaryReportsDao.java b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/db/daos/RegionSummaryReportsDao.java index 3e7038e0..86eb132d 100644 --- a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/db/daos/RegionSummaryReportsDao.java +++ b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/db/daos/RegionSummaryReportsDao.java @@ -1,6 +1,7 @@ package org.softwire.training.db.daos; import org.softwire.training.db.daos.searchcriteria.ReportSearchCriterion; +import org.softwire.training.models.LocationStatusReport; import org.softwire.training.models.RegionSummaryReport; import javax.inject.Inject; @@ -56,4 +57,8 @@ public List searchReports(List searc return results; } + + public void updateReport(LocationStatusReport report) { + } + } diff --git a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/db/daos/ReportsDao.java b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/db/daos/ReportsDao.java index e89910a9..87975f4f 100644 --- a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/db/daos/ReportsDao.java +++ b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/db/daos/ReportsDao.java @@ -1,6 +1,7 @@ package org.softwire.training.db.daos; import org.softwire.training.db.daos.searchcriteria.ReportSearchCriterion; +import org.softwire.training.models.LocationStatusReport; import java.util.List; import java.util.Optional; @@ -18,4 +19,6 @@ public interface ReportsDao { void deleteReport(int reportId); List searchReports(List searchCriteria); + + void updateReport(LocationStatusReport report); } diff --git a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/db/daos/searchcriteria/AgentIdSearchCriterion.java b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/db/daos/searchcriteria/AgentIdSearchCriterion.java new file mode 100644 index 00000000..9c04e9eb --- /dev/null +++ b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/db/daos/searchcriteria/AgentIdSearchCriterion.java @@ -0,0 +1,24 @@ +package org.softwire.training.db.daos.searchcriteria; + +import java.util.Collections; +import java.util.Map; + +public final class AgentIdSearchCriterion extends ReportSearchCriterion { + + private static final String AGENT_ID_BINDING_NAME = "agent_id_sc_agent_id"; + private final int agentId; + + public AgentIdSearchCriterion(int userId) { + this.agentId = userId; + } + + @Override + public String getSqlForWhereClause() { + return "agent_id = :" + AGENT_ID_BINDING_NAME; + } + + @Override + public Map getBindingsForSql() { + return Collections.singletonMap(AGENT_ID_BINDING_NAME, agentId); + } +} diff --git a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/models/ForumMessage.java b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/models/ForumMessage.java new file mode 100644 index 00000000..2683fbfb --- /dev/null +++ b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/models/ForumMessage.java @@ -0,0 +1,42 @@ +package org.softwire.training.models; + +import javax.persistence.*; + +@Entity +@Table(name = "messages") +public class ForumMessage { + + private int MessageId; + private String Message; + + public ForumMessage() {} + + public ForumMessage(int messageId, String message) { + MessageId = messageId; + Message = message; + } + + @Id + @Column(name = "MessageId", nullable = false) + @GeneratedValue(strategy = GenerationType.IDENTITY) + public int getMessageId() { return MessageId; } + + public void setMessageId(int messageId) { this.MessageId = messageId; } + + @Column(name = "Message", nullable = false) + public String getMessage() { return Message; } + + public void setMessage(String Message) { this.Message = Message; } + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "UserID") + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + private User user; +} diff --git a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/models/ReportBase.java b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/models/ReportBase.java index 58e3e71f..d43125b2 100644 --- a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/models/ReportBase.java +++ b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/models/ReportBase.java @@ -7,7 +7,7 @@ public class ReportBase { private int reportId; - private byte status; + private int status; private LocalDateTime reportTime; // Always UTC in the DB private String reportBody; private int agentId; @@ -24,11 +24,11 @@ public void setReportId(int reportId) { } @Column(name = "status", nullable = false) - public byte getStatus() { + public int getStatus() { return status; } - public void setStatus(byte status) { + public void setStatus(int status) { this.status = status; } diff --git a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/models/User.java b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/models/User.java index 84fafc94..05c9a14e 100644 --- a/AgentDiscoveries-Backend/src/main/java/org/softwire/training/models/User.java +++ b/AgentDiscoveries-Backend/src/main/java/org/softwire/training/models/User.java @@ -1,6 +1,7 @@ package org.softwire.training.models; import javax.persistence.*; +import java.util.List; @Entity @Table(name = "users") diff --git a/AgentDiscoveries-Backend/src/main/resources/db/migration/V10__Message_board.sql b/AgentDiscoveries-Backend/src/main/resources/db/migration/V10__Message_board.sql new file mode 100644 index 00000000..5b2515b9 --- /dev/null +++ b/AgentDiscoveries-Backend/src/main/resources/db/migration/V10__Message_board.sql @@ -0,0 +1,6 @@ +create table messages ( + MessageID INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + Message VARCHAR(255) NOT NULL, + UserID INT NULL, + FOREIGN KEY fk_messages_user_id (UserID) REFERENCES users(user_id) ON DELETE SET NULL +); \ No newline at end of file diff --git a/AgentDiscoveries-Frontend/app/sass/styles.scss b/AgentDiscoveries-Frontend/app/sass/styles.scss index 6e9078e2..f9fafe93 100644 --- a/AgentDiscoveries-Frontend/app/sass/styles.scss +++ b/AgentDiscoveries-Frontend/app/sass/styles.scss @@ -1,111 +1,201 @@ -$background-color: rgb(238, 238, 238); -$text-color: rgb(12, 12, 12); -$overlay-color: rgb(247, 197, 89); - -body { - background-color: $background-color; - color: $text-color; -} - -.agent-discoveries-logo { - height: 100%; - display: inline-block; - padding-right: 8px; -} - -.rm-3 { - margin-right: 0.75rem !important; -} - -.ml-3 { - margin-left: 0.75rem !important; -} - -.img-md { - max-width: 40%; - max-height: 40%; -} - -.border-3 { - margin-top: 0.75rem; - margin-left: 0.75rem; - margin-right: 0.75rem; - margin-bottom: 0.75rem; -} - -.overlay { - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - height: 100%; - width: 100%; - opacity: 0; - transition: .5s ease; - background-color: $overlay-color; -} - -.overlay-action { - width: 100%; - height: 100%; - - p { - color: white; - font-size: 20px; - position: absolute; - text-align: center; - top: 50%; - left: 50%; - margin-right: -50%; - transform: translate(-50%, -50%) - } -} - - -.img { - display: inline-block; - width: 100%; - height: 100%; -} - -.profile-img-container { - position: relative; - width: 50%; -} - -.profile-img-container:hover .overlay { - opacity: 0.8; -} - -.resizeOff{ - resize: none; -} - -.motto { - font-size: 30px; - text-align: center; - font-family: "Courier New"; - font-weight: bold; - color: #0A5FA9; -} - -.align-properly{ - margin: auto; - width: 50%; - padding: 10px; -} - -.cia-logo{ - max-width: 60%; - max-height: 60%; -} - -.my-custom-scrollbar { -height: 200px; -overflow: auto; -} - -.table-wrapper-scroll-y { -display: block; +$background-color: rgb(238, 238, 238); //238 x 3 +$text-color: rgb(12, 12, 12); +$overlay-color: rgb(247, 197, 89); +$dark-background-color: rgb(52, 52, 52); +$dark-text-color: rgb(100, 100, 100); +$minimum-view-height: 1000px; + +body { + background-color: $background-color; + color: $text-color; +} + +.nightMode { + background-color: $dark-background-color; + color: $dark-text-color; + min-height: $minimum-view-height; +} + +.lightMode { + background-color: $background-color; + color: $text-color; + min-height: $minimum-view-height; +} + +.default { + background-color: white; + min-height: $minimum-view-height; + h3 { + text-decoration: underline; + } +} +.agent-discoveries-logo { + height: 100%; + display: inline-block; + padding-right: 8px; +} + +.rm-3 { + margin-right: 0.75rem !important; +} + +.ml-3 { + margin-left: 0.75rem !important; +} + +.img-md { + max-width: 40%; + max-height: 40%; +} + +.border-3 { + margin-top: 0.75rem; + margin-left: 0.75rem; + margin-right: 0.75rem; + margin-bottom: 0.75rem; +} + +.overlay { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + height: 100%; + width: 100%; + opacity: 0; + transition: .5s ease; + background-color: $overlay-color; +} + +.overlay-action { + width: 100%; + height: 100%; + + p { + color: white; + font-size: 20px; + position: absolute; + text-align: center; + top: 50%; + left: 50%; + margin-right: -50%; + transform: translate(-50%, -50%) + } +} + + +.img { + display: inline-block; + width: 100%; + height: 100%; +} + +.profile-img-container { + position: relative; + width: 50%; +} + +.profile-img-container:hover .overlay { + opacity: 0.8; +} + +.resizeOff{ + resize: none; +} + +.motto { + font-size: 30px; + text-align: center; + font-family: "Courier New"; + font-weight: bold; + color: #0A5FA9; +} + +.align-properly{ + margin: auto; + width: 50%; + padding: 10px; +} + +.cia-logo{ + max-width: 60%; + max-height: 60%; +} + +.my-custom-scrollbar { + height: 200px; + overflow: auto; +} + +.table-wrapper-scroll-y { + display: block; +} + +.mostwantedtable { + display: flex; + flex-flow: wrap; + justify-content: center; +} + +.customtable { + display: flex; + flex-flow: wrap; + justify-content: center; +} + +.mostwantedportrait { + text-align: center; + display: inline-table; + max-width: 20%; + +} + +.customWanted { + max-width: 246px; +} + +.mostwanted-title { + font-size: 20px; + height: 85px; + margin-top: 0; + color: black; +} + +.mostwanted-description { + font-size: 14px; + text-align: left; + padding-left: 10px; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; + margin-bottom: 50px; + color: black; +} + +.mostwanted-image { + width: 90%; + max-height: 210px; +} + +.wantedlogo { + width: 100%; +} + +.messageBox { + background-color: #f0f0f0; + border-style: solid; + border-radius: 4px; + border-width: thin; + border-color: #A9A9A9; + padding: 10px 20px 0px 15px; + margin: 0 0 12px 0; +} +.messageText { + font-style: italic; +} +.sender { + text-align: right; + color: #808080; } \ No newline at end of file diff --git a/AgentDiscoveries-Frontend/app/src/components/Theme.jsx b/AgentDiscoveries-Frontend/app/src/components/Theme.jsx new file mode 100644 index 00000000..c521b8aa --- /dev/null +++ b/AgentDiscoveries-Frontend/app/src/components/Theme.jsx @@ -0,0 +1,17 @@ +const white = '#FFFFFF'; +const black = '161617'; +const gray = 'F8F8F9'; + +const themeLight = { + background: gray, + body: black +}; + +const themeDark = { + background: black, + body: white +}; + +const theme = mode => (mode === 'dark' ? themeDark : themeLight); + +export default theme; \ No newline at end of file diff --git a/AgentDiscoveries-Frontend/app/src/components/ThemeButton.jsx b/AgentDiscoveries-Frontend/app/src/components/ThemeButton.jsx new file mode 100644 index 00000000..ef60ad6e --- /dev/null +++ b/AgentDiscoveries-Frontend/app/src/components/ThemeButton.jsx @@ -0,0 +1,10 @@ +import * as React from 'react'; + +export default class ThemeButton extends React.Component { + + render(){ + return( ); + } + +} + diff --git a/AgentDiscoveries-Frontend/app/src/components/admin/location-form.jsx b/AgentDiscoveries-Frontend/app/src/components/admin/location-form.jsx index 8dec0c74..dd8ec2ba 100644 --- a/AgentDiscoveries-Frontend/app/src/components/admin/location-form.jsx +++ b/AgentDiscoveries-Frontend/app/src/components/admin/location-form.jsx @@ -118,7 +118,10 @@ export default class LocationForm extends React.Component { } onRegionIdChange(event) { - this.setState({ regionId: parseInt(event.target.value) }); + if(event.target.value < 100000 ){ + const newRegionId = event.target.value === '' ? null : event.target.value; + this.setState({ regionId: newRegionId }); + } } onSubmit(event) { @@ -139,12 +142,19 @@ export default class LocationForm extends React.Component { request .then(() => window.location.hash = '#/admin/locations') - .catch(error => this.setState({ message: { message: error.message, type: 'danger' } })); + .catch(error => {this.handleError(error);}); } loadLocation(id) { apiGet('locations', id) .then(result => this.setState(result)) - .catch(error => this.setState({ message: { message: error.message, type: 'danger' } })); + .catch(error => { + this.handleError(error); + }); } + + handleError(error){ + error.response.json().then(result => { + this.setState({ message: { message: result.message, type: 'danger' } }); + });} } diff --git a/AgentDiscoveries-Frontend/app/src/components/admin/region-form.jsx b/AgentDiscoveries-Frontend/app/src/components/admin/region-form.jsx index e833347c..2d7566fc 100644 --- a/AgentDiscoveries-Frontend/app/src/components/admin/region-form.jsx +++ b/AgentDiscoveries-Frontend/app/src/components/admin/region-form.jsx @@ -61,12 +61,18 @@ export default class RegionForm extends React.Component { request .then(() => window.location.hash = '#/admin/regions') - .catch(error => this.setState({ message: { message: error.message, type: 'danger' } })); + .catch(error => this.handleError(error)); } loadRegion(id) { apiGet('regions', id) .then(result => this.setState({ name: result.name, locations: result.locations.join(', ') })) - .catch(error => this.setState({ message: { message: error.message, type: 'danger' } })); + .catch(error => this.handleError(error)); } + + handleError(error){ + error.response.json().then(result => { + this.setState({ message: { message: result.message, type: 'danger' } }); + });} + } diff --git a/AgentDiscoveries-Frontend/app/src/components/admin/user-form.jsx b/AgentDiscoveries-Frontend/app/src/components/admin/user-form.jsx index b185eecf..28997751 100644 --- a/AgentDiscoveries-Frontend/app/src/components/admin/user-form.jsx +++ b/AgentDiscoveries-Frontend/app/src/components/admin/user-form.jsx @@ -123,7 +123,7 @@ export default class UserForm extends React.Component { : apiPost('users', user); }) .then(() => window.location.hash = '#/admin/users') - .catch(error => this.setState({ message: { message: error.message, type: 'danger' } })); + .catch(error => this.handleError(error)); } loadUser(id) { @@ -134,12 +134,17 @@ export default class UserForm extends React.Component { this.loadAgent(result.agentId); } }) - .catch(error => this.setState({ message: { message: error.message, type: 'danger' } })); + .catch(error => this.handleError(error)); } loadAgent(id) { apiGet('agents', id) .then(result => this.setState({ isAgent: true, agent: result })) - .catch(error => this.setState({ message: { message: error.message, type: 'danger' } })); + .catch(error => this.handleError(error)); } + + handleError(error){ + error.response.json().then(result => { + this.setState({ message: { message: result.message, type: 'danger' } }); + });} } diff --git a/AgentDiscoveries-Frontend/app/src/components/app.jsx b/AgentDiscoveries-Frontend/app/src/components/app.jsx index 760d069f..07bcc92d 100644 --- a/AgentDiscoveries-Frontend/app/src/components/app.jsx +++ b/AgentDiscoveries-Frontend/app/src/components/app.jsx @@ -7,7 +7,7 @@ import Page from './page'; import Profile from './profile/profile'; import EditProfilePicture from './profile/edit-profile-picture'; import EditProfileCallSign from './profile/edit-profile-callsign'; - +import Forum from './submit-forms/submit-message'; import LocationReportSearch from './search-forms/search-location-reports'; import RegionSummarySearch from './search-forms/search-region-summaries'; import LocationReportSubmit from './submit-forms/submit-location-report'; @@ -17,47 +17,93 @@ import TodaysCodePage from './todays-code-page'; import LocationForm from './admin/location-form'; import RegionForm from './admin/region-form'; import UserForm from './admin/user-form'; +import MostWanted from './mostwanted'; import Error from './error'; +import MyReportsPage from './myReports'; +import { Button } from 'react-bootstrap'; import {apiGet} from './utilities/request-helper.js'; export default class App extends React.Component { + + constructor(props){ + super(props); + this.state = { + class: '', + }; + + this.switch = this.switch.bind(this); + } + + switch() { + var mode = localStorage.getItem('mode'); + if (mode === 'lightMode') { + localStorage.setItem('mode', 'nightMode'); + + this.setState({ class: 'nightMode' }); + this.setState({ class: 'nightMode'}); + + } else { + localStorage.setItem('mode', 'lightMode'); + this.setState({ class: 'lightMode'}); + } + } + + componentDidMount() { + var mode = localStorage.getItem('mode'); + if(mode){ + this.setState({ class: mode }); + } else { + this.setState({ class: 'lightMode' }); + } + } + render() { apiGet('/checktoken'); return ( - - - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - } /> - } /> - } /> - - } /> - } /> - } /> - - } /> - } /> - } /> - - } /> - } /> - } /> - } /> - - }/> - }/> - - - +
+ + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + } /> + } /> + } /> + + } /> + } /> + } /> + + } /> + } /> + } /> + + } /> + } /> + } /> + } /> + } /> + + } /> + }/> + }/> + + + +
+

___________________________________________________________________________________

+ +
+
); } } diff --git a/AgentDiscoveries-Frontend/app/src/components/home.jsx b/AgentDiscoveries-Frontend/app/src/components/home.jsx index 4f63f915..2fda686c 100644 --- a/AgentDiscoveries-Frontend/app/src/components/home.jsx +++ b/AgentDiscoveries-Frontend/app/src/components/home.jsx @@ -63,4 +63,4 @@ export default class Home extends React.Component { ); } -} +} \ No newline at end of file diff --git a/AgentDiscoveries-Frontend/app/src/components/mostwanted.jsx b/AgentDiscoveries-Frontend/app/src/components/mostwanted.jsx new file mode 100644 index 00000000..281d14c5 --- /dev/null +++ b/AgentDiscoveries-Frontend/app/src/components/mostwanted.jsx @@ -0,0 +1,101 @@ +import * as React from 'react'; +import {apiGet} from './utilities/request-helper'; +import {errorLogAndRedirect} from './error'; +import logo from '../../static/wanted.png'; +import {Link} from 'react-router-dom'; +import mead from '../../static/Jack.jpg'; + +export default class MostWanted extends React.Component { + constructor(props) { + super(props); + this.state = { + mostwanted: [], + showCustom: false + }; + this.loadCustomWantedPosters = this.loadCustomWantedPosters.bind(this); + } + + getMostWanted() { + apiGet('mostwanted') + .then(mostwanted => { + this.setState({ mostwanted: mostwanted }); + }) + .catch(errorLogAndRedirect); + } + + componentWillMount() { + this.getMostWanted(); + } + + loadCustomWantedPosters() { + + this.setState( { + showCustom: true, + mostwanted: [ + { + title: 'The Smoker', + images: [ {original: dan}], + description: 'Being good at Frontend programming, vapes huge clouds' + }, + { + title: 'The Ball Kicker', + images: [ {original: luan} ], + description: 'Known for foosball enthusiasm' + }, + { + title: 'Mdoggy-Dog', + images: [ {original: mdog} ], + description: 'Last seen putting Rich in his car...' + }, + { + title: 'The Heartbreaker', + images: [ {original: mike} ], + description: 'Recently known as "The Heartbroken"' + }, + { + title: 'Known alias "Can of Coke, Thor, Viking"', + images: [ {original: sandris} ], + description: 'Proud of his Li... Lat... heritage' + }, + { + title: 'The Duck', + images: [ {original: henry} ], + description: 'Using ducks in criminal activity' + }, + { + title: 'The Lint Master', + images: [ {original: mead} ], + description: 'Ring leader !! ' + }, + ], + }); + } + + + render() { + if(this.state.mostwanted.length === 0){ + return
; + } + + return ( +
+ +
+ { + this.state.mostwanted.map(wantedIndividual => { + return ( +
+ +

{wantedIndividual.title}

+ +

{wantedIndividual.description}

+ +
+ ); + }) + } +
+
+ ); + } +} diff --git a/AgentDiscoveries-Frontend/app/src/components/myReports.jsx b/AgentDiscoveries-Frontend/app/src/components/myReports.jsx new file mode 100644 index 00000000..54f03f37 --- /dev/null +++ b/AgentDiscoveries-Frontend/app/src/components/myReports.jsx @@ -0,0 +1,44 @@ +import * as React from 'react'; +import SearchResult from './search-forms/search-result'; +import QueryString from 'query-string'; +import {apiGet} from './utilities/request-helper'; + +export default class myReports extends React.Component { + + constructor(props) { + super(props); + + this.state = { + results: [], + agentId: window.localStorage.getItem('AgentId') + }; + } + + componentDidMount() { + this.loadEntities(); + } + + render() { + + return ( + + ); + + } + + loadEntities(){ + + const param = { + agentId: this.state.agentId + }; + + const url = 'reports/locationstatuses?' + QueryString.stringify(param); + + apiGet(url) + .then(results => this.setState({ results: results, message: {} })) + .catch(error => this.setState({ message: { message: error.message, type: 'danger' } })); + + } + +} + diff --git a/AgentDiscoveries-Frontend/app/src/components/nav.jsx b/AgentDiscoveries-Frontend/app/src/components/nav.jsx index eb6e0cb4..1bc5303a 100644 --- a/AgentDiscoveries-Frontend/app/src/components/nav.jsx +++ b/AgentDiscoveries-Frontend/app/src/components/nav.jsx @@ -61,6 +61,9 @@ export default class NavigationBar extends React.Component { {this.state.isAgent ? this.renderAgentOptions() : null} {this.state.isAdmin ? this.renderAdminOptions() : null} ); } diff --git a/AgentDiscoveries-Frontend/app/src/components/page.jsx b/AgentDiscoveries-Frontend/app/src/components/page.jsx index 357b12ce..7abb3cc8 100644 --- a/AgentDiscoveries-Frontend/app/src/components/page.jsx +++ b/AgentDiscoveries-Frontend/app/src/components/page.jsx @@ -3,6 +3,7 @@ import * as React from 'react'; import NavigationBar from './nav'; export default class Page extends React.Component { + render() { return ( diff --git a/AgentDiscoveries-Frontend/app/src/components/search-forms/search-result.jsx b/AgentDiscoveries-Frontend/app/src/components/search-forms/search-result.jsx index 24d2649f..98a0274e 100644 --- a/AgentDiscoveries-Frontend/app/src/components/search-forms/search-result.jsx +++ b/AgentDiscoveries-Frontend/app/src/components/search-forms/search-result.jsx @@ -1,33 +1,74 @@ import * as React from 'react'; import {Panel} from 'react-bootstrap'; +import moment from 'moment-timezone'; +import {Button} from 'react-bootstrap'; +import Link from 'react-router-dom/Link'; export default class SearchResult extends React.Component { + render() { + return (
{this.getResultsHeader(this.props.results)} {this.renderResults(this.props.results)} + {this.renderEditButton()}
); } renderResults(results) { + return results.map((result, index) => { - return ( - - Result - {this.renderResultBody(result)} - - ); + + const url = '/submit/location/edit/' + results[index].reportId; + + if (this.props.isPersonal){ + return ( + + Result + {this.renderResultBody(result)} + + + + + + + ); + } else { + return ( + + Result + {this.renderResultBody(result)} + + ); + } }); } renderResultBody(result) { - return Object.keys(result).map(key => { + return Object.keys(result).map((key) => { + + if(this.isADate(result[key])){ + const time = moment(result[key]).format('H:mm:ss'); + + return ( +
+

{`${key}: ${result[key]}`}

+

Reporter's Local Time: {time}

+
+ ); + } + return

{`${key}: ${result[key]}`}

; }); } + isADate(date){ + date = moment(date); + return (date.isValid() && (date.year() !== 1970)); + } + getResultsHeader(results) { return results.length > 0 ? (results.length === 1 @@ -35,4 +76,7 @@ export default class SearchResult extends React.Component { :

{`${results.length} results`}

) : ''; } + + renderEditButton() { + } } diff --git a/AgentDiscoveries-Frontend/app/src/components/submit-forms/forum-messages.jsx b/AgentDiscoveries-Frontend/app/src/components/submit-forms/forum-messages.jsx new file mode 100644 index 00000000..18cea571 --- /dev/null +++ b/AgentDiscoveries-Frontend/app/src/components/submit-forms/forum-messages.jsx @@ -0,0 +1,19 @@ +import * as React from 'react'; +// import {currentUserId} from './utilities/user-helper'; + +export default class ForumMessages extends React.Component { + + render() { + + return ( + +
+ { + this.props.forum.map(message =>

{message.Message}

by {message.user.username}

) + } +
+
+ ); + } +} \ No newline at end of file diff --git a/AgentDiscoveries-Frontend/app/src/components/submit-forms/submit-location-report.jsx b/AgentDiscoveries-Frontend/app/src/components/submit-forms/submit-location-report.jsx index e8fadeba..1622bec7 100644 --- a/AgentDiscoveries-Frontend/app/src/components/submit-forms/submit-location-report.jsx +++ b/AgentDiscoveries-Frontend/app/src/components/submit-forms/submit-location-report.jsx @@ -1,6 +1,6 @@ import * as React from 'react'; import {Button, Checkbox, ControlLabel, Form, FormControl, FormGroup} from 'react-bootstrap'; -import {apiGet, apiPost} from '../utilities/request-helper'; +import {apiGet, apiPost, apiPut} from '../utilities/request-helper'; import {Messages} from '../message'; @@ -10,7 +10,6 @@ export default class LocationReportSubmit extends React.Component { this.state = { locations: [], - locationId: '', status: '', reportTitle: '', @@ -26,6 +25,11 @@ export default class LocationReportSubmit extends React.Component { this.onReportTitleChange = this.onReportTitleChange.bind(this); this.onExternalChange = this.onExternalChange.bind(this); this.onSubmit = this.onSubmit.bind(this); + + if (this.props.id) { + this.loadLocation(this.props.id); + } + } componentWillMount() { @@ -56,7 +60,7 @@ export default class LocationReportSubmit extends React.Component { Status @@ -64,7 +68,7 @@ export default class LocationReportSubmit extends React.Component { Title this.addMessage('Report submitted', 'info')) - .catch(() => this.addMessage('Error submitting report, please try again later', 'danger')); - - if (this.state.sendExternal) { - apiPost('external/reports', body) - .then(() => this.addMessage('Report submitted to external partner', 'info')) - .catch(() => this.addMessage('Error submitting report externally, please try again later', 'danger')); - } + const request = this.props.id + ? apiPut('reports/locationstatuses', body, this.props.id) + : apiPost('reports/locationstatuses', body); + request + .then(window.location.hash = '#/myReports') + .catch(error => console.log(error)); } addMessage(message, type) { @@ -143,4 +146,12 @@ export default class LocationReportSubmit extends React.Component { return { messages: [...oldState.messages, { message: message, type: type }] }; }); } + + loadLocation(id) { + apiGet('reports/locationstatuses', id) + .then(result => this.setState(result)) + .catch(error => { + this.handleError(error); + }); + } } diff --git a/AgentDiscoveries-Frontend/app/src/components/submit-forms/submit-message.jsx b/AgentDiscoveries-Frontend/app/src/components/submit-forms/submit-message.jsx new file mode 100644 index 00000000..3d594e8a --- /dev/null +++ b/AgentDiscoveries-Frontend/app/src/components/submit-forms/submit-message.jsx @@ -0,0 +1,92 @@ +import * as React from 'react'; +import {Button, ControlLabel, Form, FormControl, FormGroup} from 'react-bootstrap'; +import {apiGet, apiPost} from '../utilities/request-helper'; +import {Messages} from '../message'; +import ForumMessages from './forum-messages'; + +export default class Forum extends React.Component { + constructor(props) { + super(props); + + this.state = { + posts: [], + + Message: '', + + messages: [] + + }; + + this.onMessageChange = this.onMessageChange.bind(this); + this.onPost = this.onPost.bind(this); + + } + + componentWillMount() { + this.getLatestMessages(); + } + + getLatestMessages(){ + apiGet('forum') + .then(results => this.setState({ posts: results.reverse() })) + .catch(() => this.addMessage('Error fetching messages, please try again later', 'danger')); + } + + render() { + return ( +
+
+

Agent Forum

+ + + + + + + Message Board + + + + + +
+ ); + } + + onMessageChange(event) { + this.setState({ Message: event.target.value }); + } + + onPost(event) { + event.preventDefault(); + + //console.log(window.localStorage.getItem("")) + + const body = { + Message: this.state.Message + //Username: window.localStorage.getItem("Username") + }; + + apiPost('forum/add', body) + .then(() => this.addMessage('Message submitted', 'info')) + .catch(() => this.addMessage('Error submitting message, please try again later', 'danger')) + .finally(() => { + this.getLatestMessages(); + this.setState({Message: ''}); + }); + + } + + addMessage(message, type) { + this.setState(oldState => { + return { messages: [{ message: message, type: type }] }; + }); + } +} diff --git a/AgentDiscoveries-Frontend/app/src/components/utilities/user-helper.js b/AgentDiscoveries-Frontend/app/src/components/utilities/user-helper.js index 058c34e6..3508cfdf 100644 --- a/AgentDiscoveries-Frontend/app/src/components/utilities/user-helper.js +++ b/AgentDiscoveries-Frontend/app/src/components/utilities/user-helper.js @@ -5,6 +5,7 @@ export function storeUserInfo(userInfo) { window.localStorage.setItem('UserId', userInfo.userId); window.localStorage.setItem('Admin', userInfo.isAdmin ? 'true' : 'false'); window.localStorage.setItem('AgentId', userInfo.agentId); + //window.localStorage.setItem('Username', userInfo.username); updateListeners(); } @@ -14,6 +15,7 @@ export function clearUserInfo() { window.localStorage.clear('UserId'); window.localStorage.clear('Admin'); window.localStorage.clear('AgentId'); + //window.localStorage.clear("Username"); updateListeners(); } diff --git a/AgentDiscoveries-Frontend/app/static/Jack.jpg b/AgentDiscoveries-Frontend/app/static/Jack.jpg new file mode 100644 index 00000000..6463f40c Binary files /dev/null and b/AgentDiscoveries-Frontend/app/static/Jack.jpg differ diff --git a/AgentDiscoveries-Frontend/app/static/wanted.png b/AgentDiscoveries-Frontend/app/static/wanted.png new file mode 100644 index 00000000..ab40b292 Binary files /dev/null and b/AgentDiscoveries-Frontend/app/static/wanted.png differ diff --git a/demo-pull-request-doc.txt b/demo-pull-request-doc.txt index 454d876d..a21f8c9e 100644 --- a/demo-pull-request-doc.txt +++ b/demo-pull-request-doc.txt @@ -3,4 +3,4 @@ Richard Tree Cami Mummery Jacob Craighead Zsolt Tekeres -Zee FishKing +Zee