Nested queries with ReactiveMongo - mongodb

I have an article collection where fields "title" and "publication" has a unique combined key constraint.
When calling insertOrUpdateArticle(a: Article) it will first try to insert it, in case it hits the constraint, it should update the article - if needed.
However, I'm stuck before that. Current error is:
Error:(88, 57) type mismatch;
found : scala.concurrent.Future[scala.concurrent.Future[Boolean]]
required: Boolean
col_article.find(selector).one[Article].map {
Source:
def insertOrUpdateArticle(a: Article): Future[Boolean] = {
// try insert article
col_article.insert[Article](a).map {
// article inserted
lastError => {
println("Article added.")
true
}
}.recover {
case lastError: LastError =>
// check if article existed
lastError.code.get match {
case 11000 => {
// article existed (duplicate key error)
// load old article
val selector = BSONDocument(
"title" -> BSONString(a.title),
"publication" -> BSONString(a.publication)
)
col_article.find(selector).one[Article].map {
case Some(old_a: Article) => {
// TODO: compare a with old_a
// TODO: if a differs from old_a, update
Future(true)
}
case None => {
// something went wrong
Future(false)
}
}
}
case _ => {
println("DB.insertOrUpdateArticle() unexpected error code when inserting: " + lastError.code.get)
false
}
}
case ex =>
println("DB.insertOrUpdateArticle() unexpected exception when inserting: " + ex)
false
}
}
I'm unsure what to do here. The code should return Future(true) if the article was saved or updated, false otherwise. There's something with reactivemongo and/or scala futures I'm missing out here.

Using recoverWith to create new Future is the solution, modified code:
def insertOrUpdateArticleOld(a: Article): Future[Boolean] = {
// try insert article
col_article.insert[Article](a).map {
// article inserted
lastError => {
println("Article added.")
true
}
}.recoverWith {
case lastError: LastError =>
// check if article existed
lastError.code.get match {
case 11000 => {
// article existed (duplicate key error)
// load old article
val selector = BSONDocument(
"title" -> BSONString(a.title),
"publication" -> BSONString(a.publication)
)
col_article.find(selector).one[Article].flatMap {
case Some(old_a: Article) => {
// TODO: compare a with old_a
// TODO: if a differs from old_a, update
Future(true)
}
case None => {
// something went wrong
Future(false)
}
}
}
case _ => {
println("DB.insertOrUpdateArticle() unexpected error code when inserting: " + lastError.code.get)
Future(false)
}
}
case ex =>
println("DB.insertOrUpdateArticle() unexpected exception when inserting: " + ex)
Future(false)
}
}

Related

Akka http 401 on not authenticated endpoint

I have the following routes definition
private val routes: Route =
concat(
pathEnd {
get {
handleErrorsAndReport("list_foos") {
}
}
},
path(Segment) { fooId =>
get {
handleErrorsAndReport("get_foo") {
rejectEmptyResponse(
complete(service.do(fooId))
)
}
}
},
pathEnd {
authenticateBasic(
realm = "Secure scope",
scopesAuthenticator
) { scopes =>
post {
handleErrorsAndReport("create_foo") {
}
}
}
},
path(Segment) { fooId =>
authenticateBasic(
realm = "Secure scopes",
scopesAuthenticator
) { scopes =>
concat(
put {
handleErrorsAndReport("update_foo") {
}
},
delete {
handleErrorsAndReport("delete_foo") {
}
}
)
}
}
)
I am trying to consume the get_foo endpoint. I have created a unit test for that and it looks like this
"allow get operation on foo without authentication present" in {
Get("/foos/{some_id}") ~> routes ~> check {
status shouldBe StatusCodes.NotFound
}
}
While debugging the test I can see that the route is correctly identified and I can access the code inside the route. The service code inside the get_foo route produces a None and complete(None) creates a rejection since it's an empty response and I have the rejectEmptyResponse directive. So I would expect that I would get a 404 response based on the handleErrorsAndReport directive that I have defined. The error handler looks like this
private def handleErrorsAndReport(endpoint: String): Directive0 = extractRequestContext.flatMap { ctx =>
val start = System.currentTimeMillis()
mapResponse { resp =>
// store response related metrics and return response
resp
} & handleExceptions(exceptionHandler)
}
private val exceptionHandler: ExceptionHandler = {
def handle(e: Throwable, responseCode: StatusCode, errorMessage: Option[String]): Route = {
extractRequest { request =>
val response = (responseCode, errorMessage) match {
case (InternalServerError, _) => "Internal Server Error"
case (_, Some(message)) => message
case _ => "Bad Request"
}
complete(HttpResponse(responseCode, entity = response))
}
}
ExceptionHandler {
case e#AError(description) => handle(e, BadRequest, Some(description))
case e: BError => handle(e, InternalServerError, Some(e.errorMessage))
case e: CError => handle(e, BadRequest, Some(e.errorMessage))
case e: DError => handle(e, BadRequest, Some(e.errorMessage))
case e: EError => handle(e, BadRequest, Some(e.errorMessage))
case e#FException(filter) => handle(e, BadRequest, Some(s"bla"))
case other => handle(other, InternalServerError, Option(other.getMessage))
}
}
What I am getting though is a 401 Unauthorized. How can this be?
As I was debugging the code I noticed that the control flow never enters my exception handler - I added breakpoints everywhere inside...
The problem with your code is that, after the rejection of request in the unauthenticated directive, the match happens against an authenticated route with the same url. So the authentication fails and results in 401 unauthorized instead of 404.
For solving it, you need to prevent this match against the authenticated route, after the failure of the unauthenticated route with the GET method, by wrapping it inside post, put, and delete routes so that the GET requests cannot reach it.
So it can be written as
private val routes: Route =
concat(
pathEnd {
get {
handleErrorsAndReport("list_foos") {
}
}
},
path(Segment) { fooId =>
get {
handleErrorsAndReport("get_foo") {
rejectEmptyResponse(
complete(service.do(fooId))
)
}
}
},
post {
pathEnd {
authenticateBasic(
realm = "Secure scope",
scopesAuthenticator
) { scopes =>
handleErrorsAndReport("create_foo") {
}
}
}
},
put{
path(Segment) { fooId =>
authenticateBasic(
realm = "Secure scopes",
scopesAuthenticator
) { scopes =>
handleErrorsAndReport("update_foo") {
}
}
}
},
delete{
path(Segment) { fooId =>
authenticateBasic(
realm = "Secure scopes",
scopesAuthenticator
) { scopes =>
handleErrorsAndReport(“delete_foo") {
}
}
}
}
)

Akka HTTP Route Handling [duplicate]

This question already has an answer here:
Akka-HTTP route not found?
(1 answer)
Closed 1 year ago.
I have the following route setup
val routes = protectedRoute("wallet") { user =>
concat {
path("wallet" / "deposit") {
get {
completeEitherT(walletService.deposit(user._id, "dollars", 50))
}
}
path("wallet") {
get {
completeEitherT(walletService.getForUser(user._id))
}
}
}
}
The completeEitherT function is as follows
def completeEitherT[T](t: EitherT[Future, DatabaseError, T])(implicit marsh: ToResponseMarshaller[T]): Route = {
onSuccess(t.value) {
case Left(x: DatabaseErrors.NotFound) =>
logger.error(x.toString)
complete(StatusCodes.NotFound)
case Left(error) => complete(StatusCodes.InternalServerError)
case Right(value) => complete(value)
}
}
The protectedRoute Directive is:
def protectedRoute(permissions: String*): Directive1[User] = new Directive1[User] {
override def tapply(f: (Tuple1[User]) => Route): Route = {
headerValueByName("Authorization") { jwt =>
Jwt.decode(jwt, jwtSecret, List(algo)) match {
case Failure(_) =>
logger.error("Unauthorized access from jwtToken:", jwt)
complete(StatusCodes.Unauthorized)
case Success(value) =>
val user = JsonParser(value.content).convertTo[User]
if (user.roles.flatMap(_.permissions).exists(permissions.contains)) f(Tuple1(user))
else complete(StatusCodes.Forbidden)
}
}
}
}
The issue that I am having is that whichever path is defined first results in a 404 when called via Postman.
The requested resource could not be found.
This is not a response from completeEitherT as it does not log the error
How do I define two or more paths inside the same directive?
Please mark as duplicate, I could not find this on Google Search but SO showed this as related.
Answer
Essentially I am missing a ~ between the paths
val routes = protectedRoute("wallet") { user =>
concat {
path("wallet" / "deposit") {
get {
completeEitherT(walletService.deposit(user._id, "dollars", 50))
}
} ~
path("wallet") {
get {
completeEitherT(walletService.getForUser(user._id))
}
}
}
}

Type 'Object' provides no match for the signature '(artistsongs: string): string'.ts(2322)

artistSelected(item: any) {
console.log(item.artist);
this.songsServices.getSongsListArtist(item.artist).subscribe(
result => {
this.songsServices.artistList = result;
this.route.navigate(['./artistsongs']);
},
error => {
console.log('There is an error');
}
);
}
Service
getSongsListArtist(album: string) {
const completeUrl = `${environment.API_URL}?album=${album}`;
return this.httpClient.get(encodeURI(completeUrl));
}
artistList(artistsongs: string) {
return artistsongs;
}
I am facing an error in this. Please sole me how to pass the object from one ts file to another ts file by using service.
artistList(artistsongs: string) {
return artistsongs;
}
change to:
artistList(artistsongs: any) {
return artistsongs;
}
As i understand when you call getSongsListArtist it will return an array object or an object not a string only. You can use typeof() to check this.httpClient.get(encodeURI(completeUrl)) return what kind of type.

Akka Http return different type of response based on Accept header

I'm novice to scala and Akka-Http. Experimenting Akka-Http for writing rest services. I have to return json or protobuf based on the Accept header.
optionalHeaderValueByName("Accept"){ contentType =>
if(contentType == Some(protoEncode)) {
complete {
NewsService.getNewsList().map {
case stories: List[Story] => HttpResponse(entity = HttpEntity(ContentType(protoEncoding), StoryList(stories).toProto().build().toByteArray))
}
}
} else {
complete {
NewsService.getNewsList().map {
case stories: List[Story] => StoryList(stories)
}
}
}
As you can see the code repetition is happening can anyone suggest what could be the best way to optimise and generalise design to avoid such situation.
The simplest way is to move the check inside the body.
optionalHeaderValueByName("Accept"){ contentType =>
complete {
NewsService.getNewsList().map {
case stories: List[Story] =>
if(contentType == Some(protoEncode)) {
HttpResponse(entity = HttpEntity(ContentType(protoEncoding), StoryList(stories).toProto().build().toByteArray))
} else
StoryList(stories)
}
}
}
Figured it out.
optionalHeaderValueByName("Accept") { contentType =>
onSuccess(NewsService.getNewsList()) {
case stories: List[Story] => contentType match {
case Some(protoEncodingString) => complete(HttpResponse(entity = HttpEntity(ContentType(protoEncoding), StoryList(stories).toProto().build().toByteArray)))
case _=> complete(StoryList(stories))
}
}
}

How to merge the output of an Observable that emits Observables without breaking the operator chain?

The context is using Couchbase to implement a REST CRUD service on a 2-level document store. The data model is an index document pointing to zero or more item documents. The index document is retrieved as an Observable using an asynchronous get. This is followed by a .flatMap() that retrieves zero or more IDs for each item document. The async get returns an Observable, so now the Observable I'm creating is Observable>. I want to chain a .merge() operator that will take "an Observable that emits Observables, and will merge their output into the output of a single Observable" to quote the ReactiveX documentation :) Then I will .subscribe() to that single Observable to retrieve item documents. The .merge() operator has a many signatures, but I can't figure out how to use it in a chain of operators as follows:
bucket
.async()
.get(id)
.flatMap(
document -> {
JsonArray itemArray = (JsonArray) document.content().get("item");
// create Observable that gets and emits zero or more
// Observable<Observable<JsonDocument>> ie. bucket.async().gets
Observable<Observable<JsonDocument>> items =
Observable.create(observer -> {
try {
if (!observer.isUnsubscribed()) {
itemArray.forEach(
(jsonObject) -> {
String itemId = ((JsonObject)jsonObject).get("itemid").toString();
observer.onNext(
bucket.async().get(itemId)
);
}
}
);
observer.onCompleted();
}
} catch (Exception e) {
observer.onError(e);
}
}
);
return items;
},
throwable -> {
// error handling omitted...
return Observable.empty();
},
() -> {
// on complete processing omitted...
return null;
}
)
.merge( ???????? )
.subscribe(
nextItem -> {
// do something with each item document...
},
throwable -> {
// error handling omitted...
},
() -> {
// do something else...
}
);
EDIT:
You probably guessed I'm a reactive newbie. The answer from #akarnokd helped me realise what I was trying to do was dumb. The solution is to merge the emissions from the items Observable<Observable<JsonDocument>> inside the document closure and return the result of that. This emits the resulting JsonDocuments from the flatMap:
bucket
.async()
.get(id)
.flatMap(
document -> {
JsonArray itemArray = (JsonArray) document.content().get("item");
// create Observable that gets and emits zero or more
// Observable<Observable<JsonDocument>> ie. bucket.async().gets
Observable<Observable<JsonDocument>> items =
Observable.create(observer -> {
try {
if (!observer.isUnsubscribed()) {
itemArray.forEach(
(jsonObject) -> {
String itemId = ((JsonObject)jsonObject).get("itemid").toString();
observer.onNext(
bucket.async().get(itemId)
);
}
}
);
observer.onCompleted();
}
} catch (Exception e) {
observer.onError(e);
}
}
);
return Observable.merge(items);
},
throwable -> {
// error handling omitted...
return Observable.empty();
},
() -> {
// on complete processing omitted...
return null;
}
)
.subscribe(
nextItem -> {
// do something with each item document...
},
throwable -> {
// error handling omitted...
},
() -> {
// do something else...
}
);
Tested and works :)
Due to expressive limits of Java, we can't have a parameterless merge() operator that can be applied on an Observble<Observable<T>>. It would require extension methods such as in C#.
The next best thing is to do an identity flatMap:
// ...
.flatMap(document -> ...)
.flatMap(v -> v)
.subscribe(...)
You can call toList() to collect all emitted items into one list. I've not tested it but what about something like this:
bucket.async()
.get(id)
.flatmap(document -> { return Observable.from((JsonArray)document.content().get("item")})
.flatMap(bucket::get)
.toList()
.subscribe(results -> /* list of documents */);