Skip to content

Commit 0fe5bc7

Browse files
committed
create invitation and send email with invitation link
1 parent 2b0ff80 commit 0fe5bc7

File tree

10 files changed

+104
-6
lines changed

10 files changed

+104
-6
lines changed

server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/invitation/model/Invitation.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,6 @@ public class Invitation extends HasIdAndAuditing {
2525
*/
2626
private final Set<String> invitedUserIds;
2727

28+
private final Set<String> invitedEmails;
29+
2830
}

server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/organization/service/OrganizationService.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ public interface OrganizationService {
1818
"Here is the link to reset your password: %s<br/>" +
1919
"Please note that the link will expire after 12 hours.<br/><br/></p>";
2020

21+
public static final String INVITATION_EMAIL_TEMPLATE_DEFAULT = "<p>Hi, sir<br/>" +
22+
"Here is the link to your invitation: %s<br/>" +
23+
"Please note that the link will expire after 12 hours.<br/><br/></p>";
24+
2125
@PossibleEmptyMono
2226
Mono<Organization> getOrganizationInEnterpriseMode();
2327

Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
package org.lowcoder.domain.user.service;
22

3+
import org.lowcoder.domain.invitation.model.Invitation;
4+
5+
import java.util.Set;
6+
37
public interface EmailCommunicationService {
48
boolean sendPasswordResetEmail(String to, String token, String message);
9+
boolean sendInvitationEmail(Invitation invitation, Set<String> emails, String message);
510
}

server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/user/service/EmailCommunicationServiceImpl.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
import lombok.RequiredArgsConstructor;
55
import lombok.extern.slf4j.Slf4j;
66
import org.apache.http.client.utils.URLEncodedUtils;
7+
import org.lowcoder.domain.invitation.model.Invitation;
78
import org.lowcoder.sdk.config.CommonConfig;
89
import org.springframework.mail.javamail.JavaMailSender;
910
import org.springframework.mail.javamail.MimeMessageHelper;
1011
import org.springframework.stereotype.Service;
1112

1213
import java.net.URLEncoder;
1314
import java.nio.charset.StandardCharsets;
15+
import java.util.Set;
1416

1517
@RequiredArgsConstructor
1618
@Service
@@ -49,4 +51,34 @@ public boolean sendPasswordResetEmail(String to, String token, String message) {
4951

5052
}
5153

54+
@Override
55+
public boolean sendInvitationEmail(Invitation invitation, Set<String> emails, String message) {
56+
try {
57+
String subject = "You've got invitation to lowcoder";
58+
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
59+
60+
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
61+
62+
mimeMessageHelper.setFrom(config.getNotificationsEmailSender());
63+
String[] to = emails.toArray(new String[0]);
64+
mimeMessageHelper.setTo(to);
65+
mimeMessageHelper.setSubject(subject);
66+
67+
// Construct the message with the token link
68+
String inviteLink = config.getLowcoderPublicUrl() + "/user/auth/invite?code=" + invitation.getId();
69+
String formattedMessage = String.format(message, inviteLink);
70+
mimeMessageHelper.setText(formattedMessage, true); // Set HTML to true to allow links
71+
72+
javaMailSender.send(mimeMessage);
73+
74+
return true;
75+
76+
} catch (Exception e) {
77+
log.error("Failed to send mail, Exception: ", e);
78+
return false;
79+
}
80+
81+
82+
}
83+
5284
}

server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/exception/BizError.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ public enum BizError {
5050
INVITED_ORG_DELETED(500, 5203),
5151
INVITED_APPLICATION_DELETED(500, 5204),
5252
INVITED_USER_NOT_LOGIN(403, 5205),
53+
INVITE_FAILED(403, 5206),
5354

5455
// APPLICATION related, code range 5300 - 5400
5556
QUERY_NOT_FOUND(500, 5300),

server/api-service/lowcoder-sdk/src/main/resources/locale_en.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ DISABLE_AUTH_CONFIG_FORBIDDEN=Can not disable current administrator''s last iden
283283
USER_NOT_EXIST=User not exist.
284284
DUPLICATE_AUTH_CONFIG_ADDITION=Provider auth type already added to organization
285285
EMAIL_PROVIDER_DISABLED=Email provider is disabled.
286+
INVITE_FAILED=Invite failed.
286287
SLUG_DUPLICATE_ENTRY=Slug already exists
287288
SLUG_INVALID=Slug format is invalid
288289
FLOW_ERROR=Flow error message: {0}

server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/InvitationApiService.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@
33
import org.lowcoder.api.usermanagement.view.InvitationVO;
44
import reactor.core.publisher.Mono;
55

6+
import java.util.Set;
7+
68
public interface InvitationApiService {
79
Mono<Boolean> inviteUser(String invitationId);
810

911
Mono<InvitationVO> getInvitationView(String invitationId);
1012

1113
Mono<InvitationVO> create(String orgId);
1214

15+
Mono<InvitationVO> createByEmails(String orgId, Set<String> emails);
16+
1317
public record JoinOrgResult(boolean alreadyInOrg, boolean success) {
1418

1519
}

server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/InvitationApiServiceImpl.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,18 @@
1111
import org.lowcoder.domain.organization.service.OrgMemberService;
1212
import org.lowcoder.domain.organization.service.OrganizationService;
1313
import org.lowcoder.domain.user.model.User;
14+
import org.lowcoder.domain.user.service.EmailCommunicationService;
15+
import org.lowcoder.domain.user.service.EmailCommunicationServiceImpl;
1416
import org.lowcoder.domain.user.service.UserService;
1517
import org.lowcoder.sdk.exception.BizError;
1618
import org.lowcoder.sdk.exception.BizException;
1719
import org.springframework.stereotype.Service;
1820
import reactor.core.publisher.Mono;
1921

22+
import java.util.Arrays;
23+
import java.util.Set;
24+
25+
import static org.lowcoder.domain.organization.service.OrganizationService.INVITATION_EMAIL_TEMPLATE_DEFAULT;
2026
import static org.lowcoder.sdk.exception.BizError.INVITED_ORG_DELETED;
2127
import static org.lowcoder.sdk.exception.BizError.INVITER_NOT_FOUND;
2228
import static org.lowcoder.sdk.util.ExceptionUtils.deferredError;
@@ -33,6 +39,7 @@ public class InvitationApiServiceImpl implements InvitationApiService {
3339
private final OrganizationService organizationService;
3440
private final OrgMemberService orgMemberService;
3541
private final AbstractBizThresholdChecker bizThresholdChecker;
42+
private final EmailCommunicationService emailCommunicationService;
3643

3744
@Override
3845
public Mono<Boolean> inviteUser(String invitationId) {
@@ -111,4 +118,27 @@ public Mono<InvitationVO> create(String orgId) {
111118
});
112119
}
113120

121+
@Override
122+
public Mono<InvitationVO> createByEmails(String orgId, Set<String> emails) {
123+
return sessionUserService.getVisitor()
124+
.zipWith(organizationService.getById(orgId)
125+
.switchIfEmpty(Mono.error(new BizException(BizError.INVALID_ORG_ID, "INVALID_ORG_ID"))))
126+
.flatMap(tuple2 -> {
127+
User user = tuple2.getT1();
128+
Organization org = tuple2.getT2();
129+
Invitation invitation = Invitation
130+
.builder()
131+
.createUserId(user.getId())
132+
.invitedOrganizationId(orgId)
133+
.invitedEmails(emails)
134+
.build();
135+
return invitationService.create(invitation)
136+
.doOnNext(i -> {
137+
boolean ret = emailCommunicationService.sendInvitationEmail(i, emails, INVITATION_EMAIL_TEMPLATE_DEFAULT);
138+
if(!ret) throw new BizException(BizError.INVITE_FAILED, "INVITE_FAILED");
139+
})
140+
.flatMap(i -> Mono.just(InvitationVO.from(i, user, org)));
141+
});
142+
}
143+
114144
}

server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/InvitationController.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.lowcoder.api.usermanagement.view.InvitationVO;
1010
import org.springframework.beans.factory.annotation.Autowired;
1111
import org.springframework.web.bind.annotation.PathVariable;
12+
import org.springframework.web.bind.annotation.RequestBody;
1213
import org.springframework.web.bind.annotation.RequestParam;
1314
import org.springframework.web.bind.annotation.RestController;
1415

@@ -50,4 +51,10 @@ public Mono<ResponseView<?>> inviteUser(@PathVariable String invitationId) {
5051
);
5152
}
5253

54+
@Override
55+
public Mono<ResponseView<?>> inviteUserByEmail(@RequestBody InviteByEmailRequest inviteByEmailRequest) {
56+
return invitationApiService.createByEmails(inviteByEmailRequest.organizationId(), inviteByEmailRequest.emails())
57+
.map(ResponseView::success);
58+
}
59+
5360
}

server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/InvitationEndpoints.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
package org.lowcoder.api.usermanagement;
22

3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import jakarta.annotation.Nullable;
35
import org.lowcoder.api.framework.view.ResponseView;
46
import org.lowcoder.api.usermanagement.view.InvitationVO;
57
import org.lowcoder.infra.constant.NewUrl;
68
import org.lowcoder.infra.constant.Url;
7-
import org.springframework.web.bind.annotation.GetMapping;
8-
import org.springframework.web.bind.annotation.PathVariable;
9-
import org.springframework.web.bind.annotation.PostMapping;
10-
import org.springframework.web.bind.annotation.RequestMapping;
11-
import org.springframework.web.bind.annotation.RequestParam;
12-
import org.springframework.web.bind.annotation.RestController;
9+
import org.springframework.web.bind.annotation.*;
1310

1411
import io.swagger.v3.oas.annotations.Operation;
1512
import reactor.core.publisher.Mono;
1613

14+
import java.util.Map;
15+
import java.util.Set;
16+
1717
@RestController
1818
@RequestMapping(value = {Url.INVITATION_URL, NewUrl.INVITATION_URL})
1919
public interface InvitationEndpoints
@@ -47,4 +47,16 @@ public interface InvitationEndpoints
4747
@GetMapping("/{invitationId}/invite")
4848
public Mono<ResponseView<?>> inviteUser(@PathVariable String invitationId);
4949

50+
@Operation(
51+
tags = TAG_INVITATION_MANAGEMENT,
52+
operationId = "inviteUserByEmail",
53+
summary = "Invite users by their email addresses",
54+
description = "Invite users and send invitation link to user's email addresses"
55+
)
56+
@PostMapping("/email")
57+
public Mono<ResponseView<?>> inviteUserByEmail(@RequestBody InviteByEmailRequest inviteByEmailRequest);
58+
59+
public record InviteByEmailRequest(@JsonProperty("orgId") String organizationId, Set<String> emails) {
60+
}
61+
5062
}

0 commit comments

Comments
 (0)