How to return user if based on facebook user ID it already exist, and create a new user if not exist in Vapor? You can see how I tried fetch data, but get error.
final class User: Content {
var id: Int?
var fbId: String
init(id: Int? = nil, fbId: String) {
self.id = id
self.fbId = fbId
}
}
router.get("user") { (request) -> Future<User> in
return Future.map(on: request) { () -> User in
let fbId = try request.query.get(String.self, at: "fbId")
return User.query(on: request).filter(\.fbId == fbId).first().map { (user) -> (U) in
if user == nil {
user = User(fbId: fbId)
}
return user
}
}
}
You have a few things going on here. To start with you don't need the first Future.map - not sure what that's doing.
Then you have the issue of the compiler - you have to return the same type in each closure and the function, which is awkward because if you already have a user you can return that, if you don't you need to create and save one, which returns Future<User>, which is not the same to User.
So to answer your question, U there should be User, but really you want to change first().map to first().flatMap in which case U becomes Future<User>. Then you can do something like:
router.get("user") { req -> Future<User> in
let fbID = try req.query.get(String.self, at: "fbId")
return User.query(on: req).filter(\.fbId == fbID).first().flatMap { user in
let returnedUser: Future<User>
if let foundUser = user {
returnedUser = req.future(foundUser)
} else {
let newUser = User(fbId: fbID)
returnedUser = newUser.save(on: req)
}
return returnedUser
}
}
To solve your problems. Hope that helps!
.map { (user) -> (U) in
This defines that you get a user into the closure and have to return a U. In your example you want to return a User (so change U to User).
If you want to create the user (in case it is nil) you probably also want to store it in the database? If that's the case, you'll have to change map to flatMap and update like this:
.flatMap { (user) -> EventLoopFuture<User> in
if user == nil {
return User(fbId: fbId).save(on: req)
}
return req.future(user)
}
Related
I have two Client APIs that return an Uni.
Uni<Customer> getCustomer(customerID)
Uni<Address> getAddress(addressID)
And I want to open a REST API
Uni<FullCustomer> getFullCustomer(String customerID)
The logic is to make the Customer Client call first. If the returned customer object has addressID then make the second Address Client call and get shipping address details. If shipping address is not available then just wrap the customer in FullCustomer object and return else wrap both customer and address in FullCustomer object and return.
I dont want to block the thread on client call (await().indefinitely()), hence i am using onItem and transfer method call. But my code returns a Uni<Uni> and i want it to return a Uni.
#GET
#Path("api/customer/{id}")
#Produces({ "application/json" })
Uni<Uni<FullCustomer>> getFullCustomer(#PathParam("id") String customerID){
Uni<Customer> customerResponse = getCustomer(customerID);
Uni<Uni<FullCustomer>> asyncResponse = customerResponse.onItem().transform(customer -> {
if (customer.getAddressId() != null) {
Uni<Address> addressResponse = getAddress(customer.getAddressId());
Uni<FullCustomer> fullCustomer = addressResponse.onItem().transform(address -> {
if (address.getShippingAddress() != null) {
return new FullCustomer(customer, address.getShippingAddress());
} else {
return new FullCustomer(customer);
}
});
}
return Uni.createFrom().item(new FullCustomer(customer));
});
return asyncResponse;
}
How can I rewrite my code so that it returns Uni keeping reactive ( async client ) calls
Got the solution. Thanks Ladicek for comments.
public Uni<FullCustomer> getFullCustomer(#PathParam("id") String customerID) {
return getCustomer(customerID)
.onItem()
.transformToUni(customer -> {
if (customer.getAddressId() != null) {
return getAddress(customer.getAddressId()).onItem().transform(address -> {
if (address.getShippingAddress() != null) {
return new FullCustomer(customer, address.getShippingAddress());
} else {
return new FullCustomer(customer);
}
});
} else {
return Uni.createFrom().item(new FullCustomer(customer));
}
});
}
I'm using the webflux framework for spring boot, the behavior I'm trying to implement is creating a new customer in the database, if it does not already exist (throw an exception if it does)
and also maintain another country code database (if the new customer is from a new country, add to the database, if the country is already saved, use the old information)
This is the function in the service :
public Mono<Customer> createNewCustomer(Customer customer) {
if(!customer.isValid()) {
return Mono.error(new BadRequestException("Bad email or birthdate format"));
}
Mono<Customer> customerFromDB = customerDB.findByEmail(customer.getEmail());
Mono<Country> countryFromDB = countryDB.findByCountryCode(customer.getCountryCode());
Mono<Customer> c = customerFromDB.zipWith(countryFromDB).doOnSuccess(new Consumer<Tuple2<Customer, Country>>() {
#Override
public void accept(Tuple2<Customer, Country> t) {
System.err.println("tuple " + t);
if(t == null) {
countryDB.save(new Country(customer.getCountryCode(), customer.getCountryName())).subscribe();
customerDB.save(customer).subscribe();
return;
}
Customer cus = t.getT1();
Country country = t.getT2();
if(cus != null) {
throw new CustomerAlreadyExistsException();
}
if(country == null) {
countryDB.save(new Country(customer.getCountryCode(), customer.getCountryName())).subscribe();
}
else {
customer.setCountryName(country.getCountryName());
}
customerDB.save(customer).subscribe();
}
}).thenReturn(customer);
return c;
}
My problem is, the tuple returns null if either country or customer are not found, while I need to know about them separately if they exist or not, so that I can save to the database correctly.
country == null is never true
I also tried to use customerFromDB.block() to get the actual value but I receive an error that it's not supported, so I guess that's not the way
Is there anyway to do two queries to get their values?
Solved it with the following solution:
public Mono<Customer> createNewCustomer(Customer customer) {
if(!customer.isValid()) {
return Mono.error(new BadRequestException("Bad email or birthdate format"));
}
return customerDB.findByEmail(customer.getEmail())
.defaultIfEmpty(new Customer("empty", "", "", "", "", ""))
.flatMap(cu -> {
if(!cu.getEmail().equals("empty")) {
return Mono.error(new CustomerAlreadyExistsException());
}
return countryDB.findByCountryCode(customer.getCountryCode())
.defaultIfEmpty(new Country(customer.getCountryCode(), customer.getCountryName()))
.flatMap(country -> {
customer.setCountryName(country.getCountryName());
customerDB.save(customer).subscribe();
countryDB.save(country).subscribe();
return Mono.just(customer);});
});
}
Instead of doing both queries simulatneaously, I queried for one result and then queries for the next, I think this is the reactive way of doing it, but I'm open for corrections.
I've been banging my head against the wall trying to figure out what's going wrong here for a while. I created a simple Ktor server that allows you to create a user, which should return a token to the user and store the session. Then I want an authenticated endpoint to allow the user to be deleted. However, the authenticated call loads an empty session, and can't find the user, so the user can't be deleted. Any help would be appreciated! Code here:
Application.kt
...
fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)
#Suppress("unused")
#kotlin.jvm.JvmOverloads
fun Application.module(testing: Boolean = false) {
install(Locations) {
}
install(Sessions) {
cookie<MySession>("MY_SESSION") {
cookie.extensions["SameSite"] = "lax"
}
}
DatabaseFactory.init()
val db = MyRepository()
val jwtService = JwtService()
val hashFunction = { s: String -> hash(s) }
install(Authentication) {
jwt("jwt") { //1
verifier(jwtService.verifier) // 2
realm = "My Server"
validate { // 3
val payload = it.payload
val claim = payload.getClaim("id")
val claimString = claim.asInt()
val user = db.findUser(claimString) // 4
user
}
}
}
install(ContentNegotiation) {
gson {
}
}
routing {
users(db, jwtService, hashFunction)
}
}
UserRoute.kt
...
const val USERS = "$API_VERSION/users"
const val USER_CREATE = "$USERS/create"
const val USER_DELETE = "$USERS/delete"
#KtorExperimentalLocationsAPI
#Location(USER_CREATE)
class UserCreateRoute
#KtorExperimentalLocationsAPI
#Location(USER_DELETE)
class UserDeleteRoute
#KtorExperimentalLocationsAPI
fun Route.users(
db: Repository,
jwtService: JwtService,
hashFunction: (String) -> String
) {
post<UserCreateRoute> {
val request = call.receive<CreateUserRequest>()
val password = request.password
?: return#post call.respond(
HttpStatusCode.Unauthorized, "Missing Fields")
val email = request.email
?: return#post call.respond(
HttpStatusCode.Unauthorized, "Missing Fields")
val hash = hashFunction(password)
try {
val newUser = db.addUser(email, hash)
newUser?.userId?.let {
call.sessions.set(MySession(it))
call.respondText(
jwtService.generateToken(newUser),
status = HttpStatusCode.Created
)
}
} catch (e: Throwable) {
call.respond(HttpStatusCode.BadRequest, "Problems creating User")
}
}
authenticate("jwt") {
delete<UserDeleteRoute> {
try {
val userId = call.sessions.get<MySession>()?.userId
if (userId == null) {
call.respond(
HttpStatusCode.BadRequest, "Problem retrieving User")
return#delete
}
if (db.deleteUser(userId)) {
call.respond(HttpStatusCode.NoContent, "User deleted")
} else {
call.respond(HttpStatusCode.BadRequest, "Failed to delete user")
}
} catch (e: Exception) {
application.log.error("Failed to delete user")
call.respond(HttpStatusCode.BadRequest, "Failed to delete user")
}
}
}
}
Is there something I'm missing? The token is returned successfully, and then my delete request is routed to the right place, but the line val userId = call.sessions.get<MySession>()?.userId returns null every time.
You don't show the client code but it is just as important. Likely the problem is on the client not on server. When the clients does the delete does it send the token?
jwt would be more complicated for for basic auth after you get a session each request must include the session header:
curl -H "MY_SESSION: f152dad6e955ba53" -D - localhost:8080/api/admin/principle
I am working on bot framework technology, in one of my current project I want to allow the user only if he/she type the ‘’ivr” or “IVR” otherwise it shows some feedback to the user.
For that I have wrote below lines of code, but this code shows some wrong output to the user. Even if the user enter ivr or IVR it shows feedback to the user for the first time, but from second time onwards its working correctly.
[Serializable]
class Customer
{
//Create Account Template
[Prompt("Please send any of these commands like **IVR** (or) **ivr**.")]
public string StartingWord;
public static IForm<Customer> BuildForm()
{
OnCompletionAsyncDelegate<Customer> accountStatus = async (context, state) =>
{
await Task.Delay(TimeSpan.FromSeconds(5));
await context.PostAsync("We are currently processing your account details. We will message you the status.");
};
var builder = new FormBuilder<Customer>();
return builder
//.Message("Welcome to the BankIVR bot! To start an conversation with this bot send **ivr** or **IVR** command.\r \n if you need help, send the **Help** command")
.Field(nameof(Customer.StartingWord), validate: async (state, response) =>
{
var result = new ValidateResult { IsValid = true, Value = response };
string str = (response as string);
if (str.ToLower() != "ivr")
{
result.Feedback = "I'm sorry. I didn't understand you.";
result.IsValid = false;
return result;
}
else if (str.ToLower() == "ivr")
{
result.IsValid = true;
return result;
}
else
{
return result;
}
})
.OnCompletion(accountStatus)
.Build();
}
};
Please tell me how to resolve this issue using Form Flow concept.
-Pradeep
Your code looks correct to me - I can only suggest you debug your code with a step-through debugger and see where the logic tests are failing.
That said, if it's not working for people in Turkey, it's because you shouldn't use .ToLower() for normalizing text, for example the .ToLower() method does not work for text that contains the Turkish dotless 'I' character: http://archives.miloush.net/michkap/archive/2004/12/02/273619.html
Also, your else case will never be hit because your two prior checks (!= and ==) cover every possible case (the C# compiler is currently not sophisticated enough to flag the else case as unreachable code).
The correct way to do a case-insensitive comparison is with String.Equals:
if( "ivr".Equals( str, StringComparison.InvariantCultureIgnoreCase ) ) {
result.IsValid = true;
return result;
}
else {
result.Feedback = "I'm sorry. I didn't understand you.";
result.IsValid = false;
}
Finally, I got the result with out any issue.
here is my updated code for to allow only the user enter "ivr or IVR" word, to start a form flow conversation with bot.
.Field(nameof(Customer.StartingWord), validate: async (state, response) =>
{
var result = new ValidateResult { IsValid = true, Value = response };
string str = (response as string);
if ("ivr".Equals(str, StringComparison.InvariantCultureIgnoreCase))
{
//result.IsValid = true;
//return result;
}
else
{
result.Feedback = "I'm sorry. I didn't understand you.";
result.IsValid = false;
//return result;
}
return result;
})
-Pradeep
i have a userSignUp and login System where a user is stored with his email address and password.
Later i want to add more information to this user, so i created a new data class on parse.com with the name "UserProfile" where i store the userObjectId from the user that has been signed up.
So now i want to update this user in the data class UserProfile but i just now the objectId from the user in the UserClass.
So i want to update like "update user where userObjectId = PFUser.currentUser().objectId"
i have this snippet of code already:
var query = PFQuery(className:"UserProfiles")
var userId = PFUser.currentUser()?.objectId?
query.whereKey("userObjectId", equalTo: userId)
//i don't think, that the following code is correct
query.getObjectInBackgroundWithId("xWMyZEGZ") {
(gameScore: PFObject?, error: NSError?) -> Void in
if error != nil {
println(error)
} else if let gameScore = gameScore {
gameScore["cheatMode"] = true
gameScore["score"] = 1338
gameScore.saveInBackground()
}
}
Assuming your "userObjectId" field is a string field and not a pointer field, then you can do this.
var query = PFQuery(className: "UserProfiles")
var userId = PFUser.currentUser()!.objectId
query.whereKey("userObjectId", equalTo: userId)
//here you would just find the results
query.findObjectsInBackgroundWithBlock {(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
//you have the user, assuming one but their could be more
//this will only return one user since you're using the objectID as a field and those are unique, so...
for user in objects! {
user["aColumnYouWishToUpdate"] = whatYouWishToUpdateTo
user["anotherColumnToUpdate"] = anotherUpdatedVariable
//do this for any columns you want updated
user.saveInBackground() //to save the newly updated user
}
} else {
//you have an error
}
}