Merge pull request #42 from FrozenOrb/async-user-login

Make POST /users/:id/login fully asynchronous
This commit is contained in:
Colin McDonald 2016-07-14 20:17:09 -04:00 committed by GitHub
commit ad0140d29f
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.get("/users/:userId/verifyPassword").blockingHandler(new GETUsersIdVerifyPassword(), false);
http.post("/users/:userId/changePassword").blockingHandler(new POSTUsersIdChangePassword(), false); http.post("/users/:userId/changePassword").blockingHandler(new POSTUsersIdChangePassword(), false);
http.post("/users/:userId/confirmPhone").blockingHandler(new POSTUsersIdConfirmPhone(), 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/notify").blockingHandler(new POSTUsersIdNotify(), false);
http.post("/users/:userId/passwordReset").blockingHandler(new POSTUsersIdPasswordReset(), false); http.post("/users/:userId/passwordReset").blockingHandler(new POSTUsersIdPasswordReset(), false);
http.post("/users/:userId/registerEmail").blockingHandler(new POSTUsersIdRegisterEmail(), 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) { public static void findByPhone(String phoneNumber, SingleResultCallback<User> callback) {
usersCollection.find(new Document("$or", ImmutableList.of( usersCollection.find(new Document("$or", ImmutableList.of(
new Document("phone", phoneNumber), new Document("phone", phoneNumber),

View File

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