Skip to content

Commit

Permalink
♻️ pull data classes into separate package
Browse files Browse the repository at this point in the history
  • Loading branch information
ebullient committed Dec 19, 2024
1 parent 64c6b79 commit b016d84
Show file tree
Hide file tree
Showing 19 changed files with 405 additions and 397 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;

import org.commonhaus.automation.admin.api.CommonhausUser.ForwardEmail;
import org.commonhaus.automation.admin.data.ApiResponse;
import org.commonhaus.automation.admin.data.CommonhausUser;
import org.commonhaus.automation.admin.data.CommonhausUserData.ForwardEmail;
import org.commonhaus.automation.admin.forwardemail.Alias;
import org.commonhaus.automation.admin.forwardemail.AliasKey;
import org.commonhaus.automation.admin.forwardemail.ForwardEmailService;
Expand Down Expand Up @@ -87,7 +89,7 @@ public Response updateAliases(Map<String, Set<String>> aliases) {
// API CALL: set/update alias mappings
Map<AliasKey, Alias> aliasMap = emailService.postAliases(sanitized, session.name());

if (!emailConfig.hasDefaultAlias
if (!emailConfig.hasDefaultAlias()
&& aliasMap.keySet().stream().anyMatch(k -> emailService.isDefaultAlias(session.login(), k))) {
user = updateHasDefaultFlag(user);
}
Expand Down Expand Up @@ -146,7 +148,7 @@ protected CommonhausUser getUser() {
CommonhausUser updateHasDefaultFlag(CommonhausUser user) {
CommonhausUser result = datastore.setCommonhausUser(new UpdateEvent(user,
(c, u) -> {
u.services().forwardEmail().hasDefaultAlias = true;
u.services().forwardEmail().enableDefaultAlias();
},
"Fix forward email service active flag",
false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.regex.Pattern;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

import org.commonhaus.automation.admin.api.CommonhausUser.MembershipApplication;
import org.commonhaus.automation.admin.api.MembershipApplicationData.ApplicationPost;
import org.commonhaus.automation.admin.api.MembershipApplicationData.Feedback;
import org.commonhaus.automation.admin.data.CommonhausUser;
import org.commonhaus.automation.admin.data.MemberStatus;
import org.commonhaus.automation.admin.data.MembershipApplication;
import org.commonhaus.automation.admin.github.AppContextService;
import org.commonhaus.automation.admin.github.CommonhausDatastore;
import org.commonhaus.automation.admin.github.CommonhausDatastore.UpdateEvent;
Expand All @@ -22,13 +23,26 @@
import org.kohsuke.github.GHIssue;
import org.kohsuke.github.GHUser;

import com.fasterxml.jackson.annotation.JsonIgnore;

import io.quarkus.logging.Log;
import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.TemplateInstance;
import io.quarkus.runtime.annotations.RegisterForReflection;

@ApplicationScoped
public class MemberApplicationProcess {

public static final String ACCEPTED = "application/accepted";
public static final String DECLINED = "application/declined";
public static final String NEW = "application/new";
static final Pattern CONTRIBUTIONS = Pattern.compile(
"([\\s\\S]*?<!--CONTRIBUTION::-->)([\\s\\S]*?)(<!--::CONTRIBUTION-->[\\s\\S]*?)", Pattern.CASE_INSENSITIVE);
static final Pattern NOTES = Pattern.compile("([\\s\\S]*?<!--NOTES::-->)([\\s\\S]*?)(<!--::NOTES-->[\\s\\S]*?)",
Pattern.CASE_INSENSITIVE);
static final Pattern NOTIFICATION = Pattern.compile("<!-- notify::(\\S+?) -->");
static final Pattern STRIP_COMMENTS = Pattern.compile("<!--:?:?(CONTRIBUTION|NOTES):?:?-->", Pattern.CASE_INSENSITIVE);

@CheckedTemplate
static class Templates {
public static native TemplateInstance applicationAccepted();
Expand All @@ -49,12 +63,11 @@ static class Templates {
*
* @param dqc QueryContext for the datastore repository
* @param issue
* @param item
*/
public void handleApplicationComment(DatastoreQueryContext dqc, DataCommonItem issue, DataCommonComment comment) {
if (MembershipApplicationData.isUserFeedback(comment.body)) {
if (isUserFeedback(comment.body)) {
Log.debugf("[%s] updateApplicationComments: #%s - user feedback", dqc.getLogId(), issue.number);
String notificationEmail = MembershipApplicationData.getNotificationEmail(issue);
String notificationEmail = getNotificationEmail(issue);
if (isValid(notificationEmail)) {
String body = Templates.applicationUpdated(comment.body.replaceAll("\\s*::response::\\s*", "")).render();
String htmlBody = MarkdownConverter.toHtml(body);
Expand All @@ -78,7 +91,7 @@ public void handleApplicationComment(DatastoreQueryContext dqc, DataCommonItem i
*/
public void handleApplicationLabelAdded(DatastoreQueryContext dqc, GHIssue issue, DataCommonItem item, DataLabel label)
throws Throwable {
String login = MembershipApplicationData.getLogin(item);
String login = getLogin(item);
GHUser applicant = dqc.getUser(login);

String teamFullName = ctx.getTeamForRole(CommonhausUser.MEMBER_ROLE);
Expand All @@ -93,24 +106,24 @@ public void handleApplicationLabelAdded(DatastoreQueryContext dqc, GHIssue issue
throw new IllegalStateException("Label added to an application for an unknown user " + login);
}

MembershipApplicationData applicationData = new MembershipApplicationData(login, item);
MemberApplicationIssue applicationData = new MemberApplicationIssue(login, item);
// We haven't approved/declined this member yet: we need a valid application
if (user.emptyMember() && !applicationData.isValid()) {
if (user.isMemberUndefined() && !applicationData.isValid()) {
throw new IllegalStateException(
"Unable to find valid application data for login %s and issue %s (%s)"
.formatted(login, item.id, item.title));
}

String notificationEmail = MembershipApplicationData.getNotificationEmail(item);
if (MembershipApplicationData.isAccepted(label)) {
if (user.emptyMember()) {
String notificationEmail = getNotificationEmail(item);
if (isAccepted(label)) {
if (user.isMemberUndefined()) {
datastore.setCommonhausUser(new UpdateEvent(user,
(c, u) -> {
if (u.status().updateFromPending()) {
u.status(MemberStatus.ACTIVE);
u.setStatus(MemberStatus.ACTIVE);
}
u.isMember = true;
u.application = null;
u.setIsMember(true);
u.setApplication(null);
},
"Membership application accepted",
true,
Expand All @@ -126,14 +139,14 @@ && isValid(notificationEmail)) {
"Welcome to the Commonhaus Foundation!",
body, htmlBody, new String[] { notificationEmail });
}
} else if (user.isMember == null && MembershipApplicationData.isDeclined(label)) {
} else if (user.isMemberUndefined() && isDeclined(label)) {
datastore.setCommonhausUser(new UpdateEvent(user,
(c, u) -> {
if (u.status().updateFromPending()) {
u.status(MemberStatus.DECLINED);
u.application = null;
u.setStatus(MemberStatus.DECLINED);
u.setApplication(null);
}
u.isMember = false;
u.setIsMember(false);
},
"Membership application declined",
true,
Expand All @@ -154,11 +167,11 @@ && isValid(notificationEmail)) {

if (isValid(notificationEmail)) {
// Try to remove the notification email from the issue body
String updated = MembershipApplicationData.NOTIFICATION.matcher(item.body).replaceAll("");
String updated = NOTIFICATION.matcher(item.body).replaceAll("");
dqc.updateItemDescription(EventType.issue, item.id, updated, DataCommonItem.ISSUE_FIELDS);
}
dqc.closeIssue(issue);
dqc.removeLabels(item.id, List.of(MembershipApplicationData.NEW));
dqc.removeLabels(item.id, List.of(NEW));
}

protected boolean isValid(String notificationEmail) {
Expand All @@ -176,37 +189,37 @@ protected boolean isValid(String notificationEmail) {
* @return updated ApplicationData object or null on error (see QueryContext for
* errors)
*/
public MembershipApplicationData userUpdateApplicationIssue(
public MemberApplicationIssue userUpdateApplicationIssue(
MemberSession session,
DatastoreQueryContext dqc,
MembershipApplicationData applicationData,
MemberApplicationIssue applicationData,
ApplicationPost applicationPost,
String notificationEmail) {

String content = MembershipApplicationData.issueContent(session, applicationPost, notificationEmail);
Collection<DataLabel> labels = dqc.findLabels(List.of(MembershipApplicationData.NEW));
String content = issueContent(session, applicationPost, notificationEmail);
Collection<DataLabel> labels = dqc.findLabels(List.of(NEW));
MembershipApplication application = applicationData == null ? null : applicationData.application;

DataCommonItem item = application == null
? dqc.createItem(EventType.issue,
MembershipApplicationData.createTitle(session),
createTitle(session),
content,
labels)
: dqc.updateItemDescription(EventType.issue, application.nodeId(), content, DataCommonItem.ISSUE_FIELDS);

return item == null
? null
: new MembershipApplicationData(session.login(), item);
: new MemberApplicationIssue(session.login(), item);
}

MembershipApplicationData findUserApplication(MemberSession session, String applicationId, boolean withComments)
MemberApplicationIssue findUserApplication(MemberSession session, String applicationId, boolean withComments)
throws Throwable {
DatastoreQueryContext dqc = ctx.getDatastoreContext();
DataCommonItem issue = dqc.getItem(EventType.issue, applicationId);
if (dqc.hasErrors()) {
throw dqc.bundleExceptions();
}
MembershipApplicationData application = new MembershipApplicationData(session.login(), issue);
MemberApplicationIssue application = new MemberApplicationIssue(session.login(), issue);
if (application.isValid() && withComments) {
Feedback feedback = getFeedback(dqc, applicationId, issue.mostRecentEdit());
if (feedback != null) {
Expand All @@ -218,10 +231,149 @@ MembershipApplicationData findUserApplication(MemberSession session, String appl

Feedback getFeedback(DatastoreQueryContext dqc, String nodeId, Date mostRecentEdit) {
List<DataCommonComment> comments = dqc.getComments(nodeId,
x -> MembershipApplicationData.isUserFeedback(x.body) && MembershipApplicationData.isNewer(x, mostRecentEdit));
x -> isUserFeedback(x.body) && isNewer(x, mostRecentEdit));

return (comments == null || comments.isEmpty())
? null
: new Feedback(comments.get(0));
}

public static String createTitle(MemberSession session) {
return "Membership application: %s (%s)".formatted(session.name(), session.login());
}

public static String getLogin(DataCommonItem issue) {
return issue.title.replaceAll("Membership application: .*? \\((.*)\\)", "$1");
}

public static boolean isMemberApplicationEvent(DataCommonItem issue, DataLabel label) {
return issue.title.startsWith("Membership application:")
&& !issue.closed
&& (label == null || ACCEPTED.equals(label.name) || DECLINED.equals(label.name));
}

public static String getNotificationEmail(DataCommonItem issue) {
var matcher = NOTIFICATION.matcher(issue.body);
return matcher.find() ? matcher.group(1) : null;
}

public static boolean isUserFeedback(String body) {
return body.contains("::response::");
}

public static boolean isNew(DataLabel label) {
return NEW.equals(label.name);
}

public static boolean isAccepted(DataLabel label) {
return ACCEPTED.equals(label.name);
}

public static boolean isDeclined(DataLabel label) {
return DECLINED.equals(label.name);
}

public static boolean isComplete(DataLabel label) {
return isAccepted(label) || isDeclined(label);
}

public static boolean isNewer(DataCommonComment x, Date issueMostRecent) {
Log.debugf("isNewer: %s %s", x.mostRecentEdit(), issueMostRecent);
return x.mostRecentEdit().after(issueMostRecent);
}

public static String issueContent(MemberSession session, ApplicationPost applicationPost, String notificationEmail) {
return """
[%s](%s)
> [!TIP]
> - Include "::response::" in your comment to send feedback to the applicant.
> - Add the 'application/accepted' label to accept the application.
> - Add the 'application/declined' label to decline or reject the application.
## Contribution Details
<!--CONTRIBUTION::-->
%s
<!--::CONTRIBUTION-->
## Additional Notes
<!--NOTES::-->
%s
<!--::NOTES-->
%s
<!--vote::marthas approve="+1" ok="eyes" revise="-1" threshold="twothirds"-->
""".formatted(
session.login(),
session.url(),
STRIP_COMMENTS.matcher(applicationPost.contributions()).replaceAll(" "),
STRIP_COMMENTS.matcher(applicationPost.additionalNotes()).replaceAll(" "),
notificationEmail == null ? "" : "<!-- notify::%s -->".formatted(notificationEmail));
}

public record ApplicationPost(
String contributions,
String additionalNotes) {
}

public static class Feedback {
final String htmlContent;
final String date;

public Feedback(DataCommonComment dataCommonComment) {
String content = dataCommonComment.body.replaceAll("::response::", "").trim();

this.htmlContent = MarkdownConverter.toHtml(content);
date = dataCommonComment.mostRecentEdit().toString();
}
}

@RegisterForReflection
public static class MemberApplicationIssue {

transient String title;
transient MembershipApplication application;

String created;
String updated;
String contributions;
String additionalNotes;
Feedback feedback;

public MemberApplicationIssue(String login, DataCommonItem issue) {
this.title = issue == null ? null : issue.title;
if (title == null || !ownerEquals(login)) {
return;
}
application = MembershipApplication.fromDataCommonType(issue);
this.created = issue.createdAt.toString();

if (issue.lastEditedAt != null && !issue.lastEditedAt.equals(issue.createdAt)) {
this.updated = issue.lastEditedAt.toString();
}
this.contributions = extract(CONTRIBUTIONS, issue.body);
this.additionalNotes = extract(NOTES, issue.body);
}

private String extract(Pattern pattern, String body) {
var matcher = pattern.matcher(body);
if (matcher.find()) {
return matcher.group(2).trim();
}
return "";
}

public void setFeedback(Feedback feedback) {
this.feedback = feedback;
}

@JsonIgnore
public boolean isValid() {
return application != null;
}

public boolean ownerEquals(String login) {
return title != null && title.contains("(%s)".formatted(login));
}

}
}
Loading

0 comments on commit b016d84

Please sign in to comment.