aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTrumeet <yuuta@yuuta.moe>2021-03-29 18:45:28 -0700
committerTrumeet <yuuta@yuuta.moe>2021-03-29 18:45:28 -0700
commit1e75349b33e478d9e83d322a5d65de1b7b63753c (patch)
treecbd6296b1ef869d31746332ac713d4606ca2ffa5
parentce7703ca44a8fb7a65cbffb7c258d043d7cd94e4 (diff)
downloaddn42peering-1e75349b33e478d9e83d322a5d65de1b7b63753c.tar
dn42peering-1e75349b33e478d9e83d322a5d65de1b7b63753c.tar.gz
dn42peering-1e75349b33e478d9e83d322a5d65de1b7b63753c.tar.bz2
dn42peering-1e75349b33e478d9e83d322a5d65de1b7b63753c.zip
refactor(central): move ASN frontend logic to a separate web API service
-rw-r--r--.idea/compiler.xml7
-rw-r--r--central/build.gradle2
-rw-r--r--central/src/main/java/moe/yuuta/dn42peering/Main.java7
-rw-r--r--central/src/main/java/moe/yuuta/dn42peering/asn/ASNHandler.java144
-rw-r--r--central/src/main/java/moe/yuuta/dn42peering/asn/ASNHttpVerticle.java41
-rw-r--r--central/src/main/java/moe/yuuta/dn42peering/asn/IASNHttpService.java22
-rw-r--r--central/src/main/java/moe/yuuta/dn42peering/asn/IASNHttpServiceImpl.java168
-rw-r--r--central/src/main/java/moe/yuuta/dn42peering/portal/RenderingUtils.java26
8 files changed, 274 insertions, 143 deletions
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 6620e8f..02f9838 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -29,8 +29,14 @@
<processorPath useClasspath="false">
<entry name="$USER_HOME$/.local/share/gradle/caches/modules-2/files-2.1/io.vertx/vertx-sql-client-templates/4.0.3/bbb167fc181ab7e793485da00b370610a6230fdf/vertx-sql-client-templates-4.0.3.jar" />
<entry name="$USER_HOME$/.local/share/gradle/caches/modules-2/files-2.1/io.vertx/vertx-codegen/4.0.3/416429ea05c4c43afcf4104ab488bda10f43ba87/vertx-codegen-4.0.3-processor.jar" />
+ <entry name="$USER_HOME$/.local/share/gradle/caches/modules-2/files-2.1/io.vertx/vertx-web-api-service/4.0.3/15b6af169e6e1987822448f3a330b4a023d701b1/vertx-web-api-service-4.0.3.jar" />
<entry name="$USER_HOME$/.local/share/gradle/caches/modules-2/files-2.1/io.vertx/vertx-service-proxy/4.0.3/df93b096a38ee0120e06943a8f7e3d0627c97418/vertx-service-proxy-4.0.3.jar" />
<entry name="$USER_HOME$/.local/share/gradle/caches/modules-2/files-2.1/io.vertx/vertx-sql-client/4.0.3/2bb64597f96e27a4a431967bea77592286d35462/vertx-sql-client-4.0.3.jar" />
+ <entry name="$USER_HOME$/.local/share/gradle/caches/modules-2/files-2.1/io.vertx/vertx-web-validation/4.0.3/2f819dd0b8082664c339410cff6e40da3ae31536/vertx-web-validation-4.0.3.jar" />
+ <entry name="$USER_HOME$/.local/share/gradle/caches/modules-2/files-2.1/io.vertx/vertx-web/4.0.3/5d0cec868aadac3e098b1ab6409dc2cebae83930/vertx-web-4.0.3.jar" />
+ <entry name="$USER_HOME$/.local/share/gradle/caches/modules-2/files-2.1/io.vertx/vertx-json-schema/4.0.3/fb31dae192398722a07b23892e9f261a7b6c4a85/vertx-json-schema-4.0.3.jar" />
+ <entry name="$USER_HOME$/.local/share/gradle/caches/modules-2/files-2.1/io.vertx/vertx-web-common/4.0.3/274167d895a6170335a5fd7e71d4519b9608d275/vertx-web-common-4.0.3.jar" />
+ <entry name="$USER_HOME$/.local/share/gradle/caches/modules-2/files-2.1/io.vertx/vertx-auth-common/4.0.3/177afb713c00713eff76a6cd83a8090c601af474/vertx-auth-common-4.0.3.jar" />
<entry name="$USER_HOME$/.local/share/gradle/caches/modules-2/files-2.1/io.vertx/vertx-core/4.0.3/40c496ede9a948ec673df8cf5ab227ff853dd061/vertx-core-4.0.3.jar" />
<entry name="$USER_HOME$/.local/share/gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-core/2.11.3/c2351800432bdbdd8284c3f5a7f0782a352aa84a/jackson-core-2.11.3.jar" />
<entry name="$USER_HOME$/.local/share/gradle/caches/modules-2/files-2.1/io.netty/netty-handler-proxy/4.1.60.Final/2352f12826400e5db64b36fd951508ce9a61c196/netty-handler-proxy-4.1.60.Final.jar" />
@@ -45,6 +51,7 @@
<entry name="$USER_HOME$/.local/share/gradle/caches/modules-2/files-2.1/io.netty/netty-buffer/4.1.60.Final/9d213d090deeca2541ad6827eb3345bcd6e1e701/netty-buffer-4.1.60.Final.jar" />
<entry name="$USER_HOME$/.local/share/gradle/caches/modules-2/files-2.1/io.netty/netty-resolver/4.1.60.Final/caba5004618d27386ee9d5ee8b23b09b6548fb0b/netty-resolver-4.1.60.Final.jar" />
<entry name="$USER_HOME$/.local/share/gradle/caches/modules-2/files-2.1/io.netty/netty-common/4.1.60.Final/44540113f7148f1014be879663501db8da1c37b0/netty-common-4.1.60.Final.jar" />
+ <entry name="$USER_HOME$/.local/share/gradle/caches/modules-2/files-2.1/io.vertx/vertx-bridge-common/4.0.3/423970a10bdc4fa14ce4ad18ef3f45d703e53420/vertx-bridge-common-4.0.3.jar" />
</processorPath>
<module name="dn42peering.central.main" />
</profile>
diff --git a/central/build.gradle b/central/build.gradle
index c0ed922..d95951d 100644
--- a/central/build.gradle
+++ b/central/build.gradle
@@ -37,6 +37,8 @@ dependencies {
annotationProcessor "io.vertx:vertx-service-proxy:${project.vertxVersion}"
compileOnly "io.vertx:vertx-codegen:${project.vertxVersion}"
implementation "io.vertx:vertx-grpc:${project.vertxVersion}"
+ implementation "io.vertx:vertx-web-api-service:${project.vertxVersion}"
+ annotationProcessor "io.vertx:vertx-web-api-service:${project.vertxVersion}"
implementation project(':rpc-common')
}
diff --git a/central/src/main/java/moe/yuuta/dn42peering/Main.java b/central/src/main/java/moe/yuuta/dn42peering/Main.java
index 5fa6690..8def1df 100644
--- a/central/src/main/java/moe/yuuta/dn42peering/Main.java
+++ b/central/src/main/java/moe/yuuta/dn42peering/Main.java
@@ -4,6 +4,7 @@ import io.vertx.core.*;
import io.vertx.core.impl.logging.Logger;
import io.vertx.core.impl.logging.LoggerFactory;
import io.vertx.core.json.JsonObject;
+import moe.yuuta.dn42peering.asn.ASNHttpVerticle;
import moe.yuuta.dn42peering.asn.ASNVerticle;
import moe.yuuta.dn42peering.node.NodeVerticle;
import moe.yuuta.dn42peering.peer.PeerVerticle;
@@ -14,6 +15,7 @@ import moe.yuuta.dn42peering.whois.WhoisVerticle;
import javax.annotation.Nonnull;
import java.io.FileInputStream;
import java.io.InputStream;
+import java.util.Arrays;
public class Main {
public static void main(@Nonnull String... args) throws Throwable {
@@ -36,14 +38,15 @@ public class Main {
.setConfig(config)
.setInstances(Runtime.getRuntime().availableProcessors() * 2);
Logger logger = LoggerFactory.getLogger("Main");
- CompositeFuture.all(
+ CompositeFuture.all(Arrays.asList(
Future.<String>future(f -> vertx.deployVerticle(PeerVerticle.class.getName(), options, f)),
Future.<String>future(f -> vertx.deployVerticle(WhoisVerticle.class.getName(), options, f)),
Future.<String>future(f -> vertx.deployVerticle(ASNVerticle.class.getName(), options, f)),
+ Future.<String>future(f -> vertx.deployVerticle(ASNHttpVerticle.class.getName(), options, f)),
Future.<String>future(f -> vertx.deployVerticle(NodeVerticle.class.getName(), options, f)),
Future.<String>future(f -> vertx.deployVerticle(ProvisionVerticle.class.getName(), options, f)),
Future.<String>future(f -> vertx.deployVerticle(HTTPPortalVerticle.class.getName(), options, f))
- ).onComplete(res -> {
+ )).onComplete(res -> {
if (res.succeeded()) {
logger.info("The server started.");
} else {
diff --git a/central/src/main/java/moe/yuuta/dn42peering/asn/ASNHandler.java b/central/src/main/java/moe/yuuta/dn42peering/asn/ASNHandler.java
index 7abc27e..0b88540 100644
--- a/central/src/main/java/moe/yuuta/dn42peering/asn/ASNHandler.java
+++ b/central/src/main/java/moe/yuuta/dn42peering/asn/ASNHandler.java
@@ -11,6 +11,7 @@ import io.vertx.ext.mail.MailConfig;
import io.vertx.ext.mail.MailMessage;
import io.vertx.ext.mail.MailResult;
import io.vertx.ext.web.Router;
+import io.vertx.ext.web.api.service.RouteToEBServiceHandler;
import io.vertx.ext.web.common.template.TemplateEngine;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.ext.web.templ.freemarker.FreeMarkerTemplateEngine;
@@ -45,28 +46,10 @@ public class ASNHandler implements ISubRouter {
@Nonnull
@Override
public Router mount(@Nonnull Vertx vertx) {
- final IASNService asnService = IASNService.createProxy(vertx, IASNService.ADDRESS);
- final IWhoisService whoisService = IWhoisService.createProxy(vertx, IWhoisService.ADDRESS);
- // Preserve. We need to get email address out of it later.
- final JsonObject mailConfig = vertx.getOrCreateContext().config().getJsonObject("mail");
- final MailClient mailClient = MailClient.create(vertx, new MailConfig(mailConfig));
-
- final TemplateEngine engine = FreeMarkerTemplateEngine.create(vertx, "ftlh");
final Router router = Router.router(vertx);
router.get("/")
.produces("text/html")
- .handler(ctx -> {
- renderIndex(engine, null, null, res -> {
- if(res.succeeded()) {
- ctx.response()
- .putHeader(HttpHeaders.CONTENT_TYPE, "text/html")
- .end(res.result());
- } else {
- ctx.fail(res.cause());
- logger.error("Cannot render /asn.", res.cause());
- }
- });
- });
+ .handler(RouteToEBServiceHandler.build(vertx.eventBus(), IASNHttpService.ADDRESS, "index"));
final ObjectSchemaBuilder registerSchema = objectSchema()
.allowAdditionalProperties(false)
@@ -81,129 +64,8 @@ public class ASNHandler implements ISubRouter {
.body(Bodies.formUrlEncoded(registerSchema))
.predicate(RequestPredicate.BODY_REQUIRED)
.build())
- .handler(ctx -> {
- final JsonObject parameters = ctx.<RequestParameters>get(ValidationHandler.REQUEST_CONTEXT_KEY)
- .body().getJsonObject();
- final String upperASN = parameters.getString("asn").toUpperCase();
- // Start: Check if the ASN exists.
- Future.<Void>future(f -> asnService.exists(upperASN, true, true, ar -> {
- if(ar.succeeded()) {
- if(ar.result()) {
- f.fail(new FormException("This ASN exists in our records. Please login instead of registering."));
- } else {
- f.complete();
- }
- } else {
- f.fail(ar.cause());
- }
- }))
- // Lookup ASN
- .<WhoisObject>compose(exists ->
- Future.future(f -> whoisService.query(upperASN, f)))
- // Lookup emails
- .<List<String>>compose(asnLookup -> {
- if(asnLookup == null) {
- return Future.failedFuture(new FormException("The ASN is not found in the DN42 registry."));
- } else {
- return Future.future(f -> asnService.lookupEmails(asnLookup, ar -> {
- if(ar.succeeded()) {
- if(ar.result().isEmpty()) {
- f.fail(new FormException("The tech-c contact for this ASN does not have emails."));
- } else {
- f.complete(ar.result());
- }
- } else {
- f.fail(ar.cause());
- }
- }));
- }
- })
- // Generate random password and register.
- .<Pair<String /* Random password */, List<String> /* Emails */>>compose(emails -> Future.future(f -> {
- final String randomPassword = new RandomStringGenerator.Builder()
- .withinRange('a', 'z')
- .build()
- .generate(15);
- asnService.registerOrChangePassword(upperASN, randomPassword, ar -> {
- if(ar.succeeded()) {
- f.complete(new Pair<>(randomPassword, emails));
- } else {
- f.fail(ar.cause());
- }
- });
- }))
- // Send mails.
- .compose(pair -> CompositeFuture.any(Stream.of(pair.b)
- .map(mail -> new MailMessage()
- .setFrom(mailConfig.getString("from"))
- .setTo(mail)
- .setText(String.format("Hi %s! Welcome to dn42 peering! Your peering initial password is %s. Make sure to change it.",
- upperASN,
- pair.a))
- .setSubject("Peering initial password"))
- .map(message -> Future.<MailResult>future(f -> mailClient.sendMail(message, f)))
- .collect(Collectors.toList())))
- // Render HTML or report errors
- .onComplete(ar -> {
- if(ar.succeeded()) {
- // Get MailResult's out of the future.
- final List<MailResult> sendRes = ar.result().list();
- renderSuccess(engine, sendRes, res -> {
- if(res.succeeded()) {
- ctx.response()
- .putHeader(HttpHeaders.CONTENT_TYPE, "text/html")
- .end(res.result());
- } else {
- ctx.fail(res.cause());
- logger.error("Cannot render /asn (success).", res.cause());
- }
- });
- } else {
- if(ar.cause() instanceof HTTPException) {
- ctx.response().setStatusCode(((HTTPException) ar.cause()).code).end();
- } else if(ar.cause() instanceof FormException) {
- renderIndex(engine,
- Arrays.asList(((FormException) ar.cause()).errors.clone()),
- upperASN,
- res -> {
- if(res.succeeded()) {
- ctx.response()
- .putHeader(HttpHeaders.CONTENT_TYPE, "text/html")
- .end(res.result());
- } else {
- ctx.fail(res.cause());
- logger.error("Cannot render /asn (with errors).", res.cause());
- }
- });
- } else {
- logger.error(String.format("Cannot register ASN %s.", upperASN), ar.cause());
- ctx.fail(ar.cause());
- }
- }
- });
- });
+ .handler(RouteToEBServiceHandler.build(vertx.eventBus(), IASNHttpService.ADDRESS, "register"));
return router;
}
-
- private void renderIndex(@Nonnull TemplateEngine engine,
- @Nullable List<String> errors,
- @Nullable String asn,
- @Nonnull Handler<AsyncResult<Buffer>> handler) {
- final Map<String, Object> root = new HashMap<>();
- root.put("input_asn", asn == null ? "" : asn);
- root.put("errors", errors);
- engine.render(root, "asn/index.ftlh", handler);
- }
-
- private void renderSuccess(@Nonnull TemplateEngine engine,
- @Nonnull List<MailResult> sendRes,
- @Nonnull Handler<AsyncResult<Buffer>> handler) {
- final Map<String, Object> root = new HashMap<>();
- root.put("emails", sendRes.stream()
- .filter(Objects::nonNull) // Nulls mean failures.
- .flatMap(res -> res.getRecipients().stream())
- .collect(Collectors.toList()));
- engine.render(root, "asn/success.ftlh", handler);
- }
}
diff --git a/central/src/main/java/moe/yuuta/dn42peering/asn/ASNHttpVerticle.java b/central/src/main/java/moe/yuuta/dn42peering/asn/ASNHttpVerticle.java
new file mode 100644
index 0000000..32f1a24
--- /dev/null
+++ b/central/src/main/java/moe/yuuta/dn42peering/asn/ASNHttpVerticle.java
@@ -0,0 +1,41 @@
+package moe.yuuta.dn42peering.asn;
+
+import io.vertx.core.AbstractVerticle;
+import io.vertx.core.Future;
+import io.vertx.core.Promise;
+import io.vertx.core.eventbus.MessageConsumer;
+import io.vertx.core.impl.logging.Logger;
+import io.vertx.core.impl.logging.LoggerFactory;
+import io.vertx.core.json.JsonObject;
+import io.vertx.serviceproxy.ServiceBinder;
+
+public class ASNHttpVerticle extends AbstractVerticle {
+ private final Logger logger = LoggerFactory.getLogger(getClass().getSimpleName());
+
+ private MessageConsumer<JsonObject> consumer;
+
+ @Override
+ public void start(Promise<Void> startPromise) throws Exception {
+ consumer = new ServiceBinder(vertx)
+ .setAddress(IASNHttpService.ADDRESS)
+ .register(IASNHttpService.class, new IASNHttpServiceImpl(vertx));
+ consumer.completionHandler(ar -> {
+ if (ar.succeeded()) {
+ startPromise.complete();
+ } else {
+ startPromise.fail(ar.cause());
+ }
+ });
+ }
+
+ @Override
+ public void stop(Promise<Void> stopPromise) throws Exception {
+ Future.future(f -> consumer.unregister(ar -> {
+ if (ar.succeeded()) f.complete();
+ else f.fail(ar.cause());
+ })).onComplete(ar -> {
+ if (ar.succeeded()) stopPromise.complete();
+ else stopPromise.fail(ar.cause());
+ });
+ }
+}
diff --git a/central/src/main/java/moe/yuuta/dn42peering/asn/IASNHttpService.java b/central/src/main/java/moe/yuuta/dn42peering/asn/IASNHttpService.java
new file mode 100644
index 0000000..aaa6913
--- /dev/null
+++ b/central/src/main/java/moe/yuuta/dn42peering/asn/IASNHttpService.java
@@ -0,0 +1,22 @@
+package moe.yuuta.dn42peering.asn;
+
+import io.vertx.core.AsyncResult;
+import io.vertx.core.Handler;
+import io.vertx.core.json.JsonObject;
+import io.vertx.ext.web.api.service.ServiceRequest;
+import io.vertx.ext.web.api.service.ServiceResponse;
+import io.vertx.ext.web.api.service.WebApiServiceGen;
+
+import javax.annotation.Nonnull;
+
+@WebApiServiceGen
+public interface IASNHttpService {
+ String ADDRESS = IASNHttpService.class.getName();
+
+ void index(@Nonnull ServiceRequest context,
+ @Nonnull Handler<AsyncResult<ServiceResponse>> handler);
+
+ void register(@Nonnull JsonObject body,
+ @Nonnull ServiceRequest context,
+ @Nonnull Handler<AsyncResult<ServiceResponse>> handler);
+}
diff --git a/central/src/main/java/moe/yuuta/dn42peering/asn/IASNHttpServiceImpl.java b/central/src/main/java/moe/yuuta/dn42peering/asn/IASNHttpServiceImpl.java
new file mode 100644
index 0000000..b2105a4
--- /dev/null
+++ b/central/src/main/java/moe/yuuta/dn42peering/asn/IASNHttpServiceImpl.java
@@ -0,0 +1,168 @@
+package moe.yuuta.dn42peering.asn;
+
+import io.vertx.core.*;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.http.HttpHeaders;
+import io.vertx.core.impl.logging.Logger;
+import io.vertx.core.impl.logging.LoggerFactory;
+import io.vertx.core.json.JsonObject;
+import io.vertx.ext.mail.MailClient;
+import io.vertx.ext.mail.MailConfig;
+import io.vertx.ext.mail.MailMessage;
+import io.vertx.ext.mail.MailResult;
+import io.vertx.ext.web.RoutingContext;
+import io.vertx.ext.web.api.service.ServiceRequest;
+import io.vertx.ext.web.api.service.ServiceResponse;
+import io.vertx.ext.web.common.template.TemplateEngine;
+import io.vertx.ext.web.templ.freemarker.FreeMarkerTemplateEngine;
+import moe.yuuta.dn42peering.jaba.Pair;
+import moe.yuuta.dn42peering.portal.FormException;
+import moe.yuuta.dn42peering.portal.HTTPException;
+import moe.yuuta.dn42peering.portal.RenderingUtils;
+import moe.yuuta.dn42peering.whois.IWhoisService;
+import moe.yuuta.dn42peering.whois.WhoisObject;
+import org.apache.commons.text.RandomStringGenerator;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+class IASNHttpServiceImpl implements IASNHttpService {
+ private final Logger logger = LoggerFactory.getLogger(getClass().getSimpleName());
+
+ private final IASNService asnService;
+ private final IWhoisService whoisService;
+ private final MailClient mailClient;
+ private final JsonObject mailConfig;
+ private final TemplateEngine templateEngine;
+
+ // Testing API
+ public IASNHttpServiceImpl(@Nonnull IASNService asnService,
+ @Nonnull IWhoisService whoisService,
+ @Nonnull MailClient mailClient,
+ @Nonnull JsonObject mailConfig,
+ @Nonnull TemplateEngine templateEngine) {
+ this.asnService = asnService;
+ this.whoisService = whoisService;
+ this.mailClient = mailClient;
+ this.mailConfig = mailConfig;
+ this.templateEngine = templateEngine;
+ }
+
+ public IASNHttpServiceImpl(@Nonnull Vertx vertx) {
+ this.asnService = IASNService.createProxy(vertx, IASNService.ADDRESS);
+ this.whoisService = IWhoisService.createProxy(vertx, IWhoisService.ADDRESS);
+ mailConfig = vertx.getOrCreateContext().config().getJsonObject("mail");
+ mailClient = MailClient.create(vertx, new MailConfig(mailConfig));
+ templateEngine = FreeMarkerTemplateEngine.create(vertx, "ftlh");
+ }
+
+ @Override
+ public void index(@Nonnull ServiceRequest context, @Nonnull Handler<AsyncResult<ServiceResponse>> handler) {
+ renderIndex(null, null, handler);
+ }
+
+ @Override
+ public void register(@Nonnull JsonObject parameters,
+ @Nonnull ServiceRequest context,
+ @Nonnull Handler<AsyncResult<ServiceResponse>> handler) {
+ final String upperASN = parameters.getString("asn").toUpperCase();
+ // Start: Check if the ASN exists.
+ Future.<Void>future(f -> asnService.exists(upperASN, true, true, ar -> {
+ if (ar.succeeded()) {
+ if (ar.result()) {
+ f.fail(new FormException("This ASN exists in our records. Please login instead of registering."));
+ } else {
+ f.complete();
+ }
+ } else {
+ f.fail(ar.cause());
+ }
+ }))
+ // Lookup ASN
+ .<WhoisObject>compose(exists ->
+ Future.future(f -> whoisService.query(upperASN, f)))
+ // Lookup emails
+ .<List<String>>compose(asnLookup -> {
+ if (asnLookup == null) {
+ return Future.failedFuture(new FormException("The ASN is not found in the DN42 registry."));
+ } else {
+ return Future.future(f -> asnService.lookupEmails(asnLookup, ar -> {
+ if (ar.succeeded()) {
+ if (ar.result().isEmpty()) {
+ f.fail(new FormException("The tech-c contact for this ASN does not have emails."));
+ } else {
+ f.complete(ar.result());
+ }
+ } else {
+ f.fail(ar.cause());
+ }
+ }));
+ }
+ })
+ // Generate random password and register.
+ .<Pair<String /* Random password */, List<String> /* Emails */>>compose(emails -> Future.future(f -> {
+ final String randomPassword = new RandomStringGenerator.Builder()
+ .withinRange('a', 'z')
+ .build()
+ .generate(15);
+ asnService.registerOrChangePassword(upperASN, randomPassword, ar -> {
+ if (ar.succeeded()) {
+ f.complete(new Pair<>(randomPassword, emails));
+ } else {
+ f.fail(ar.cause());
+ }
+ });
+ }))
+ // Send mails.
+ .compose(pair -> CompositeFuture.any(Stream.of(pair.b)
+ .map(mail -> new MailMessage()
+ .setFrom(mailConfig.getString("from"))
+ .setTo(mail)
+ .setText(String.format("Hi %s! Welcome to dn42 peering! Your peering initial password is %s. Make sure to change it.",
+ upperASN,
+ pair.a))
+ .setSubject("Peering initial password"))
+ .map(message -> Future.<MailResult>future(f -> mailClient.sendMail(message, f)))
+ .collect(Collectors.toList())))
+ // Render HTML or report errors
+ .onSuccess(res -> {
+ // Get MailResult's out of the future.
+ final List<MailResult> sendRes = res.list();
+ renderSuccess(sendRes, handler);
+ })
+ .onFailure(err -> {
+ if (err instanceof HTTPException) {
+ handler.handle(Future.succeededFuture(new ServiceResponse(((HTTPException) err).code, null, null, null)));
+ } else if (err instanceof FormException) {
+ renderIndex(Arrays.asList(((FormException) err).errors.clone()),
+ upperASN,
+ handler);
+ } else {
+ logger.error(String.format("Cannot register ASN %s.", upperASN), err);
+ handler.handle(Future.failedFuture(err));
+ }
+ });
+ }
+
+ private void renderIndex(@Nullable List<String> errors,
+ @Nullable String asn,
+ @Nonnull Handler<AsyncResult<ServiceResponse>> handler) {
+ final Map<String, Object> root = new HashMap<>();
+ root.put("input_asn", asn == null ? "" : asn);
+ root.put("errors", errors);
+ templateEngine.render(root, "asn/index.ftlh", RenderingUtils.getGeneralRenderingHandler(handler));
+ }
+
+ private void renderSuccess(@Nonnull List<MailResult> sendRes,
+ @Nonnull Handler<AsyncResult<ServiceResponse>> handler) {
+ final Map<String, Object> root = new HashMap<>();
+ root.put("emails", sendRes.stream()
+ .filter(Objects::nonNull) // Nulls mean failures.
+ .flatMap(res -> res.getRecipients().stream())
+ .collect(Collectors.toList()));
+ templateEngine.render(root, "asn/success.ftlh", RenderingUtils.getGeneralRenderingHandler(handler));
+ }
+}
diff --git a/central/src/main/java/moe/yuuta/dn42peering/portal/RenderingUtils.java b/central/src/main/java/moe/yuuta/dn42peering/portal/RenderingUtils.java
new file mode 100644
index 0000000..a8514f3
--- /dev/null
+++ b/central/src/main/java/moe/yuuta/dn42peering/portal/RenderingUtils.java
@@ -0,0 +1,26 @@
+package moe.yuuta.dn42peering.portal;
+
+import io.vertx.core.AsyncResult;
+import io.vertx.core.Future;
+import io.vertx.core.Handler;
+import io.vertx.core.MultiMap;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.http.HttpHeaders;
+import io.vertx.ext.web.api.service.ServiceResponse;
+
+import javax.annotation.Nonnull;
+
+public class RenderingUtils {
+ public static Handler<AsyncResult<Buffer>> getGeneralRenderingHandler(@Nonnull Handler<AsyncResult<ServiceResponse>> handler) {
+ return res -> {
+ if (res.succeeded()) {
+ handler.handle(Future.succeededFuture(new ServiceResponse(200,
+ null,
+ res.result(),
+ MultiMap.caseInsensitiveMultiMap().add(HttpHeaders.CONTENT_TYPE, "text/html"))));
+ } else {
+ handler.handle(Future.failedFuture(res.cause()));
+ }
+ };
+ }
+}