Make POST /users/:id/login fully asynchronous

This commit is contained in:
Colin McDonald 2016-07-14 20:16:05 -04:00
parent 43e91b63be
commit da992e864b
3 changed files with 95 additions and 36 deletions

View File

@ -372,7 +372,7 @@ public final class APIv3 extends AbstractVerticle {
http.get("/users/:userId/verifyPassword").blockingHandler(new GETUsersIdVerifyPassword(), false);
http.post("/users/:userId/changePassword").blockingHandler(new POSTUsersIdChangePassword(), false);
http.post("/users/:userId/confirmPhone").blockingHandler(new POSTUsersIdConfirmPhone(), false);
http.post("/users/:userId/login").blockingHandler(new POSTUsersIdLogin());
http.post("/users/:userId/login").handler(new POSTUsersIdLogin());
http.post("/users/:userId/notify").blockingHandler(new POSTUsersIdNotify(), false);
http.post("/users/:userId/passwordReset").blockingHandler(new POSTUsersIdPasswordReset(), false);
http.post("/users/:userId/registerEmail").blockingHandler(new POSTUsersIdRegisterEmail(), false);

View File

@ -79,6 +79,43 @@ public final class User {
}
}
public static void findOrCreateById(UUID id, String username, SingleResultCallback<User> callback) {
if (!UuidUtils.isAcceptableUuid(id)) {
callback.onResult(null, null);
return;
}
usersCollection.find(new Document("_id", id)).first(SyncUtils.vertxWrap((user, error) -> {
if (error != null) {
callback.onResult(null, error);
return;
}
if (user != null) {
callback.onResult(user, null);
return;
}
User created = new User(id, username);
created.checkNameCollisions((ignored, nameCollisionsError) -> {
if (nameCollisionsError != null) {
callback.onResult(null, nameCollisionsError);
return;
}
created.insert((ignored2, insertUserError) -> {
if (insertUserError != null) {
callback.onResult(null, insertUserError);
} else {
callback.onResult(created, null);
}
});
});
}));
}
public static void findByPhone(String phoneNumber, SingleResultCallback<User> callback) {
usersCollection.find(new Document("$or", ImmutableList.of(
new Document("phone", phoneNumber),

View File

@ -1,6 +1,7 @@
package net.frozenorb.apiv3.route.users;
import com.google.common.collect.ImmutableMap;
import com.mongodb.async.SingleResultCallback;
import io.vertx.core.Handler;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
@ -12,7 +13,6 @@ import net.frozenorb.apiv3.model.Server;
import net.frozenorb.apiv3.model.User;
import net.frozenorb.apiv3.util.ErrorUtils;
import net.frozenorb.apiv3.util.IpUtils;
import net.frozenorb.apiv3.util.SyncUtils;
import net.frozenorb.apiv3.util.UuidUtils;
import java.util.UUID;
@ -23,12 +23,11 @@ public final class POSTUsersIdLogin implements Handler<RoutingContext> {
UUID uuid = UuidUtils.parseUuid(ctx.request().getParam("userId"));
if (!UuidUtils.isAcceptableUuid(uuid)) {
ErrorUtils.respondInvalidInput(ctx, "UUID \"" + uuid + "\" is not valid - must be version 4 UUID.");
ErrorUtils.respondInvalidInput(ctx, "Uuid \"" + uuid + "\" is not valid.");
return;
}
JsonObject requestBody = ctx.getBodyAsJson();
User user = SyncUtils.runBlocking(v -> User.findById(uuid, v));
String currentUsername = requestBody.getString("username");
String userIp = requestBody.getString("userIp");
Actor actor = ctx.get("actor");
@ -45,49 +44,72 @@ public final class POSTUsersIdLogin implements Handler<RoutingContext> {
return;
}
if (user == null) {
user = new User(uuid, currentUsername);
User finalUser = user;
User.findOrCreateById(uuid, currentUsername, (user, findUserError) -> {
if (findUserError != null) {
ErrorUtils.respondInternalError(ctx, findUserError);
return;
}
SyncUtils.<Void>runBlocking(v -> finalUser.checkNameCollisions(v));
SyncUtils.<Void>runBlocking(v -> finalUser.insert(v));
}
incrementIpLog(user, userIp, (ignored, ipLogError) -> {
if (ipLogError != null) {
ErrorUtils.respondInternalError(ctx, ipLogError);
return;
}
User finalUser = user;
IpLogEntry ipLogEntry = SyncUtils.runBlocking(v -> IpLogEntry.findByUserAndIp(finalUser, userIp, v));
updateUsername(user, currentUsername, (ignored2, updateUsernameError) -> {
if (updateUsernameError != null) {
ErrorUtils.respondInternalError(ctx, updateUsernameError);
return;
}
// We use a little bit more verbose code here to save on the
// overhead of a .insert() immediately followed by a .save()
if (ipLogEntry == null) {
ipLogEntry = new IpLogEntry(user, userIp);
ipLogEntry.used();
user.seenOnServer(actorServer);
user.save((ignored3, saveUserError) -> {
if (saveUserError != null) {
ErrorUtils.respondInternalError(ctx, saveUserError);
return;
}
IpLogEntry finalIpLogEntry = ipLogEntry;
SyncUtils.<Void>runBlocking(v -> finalIpLogEntry.insert(v));
} else {
ipLogEntry.used();
user.getLoginInfo(actorServer, userIp, (loginInfo, loginInfoError) -> {
if (loginInfoError != null) {
ErrorUtils.respondInternalError(ctx, loginInfoError);
} else {
APIv3.respondJson(ctx, 200, loginInfo);
}
});
});
});
});
});
}
IpLogEntry finalIpLogEntry = ipLogEntry;
SyncUtils.<Void>runBlocking(v -> finalIpLogEntry.save(v));
}
public void incrementIpLog(User user, String userIp, SingleResultCallback<Void> callback) {
IpLogEntry.findByUserAndIp(user, userIp, (existingEntry, error) -> {
if (error != null) {
callback.onResult(null, error);
return;
}
if (existingEntry != null) {
existingEntry.used();
existingEntry.save(callback);
return;
}
IpLogEntry inserted = new IpLogEntry(user, userIp);
inserted.used();
inserted.insert(callback);
});
}
public void updateUsername(User user, String currentUsername, SingleResultCallback<Void> callback) {
String lastUsername = user.getLastUsername();
user.updateUsername(currentUsername);
if (!currentUsername.equals(lastUsername)) {
SyncUtils.<Void>runBlocking(v -> finalUser.checkNameCollisions(v));
user.checkNameCollisions(callback);
} else {
callback.onResult(null, null);
}
finalUser.seenOnServer(actorServer);
SyncUtils.<Void>runBlocking(v -> finalUser.save(v));
user.getLoginInfo(actorServer, userIp, (loginInfo, error) -> {
if (error != null) {
ErrorUtils.respondInternalError(ctx, error);
} else {
APIv3.respondJson(ctx, 200, loginInfo);
}
});
}
}