I have 3 collections: attributes, products and product_attributes. I am trying to retrieve product attributes with aggregation with nested attribute and product in it. But product and attribute always null
service method:
fun findById(id: String): Mono<ProductAttribute> {
val idField = "_id"
val productId = "productId"
val attributeId = "attributeId"
val attributesDb = "attributes"
val productsDb = "products"
val attributeName = "attribute"
val productName = "product"
fun lookup(from: String, localField: String, name: String) = LookupOperation.newLookup()
.from(from)
.localField(localField)
.foreignField(idField)
.`as`(name)
fun match(id: String) = Aggregation.match(Criteria.where(idField).`is`(ObjectId(id)))
fun unwind(field: String) = Aggregation.unwind(field)
val aggregation = Aggregation.newAggregation(
match(id),
lookup(productsDb, productId, productName),
unwind(productName),
lookup(attributesDb, attributeId, attributeName),
unwind(attributeName)
).withOptions(AggregationOptions.builder().allowDiskUse(true).build())
return operations.aggregate(aggregation, "product_attributes", ProductAttribute::class.java)
.last()
.doOnError { throwable -> logger.error("Failed to get productAttribute", throwable) }
}
ProductAttribute.kt
#Document(collection = "product_attributes")
data class ProductAttribute(
#Id
#JsonProperty("_id")
val id: String? = ObjectId().toHexString(),
#get:Transient #Value("null") val product: Product?,
#get:Transient #Value("null") val attribute: Attribute?,
val attributeId: String,
val productId: String,
val name: String,
val quantity: Int,
val photos: List<String>
) : Serializable
Response:
{
"data": {
"productAttributeId": {
"id": "62a3bff787418b6f837e9150",
"product": null,
"attribute": null,
"name": "string",
"quantity": 1,
"photos": [
"string"
]
}
}
}
As a workaround I have ended up with this result:
I made aggregate method with generic type. It will aggregate to Any type and then map to my specific type.
inline fun <reified T> aggregate(
aggregation: Aggregation,
collectionName: String,
operations: ReactiveMongoOperations,
objectMapper: ObjectMapper
): Flux<T> =
operations.aggregate(aggregation, collectionName, Any::class.java)
.map { data -> objectMapper.convertValue(data, T::class.java) }
I think there is a way to implement custom converter. I will think about this later. I will update answer on success
Related
I figued out how to consume an EntityModel, but so far am unable to consume a CollectionModel (Spring code is using Groovy)
My Class:
#Relation(value = "person", collectionRelation = "people")
class Person {
long id
String firstName
String lastName
}
My Controller:
CollectionModel<Person> getPeople() {
Person person = new Person(
id: 2L,
firstName: 'Mark',
lastName: 'Hamil'
)
Collection<Person> people = Collections.singleton(person)
CollectionModel.of(people)
}
Then I created a service to consume the output of the controller:
CollectionModel<Person> model= this.webClient.get().uri('localhost:8080/api/people')
.retrieve()
.bodyToMono(new TypeReferences.CollectionModelType<Person>())
.block()
List<Person> people = model.content
But the model is blank. I'm not sure what I'm doing incorrectly.
Here is the raw output of localhost:8080/api/people
{
"_embedded": {
"people": [
{
"id": 2,
"firstName": "Mark",
"lastName": "Hamil"
}
]
}
}
After posting over at https://gitter.im/spring-projects/spring-hateoas# and getting some insight, I was able to get this working. Note this is using Groovy.
First, in the controller I made sure the produces was set for hal:
#RequestMapping(method = RequestMethod.GET, produces = "application/hal+json")
CollectionModel<Person> getPeople() {
Link link = linkTo(PersonController).withSelfRel()
List<EntityModel> entityModelList = [this.getPerson()]
}
Where this.getPerson() is:
EntityModel<Person> getPerson() {
Person person = new Person(
id: 2L,
firstName: 'Mark',
lastName: 'Hamil'
)
Link link = linkTo(PersonController).slash(person.id).withSelfRel()
EntityModel<Person> personEntityModel = EntityModel.of(person, link)
personEntityModel
}
Then in the calling api:
Mono<CollectionModel<EntityModel<Person>>> personCollectionModel = this.webClient
.get()
.uri('localhost:8080/uswf-api/people')
.accept(MediaTypes.HAL_JSON)
.exchange()
.flatMap { response ->
if (response.statusCode().isError()) {
System.out.println('Error')
} else {
response.bodyToMono(new ParameterizedTypeReference<CollectionModel<EntityModel<Person>>>() {})
}
}
Then I can use this result by subscribing to the result:
personCollectionModel.subscribe( { collectionModel -> collectionModel.content })
My IDE (Phpstorm with JS GraphQL) is giving me the title error for my schema.
I'm new to GraphQL, what should the query be set to if the actual query operation only has a mutation at the root level?
Below is an actual query taken out of a (Shopify) tutorial for their GraphQL API. I'm copying my local schema definition below which attempted to accommodate its shape.
As you can see, The query is entirely nested in a mutation so I don't know what a query definition at the root level should even have.
// graphql.ts
import "isomorphic-fetch";
const buildPricingPlanQuery = (redirectUrl: string) => `mutation {
appSubscribeCreate(
name : "Plan 1"
returnUrl : "${redirectUrl}"
test : true
lineItems : [
{
plan : {
appUsagePricingDetails : {
cappedAmount : {
amount : 10
, currencyCode : USD
}
terms : "Up to 50 products"
}
}
}
{
plan : {
appRecurringPricingDetails : {
price : {
amount : 10
, currencyCode : USD
}
terms : "some recurring terms"
}
}
}
]
)
{
userErrors {
field
message
}
confirmationUrl
appSubscription {
id
}
}
}`;
export const requestSubscriptionUrl = async (ctx: any, accessToken: string, shopDomain: string) => {
const requestUrl = `https://${shopDomain}/admin/api/2019-10/graphql.json`;
const response = await fetch(requestUrl, {
method : 'post'
, headers : {
'content-type' : "application/json"
, 'x-shopify-access-token' : accessToken
},
body : JSON.stringify({query: buildPricingPlanQuery(`https://${shopDomain}`)})
});
const responseBody = await response.json();
const confirmationUrl = responseBody
.data
.appSubscriptionCreate
.confirmationUrl;
return confirmationUrl;
};
// pricingSchema.graphql
# ------------ Minor Types
enum CurrencyCode {
USD
EUR
JPY
}
type cappedAmount {
amount: Int
currencyCode : CurrencyCode
}
type appUsagePricingDetails {
cappedAmount: cappedAmount
}
input PlanInput {
appUsagePricingDetails: cappedAmount
terms: String
}
type userErrors {
field: String
message: String
}
type appSubscription {
id: Int
}
# ------------ Major Type and Schema definition
type PricingPlan {
appSubscribeCreate(
name: String!
returnUrl: String!
test: Boolean
lineItems: [PlanInput!]!
): String
userErrors: userErrors
confirmationUrl: String
appSubscription: appSubscription
}
schema {
mutation: PricingPlan
}
The error you see is referring to this stipulation of the GraphQL specification:
The query root operation type must be provided and must be an Object type.
There have been a couple proposals to remove this restriction, but as of the latest (June 2018) spec, a schema is considered invalid if there is no Query type. The spec also states that Object types (including Query) cannot be empty.
My advice: Just add a simple query type, such as
type Query {
ping: String #deprecated(reason: "https://stackoverflow.com/questions/59868942/graphql-a-schema-must-have-a-query-operation-defined")
}
If the spec gets updated, you can remove it later :)
I am receiving the following error and not sure why. The error also shows me I am not getting the _id returned either.
{
"timestamp": "2018-10-28T09:45:26.129+0000",
"status": 500,
"error": "Internal Server Error",
"message": "failed to map source [ {\"meta_description\":\"Harry ollectables gifts\",\"body\":\"About us sDesign:\",\"title\":\"Harry Potter-Harry Potter nyl\",\"meta_keywords\":\"Harry Potter,\"}] to class Result",
"path": "/search/harry%20potter" }
So in kibana(Elasticsearch) if I query the data it looks like:
{
"_index": "burf",
"_type": "pages",
"_id": "https://www.ebay.ca/sns",
"_score": 15.293041,
"_source": {
"meta_description": "With nearly one million Stores on eBay, you're sure to find your version of perfect.",
"body": "Skip to main ",
"title": "Search eBay Stores | eBay",
"meta_keywords": ""
}
},
My model in Spring Boot looks like:
#org.springframework.data.elasticsearch.annotations.Document(indexName = "burf", type = "pages")
data class Result(#Id val id: String,
val title: String,
val body: String,
val meta_description: String?,
val meta_keywords: String?) {
}
So finding another example: the following model fixed this, however, I still can't get the score?
#Document(indexName = "burf", type = "pages")
class Result {
#Id
var id: String? = null
var score: Float = 0.0f
var title: String? = null
var body: String? = null
var meta_description: String? = null
var meta_keywords: String? = null
}
I have the following arrangement:
const Person = function(name, age, interest) {
this.name = name;
this.age = age;
this.interest = interest;
}
const people = [
new Person("rajat", 29, ['prog','food','gym']),
new Person("ravi", 23, ['travel', 'cook', 'eat']),
new Person("preeti", 19, ['comedy', 'arts', 'beauty'])
];
const schema = buildSchema(`
type Person {
name: String,
age: Int,
interest: [String]
},
type Query {
hello: String,
giveTen(input: Int!): Int,
person(name: String!): Person!,
}
`);
const root = {
hello: () => 'Hello World',
giveTen: (args) => args.input * 10,
person: (args) => people.filter(item => item.name === args.name),
};
When I run the following query:
query PersonDetails {
person(name: "rajat") {
name
age
interest
}
}
I get bunch of nulls, when there clearly is matching data in people array.
{
"data": {
"person": {
"name": null,
"age": null,
"interest": null
}
}
}
What you return inside your resolver needs to match the type for that particular field. Inside your schema, you've specified the Root Query field person should return a Person type, not a List (array) of that type.
Array.prototype.filter() always returns an array.
If you want to return a single object from people, you should use Array.prototype.find() instead, which will return the first element that matches the test (or null if none are found).
If you want to return all possible matches, you'll need to change your schema to reflect that (change the return type from Person to [Person]). Then you can keep using filter and it should work as expected.
I'm testing Spring Data REST.
I was following this tutorial to understand how it works.
The code so far is simple as:
#Entity
#SequenceGenerator(name="my_seq", sequenceName="user_id_seq")
#Table(name = "employees")
data class Employee (#Id #GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "my_seq") val id: Long? = null,
val name: String = "defConstructorHell")
and when I ask with GET I obtain the following json:
{
"_embedded" : {
"employees" : [ {
"name" : "ciao",
"_links" : {
"self" : {
"href" : "http://localhost:5000/api/employees/1"
},
"employee" : {
"href" : "http://localhost:5000/api/employees/1"
}
}
} ]
},
"_links" : {
"self" : {
"href" : "http://localhost:5000/api/employees{?page,size,sort}",
"templated" : true
},
"profile" : {
"href" : "http://localhost:5000/api/profile/employees"
}
},
"page" : {
"size" : 20,
"totalElements" : 1,
"totalPages" : 1,
"number" : 0
}
}
Which is totally fine but I'm using a js framework that requires the header X-Total-Count.
Do you know if there is a way of doing it wi Spring Data REST?
Eventually I added a controller that add the requested header:
#RepositoryRestController
class EmployeeController #Autowired constructor(val repo: EmployeeRepository) {
#RequestMapping(method = arrayOf(GET),
path = arrayOf("employees"))
#ResponseBody
fun getEmployees(#RequestParam("_sort", required = false, defaultValue = "id") _sort: String,
#RequestParam("_order", required = false, defaultValue = "DESC") _order: String,
#RequestParam("_start", required = false, defaultValue = "0") _start: Int,
#RequestParam("_end", required = false, defaultValue = "20") _end: Int): ResponseEntity<MutableIterable<Employee>> {
val pr = PageRequest(_start, 20, Sort.Direction.valueOf(_order), _sort)
val result = repo.findAll(pr)
val headers = HttpHeaders()
headers.add("X-Total-Count", result.count().toString())
return ResponseEntity(result.content, headers, OK)
}
}