Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clot.reject image #222

Merged
merged 12 commits into from
Mar 31, 2024
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.codeforcommunity.api;

import com.codeforcommunity.auth.JWTData;
import com.codeforcommunity.dto.site.*;
import com.codeforcommunity.dto.site.AddSiteRequest;
import com.codeforcommunity.dto.site.AddSitesRequest;
import com.codeforcommunity.dto.site.AdoptedSitesResponse;
Expand Down Expand Up @@ -109,6 +110,9 @@ void uploadSiteImage(
/** Edits the site entry with the given entryId */
void editSiteEntry(JWTData userData, int entryId, UpdateSiteRequest editSiteEntryRequest);

/** Rejects the site image (deletes and optionally sends an email with a rejection reason
* to the uploader) with the given imageId */
void rejectSiteImage(JWTData userData, int imageId, RejectImageRequest rejectImageRequest);
List<SiteEntryImage> getUnapprovedImages(JWTData userData);

/** Allows Admin users to approve uploaded site images of the given ID */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.codeforcommunity.dto.site;

import com.codeforcommunity.dto.ApiDto;
import java.util.ArrayList;
import java.util.List;

public class RejectImageRequest extends ApiDto {
private String rejectionReason;

public RejectImageRequest(String rejectionReason) {
this.rejectionReason = rejectionReason;
}

private RejectImageRequest() {}

public String getRejectionReason() {
return rejectionReason;
}

public void setNewReason(String newEmail) {
this.rejectionReason = rejectionReason;
}

@Override
public List<String> validateFields(String fieldPrefix) {
List<String> fields = new ArrayList<>();
return fields;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.codeforcommunity.dto.site.ParentAdoptSiteRequest;
import com.codeforcommunity.dto.site.ParentRecordStewardshipRequest;
import com.codeforcommunity.dto.site.RecordStewardshipRequest;
import com.codeforcommunity.dto.site.RejectImageRequest;
import com.codeforcommunity.dto.site.SiteEntryImage;
import com.codeforcommunity.dto.site.UpdateSiteRequest;
import com.codeforcommunity.dto.site.UploadSiteImageRequest;
Expand Down Expand Up @@ -64,6 +65,7 @@ public Router initializeRouter(Vertx vertx) {
registerDeleteSiteImage(router);
registerFilterSites(router);
registerEditSiteEntry(router);
registerRejectSiteImage(router);
registerApproveSiteImage(router);
registerGetUnapprovedImages(router);
return router;
Expand Down Expand Up @@ -404,6 +406,23 @@ private void handleEditSiteEntry(RoutingContext ctx) {
end(ctx.response(), 200);
}

private void registerRejectSiteImage(Router router) {
Route rejectImage = router.delete("/reject_image/:image_id");
rejectImage.handler(this::handleRejectSiteImage);
}

private void handleRejectSiteImage(RoutingContext ctx) {
JWTData userData = ctx.get("jwt_data");
int imageId = RestFunctions.getRequestParameterAsInt(ctx.request(), "image_id");

RejectImageRequest rejectImageRequest =
RestFunctions.getJsonBodyAsClass(ctx, RejectImageRequest.class);

processor.rejectSiteImage(userData, imageId, rejectImageRequest);

end(ctx.response(), 200);
}

private void registerApproveSiteImage(Router router) {
Route approveSiteImage = router.put("/approve_image/:image_id");
approveSiteImage.handler(this::handleApproveSiteImage);
Expand Down
392 changes: 392 additions & 0 deletions common/src/main/resources/emails/RejectImageEmail.html

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ private void initializeServer() {
ILeaderboardProcessor leaderboardProc = new LeaderboardProcessorImpl(this.db);
IMapProcessor mapProc = new MapProcessorImpl(this.db);
ITeamsProcessor teamsProc = new TeamsProcessorImpl(this.db);
IProtectedSiteProcessor protectedSiteProc = new ProtectedSiteProcessorImpl(this.db);
IProtectedSiteProcessor protectedSiteProc = new ProtectedSiteProcessorImpl(this.db, emailer);
ISiteProcessor siteProc = new SiteProcessorImpl(this.db);
IProtectedReportProcessor protectedReportProc = new ProtectedReportProcessorImpl(this.db);
IReportProcessor reportProc = new ReportProcessorImpl(this.db);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

import com.codeforcommunity.api.IProtectedSiteProcessor;
import com.codeforcommunity.auth.JWTData;
import com.codeforcommunity.dataaccess.AuthDatabaseOperations;
import com.codeforcommunity.dto.site.*;
import com.codeforcommunity.dto.site.AddSiteRequest;
import com.codeforcommunity.dto.site.AddSitesRequest;
import com.codeforcommunity.dto.site.AdoptedSitesResponse;
Expand All @@ -43,6 +45,7 @@
import com.codeforcommunity.exceptions.ResourceDoesNotExistException;
import com.codeforcommunity.exceptions.WrongAdoptionStatusException;
import com.codeforcommunity.logger.SLogger;
import com.codeforcommunity.requester.Emailer;
import com.codeforcommunity.requester.S3Requester;
import com.fasterxml.jackson.databind.MappingIterator;
import com.fasterxml.jackson.dataformat.csv.CsvMapper;
Expand All @@ -52,7 +55,9 @@
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.jooq.Condition;
import org.jooq.DSLContext;
Expand All @@ -65,19 +70,21 @@
import org.jooq.generated.tables.records.SitesRecord;
import org.jooq.generated.tables.records.StewardshipRecord;
import org.jooq.generated.tables.records.UsersRecord;
import org.jooq.generated.tables.pojos.Users;

public class ProtectedSiteProcessorImpl extends AbstractProcessor
implements IProtectedSiteProcessor {

private final DSLContext db;

private final Emailer emailer;
private final SLogger logger = new SLogger(ProtectedSiteProcessorImpl.class);

private static final int MAX_SUBMITTED_SITE_IMAGES = 20;
private static final int UPLOAD_SITE_IMAGE_SLACK_FREQ = 2;

public ProtectedSiteProcessorImpl(DSLContext db) {
public ProtectedSiteProcessorImpl(DSLContext db, Emailer emailer) {
this.db = db;
this.emailer = emailer;
}

/**
Expand Down Expand Up @@ -663,7 +670,7 @@ private SiteEntriesRecord latestSiteEntry(int siteId) {
@Override
public void uploadSiteImage(
JWTData userData, int siteEntryId, UploadSiteImageRequest uploadSiteImageRequest) {
checkSiteExists(siteEntryId);
checkEntryExists(siteEntryId);
checkCanUploadImage(userData);

Integer maxImageId =
Expand Down Expand Up @@ -915,4 +922,24 @@ public void approveSiteImage(JWTData userData, int imageID) {
imageRecord.setApprovalStatus(ImageApprovalStatus.APPROVED.getApprovalStatus());
imageRecord.store();
}

@Override
public void rejectSiteImage(JWTData userData, int imageId, RejectImageRequest rejectImageRequest) {
checkImageExists(imageId);
assertAdminOrSuperAdmin(userData.getPrivilegeLevel());

if (rejectImageRequest.getRejectionReason() != null) {
int uploaderId = db.select(SITE_IMAGES.UPLOADER_ID)
.from(SITE_IMAGES)
.where(SITE_IMAGES.ID.eq(imageId))
.fetchOne(0, int.class);

UsersRecord user = db.selectFrom(USERS).where(USERS.ID.eq(uploaderId)).fetchOne();
String userEmail = user.getEmail();
String userFullName =
AuthDatabaseOperations.getFullName(user.into(Users.class));
emailer.sendRejectImageEmail(userEmail, userFullName, rejectImageRequest.getRejectionReason());
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⛏️ I think we should send a rejection email regardless of if a reason was added just so to keep users updated - we could probably just add a default rejection message. Also, can we check if the reason is an empty string as well so we don't accidentally send no message?
Sorry if the API spec wasn't clear about this - that's totally my bad

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to notify people that their image was rejected/deleted even if a reason isn't provided?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess yes (#222 (comment))

deleteSiteImage(userData, imageId);
}
}
13 changes: 13 additions & 0 deletions service/src/main/java/com/codeforcommunity/requester/Emailer.java
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,19 @@ public void sendInviteTeamEmail(String sendToEmail, String sendToName, String te
// TODO implement this
}

public void sendRejectImageEmail(String sendToEmail, String sendToName, String reason) {
String filePath = "/emails/RejectImageEmail.html";

Map<String, String> templateValues = new HashMap<>();
templateValues.put("reason", reason);
Optional<String> emailBody = emailOperations.getTemplateString(filePath, templateValues);

emailBody.ifPresent(
s ->
emailOperations.sendEmailToOneRecipient(
sendToName, sendToEmail, subjectEmailChange, s));
}

public void sendArbitraryEmail(HashSet<String> sendToEmails, String subject, String body,
List<AttachmentResource> attachments) {
String filePath = "/emails/Email.html";
Expand Down
Loading