Yii2 REST choosing fields in extrafields - rest

From the yii2 definite guide:
public function fields()
{
return ['id', 'email'];
}
public function extraFields()
{
return ['profile'];
}
the request with http://localhost/users?fields=id,email&expand=profile may return the following JSON data:
[
{
"id": 100,
"email": "100#example.com",
"profile": {
"id": 100,
"age": 30,
}
},
...
]
How can I tune extraFields (or maybe something else) to get only one field (for example, age) in profile section of response in this sample?

This feature will be added in Yii 2.0.14
https://github.com/yiisoft/yii2/pull/14219
Example API request will be work like this: users?fields=id,email&expand=profile.age
Edit: For now you can use something like this:
public function extraFields()
{
return [
'profileAge' => function($item){
return $item->profile->age
}
];
}
with request: http://localhost/users?fields=id,email&expand=profileAge

The first thing that came to mind
public function extraFields(){
return [
'profile' => function($item){
return [
'age' => $item->profile->age
];
}
];
}

Related

Multipart/form-data ASP.NET Core 6 backend - 415 Unsupported Media Type

I have a minimal API (or Web API?) that should receive form data (fields, plus a file).
I read that I have to decorate my endpoint with [FromForm] to get the data, but it's not working.
When I submit the form, I get an error (415 unsupported media type)
it doesn't even get to the point where it should print "Received" on the console.
I also tried sending a form-data from postman and the same error comes back (415)
Here is my code:
const onSubmit = async (data: any) => {
setUploading(true);
// Add form values to formdata, including files
let formData = new FormData();
for (let key in data) {
if (key === "file") {
data.file.forEach((f: any) => {
formData.append("file", f.originFileObj);
});
continue;
}
formData.append(key, data[key]);
}
try {
const response = await axios.post("http://localhost:5210/file", formData, { headers: { "Content-Type": "multipart/form-data" } });
message.success("File sent successfully!");
form.resetFields();
} catch (error: any) {
console.log(error.response.data);
}
setUploading(false);
};
using Microsoft.AspNetCore.Mvc;
var builder = Microsoft.AspNetCore.Builder.WebApplication.CreateBuilder(args);
builder.Services.AddCors(options=> options.AddDefaultPolicy(builder => {builder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod();} ));
var app = builder.Build();
app.UseCors();
app.MapPost("/file" ,([FromForm] FormData data)=>{
System.Console.WriteLine("Received");
return data;
});
app.Run();
My FormData class :
public class FormData
{
public string? firstName { get; set; }
public string? lastName { get; set; }
public string? company { get; set; }
public string? sendTo { get; set; }
public IFormCollection? files { get; set;}
}
Found solution here:
.NET 6 Minimal API and multipart/form-data
What i had to do is remove [FromForm] as it looks like it is no longer supported by .Net 6. (https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis?view=aspnetcore-6.0#explicit-parameter-binding)
I also don't need the Model (FormData) class for this to work.
Changing my endpoint to this solved my issue
app.MapPost("/file",(HttpContext ctx)=> {
var data = ctx.Request.Form; // form data is stored here
var files = ctx.Request.Form.Files; // files are stored here
return "Success";
});
the way the data is returned is a bit ugly, so now my next goal is to clean it up, but that is a different topic.
this is what data looks like:
[
{
"key": "firstName",
"value": [
"Mike"
]
},
{
"key": "lastName",
"value": [
"Jones"
]
},
{
"key": "company",
"value": [
"123Net"
]
},
{
"key": "sendTo",
"value": [
"Someone"
]
}
]

Spring-boot data mongoDB query nested list

I'm working on spring-boot-data-mongoDB. I have some issues querying a nested document that has a list of a specific object.
Mock class
#Document
public class Mock {
#Id
private String id;
#Indexed(unique = true)
private String name;
private List<Request> requests;
}
Request class
#Document
public class Request {
#Id
private String id;
private int status;
private String method;
private String endPoint;
private Map<String, Object> response;
private Map<String, Object> body;
private Map<String, String> params;
}
Example JSON
[
{
_id: '53fc6dde-7a534-4b37-a57e-t0bd62f50046',
name: 'mock1',
requests: [
{
status: 200,
method: 'GET',
endPoint: 'status',
response: {},
body: {},
params: {}
}
],
_class: 'com.example.mockserverspring.models.Mock'
},
{
_id: '73fc6dde-7a5b-4b37-a57e-d0bd62f50046',
name: 'tester',
requests: [
{
_id: '802220ea-a1c7-484d-af1b-86e29b540179',
status: 200,
method: 'GET',
endPoint: 'api',
response: {
data: 'GET'
},
body: {
body: 'body'
},
params: {
params: 'params'
}
},
{
_id: 'ff8673d7-01a9-4d6f-a42e-0214a56b227b',
status: 200,
method: 'GET',
endPoint: 'data',
response: {},
body: {
data: 'data'
},
params: {
value: '10'
}
},
{
_id: '7fd5a860-b415-43b0-8115-1c8e1b95c3ec',
status: 200,
method: 'GET',
endPoint: 'status',
response: {},
body: {},
params: {}
}
],
_class: 'com.example.mockserverspring.models.Mock'
}
]
Desired query output : pass in the endPoint, mockName, body, params, and method
Get the mock object of the mockName from the db.
Match endPoint, body, params, method inside the Requests List of the returned mock.
Return the response field from the request that is found matching all the above criteria.
From the above example json :
Passed in values : mockName : tester , method : GET , endPoint : api , body: {body: 'body' }, params: { params: 'params' }
This should return :
response: { data: 'GET' }
It should return if and only if all these criteria matches.
Any queries please let me know.
To perform this search the best is to use a mongoDB aggregation, inside this aggregation we will be able to execute operations step by step.
As you want to query only 1 subdocument within an array, the first operation we must perform is a $unwind of that array. This will separate each subdocument and we can perform our search.
{
"$unwind": "$requests"
}
Now we will introduce the search parameters in $match. We will be able to use as many as we want.
{
"$match": {
"name": "tester",
"requests.method": "GET",
"requests.endPoint": "api",
"requests.body": {
body: "body"
},
"requests.params": {
params: "params"
}
}
}
Finally as we only want the information of a specific field we will use $replaceRoot to format our output.
{
"$replaceRoot": {
"newRoot": "$requests.response"
}
}
Playground
EDIT:
What you trying to match by?
Generally APIs/endpoints when called via REST protocol work like this:
Request => <= Response
So I make a req and I get back a res - whether it's good or not I like it or not I get it back. Whether my params match or not.
What I can't understand is the whole design and what you're trying to match by i.e. which params? How can we match if no values exist in request LIST?
I think there are a lot of questions to be answered first. But here is some help and how I would start designing this:
Freestyle it or use Swagger to first design Req and Resp objects (schemas) - THESE ARE NOT your Requests above. These is the basic API design
Two - define what needs to happen when a request is made, with what arguments and expect what values to do the condition checking with
Define what you expect back - what fields, etc.
Define testing all of the first 3 points above individually and end to end
Then you can use each of the items in Request to test your API with. It's also fair straight forward to pull items in/out of cloud mongo service such as mongodb.com and with express easy to do the REST.

How to return the a formatted response from a mongo query/projection?

I'm trying to create an API to validate a promocode. I have minimal experience with mongo and the backend in general so I'm a bit confused in what is the best approach to do what I'm trying to accomplish.
I have this PromoCode form in the client. When a user types a promocode I would like for my backend to
verify if the code exists in one of the docs.
if it exists then return that code, the value for that code and the couponId
if the code doesn't exist then return an error.
My db is structured like this. The user will type one of those codes inside the codes: []
{
"_id": {
"$oid": "603f7a3b52e0233dd23bef79"
},
"couponId": "rate50",
"value": 50,
"codes": ["K3D01XJ50", "2PACYFN50", "COKRHEQ50"]
},
{
"_id": {
"$oid": "603f799d52e0233dd23bef78"
},
"couponId": "rate100",
"value": 100,
"codes": ["rdJ2ZMF100", "GKAAYLP100", "B9QZILN100"]
}
My route is structure like this:
router.post('/promoCode', (req, res, next) => {
const { promoCode } = req.body;
console.log('this is the req.body.promoCode on /promoCode', promoCode)
if (!promoCode) {
throw new Error('A promoCode needs to be passed')
}
promoCodesModel
.validatePromoCode(req.body.promoCode)
.then((response) => {
console.log('response inside /promoCode', response)
res.status(200).json({ data: response })
})
.catch((error) => {
res.status(400).json({ result: 'nok', error: error })
})
})
The validatePromoCode function is the following:
const validatePromoCode = async (code) => {
try {
let promoCode = await PromoCodesModel.find(
{"codes": code},
{_id: 0, codes: { $elemMatch: { $eq: code }} })
console.log('This is the promocode', promoCode)
return promoCode
} catch (err) {
throw new Error (err.stack)
}
}
All this seems to sort of work since I get the following response when the code is typed correctly
{
"data": [
{
"codes": [
"COKRHEQ50"
]
}
]
}
when typed incorrectly I get
{
"data": []
}
What I would like to get back is. (How can I accomplish this ?). Thanks
// when typed correctly
{
"data": { value: 50, couponId: "rate50", code: "COKRHEQ50" }
}
// when typed incorrectly
{
"error": "this is not valid code"
}
TL;DR: I would like to return a formatted query with specific values from a mongo query or an error object if that value does not exist on the document object.
Ok just figured it out
To be able to get the this responsed (what I wanted):
{
"data": [
{
"codes": [
"K3D01XJ50"
],
"couponId": "rate50",
"value": 50
}
]
}
I ended up having to do this on validatePromoCode
onst validatePromoCode = async (code) => {
try {
let promoCode = await PromoCodesModel.find(
{ codes: code },
{ _id: 0, codes: { $elemMatch: { $eq: code } }, couponId: 1, value: 1 },
)
return promoCode
} catch (err) {
throw new Error(err.stack)
}
}
But is there a better way on doing this ? Thanks

Setting extraFields in rest api yii2

public function fields()
{
return [
'field' => 'field',
];
}
public function extraFields()
{
return [
'users',
];
}
return:
{ "field": "field", "users": { "id": 1, "name": "user" } }
how to exclude id?
public function extraFields()
{
return [
'users' => function($model){
return [
'name' => $model->users->name,
];
}
];
}
return:
{ "field": "field", "users": { "name": null } }
how to fill in the name field correctly or how to customize field output filtering?
Option can be overwrite field() method in User model:
public function fields()
{
$fields = parent::fields();
if ($something) {
unset($fields['id']);
}
return $fields;
}

Distinct Query with Cloudant Connector using Loopback in API Connect/StrongLoop

I am trying to get distinct values for a query using Loopback with a Cloudant Connector, but I haven't found anything about this in the documentation.
e.g. I need a query to turn this:
[
{
rating: "★★★★★"
},
{
rating: "★★★★★"
},
{
rating: "★★★★★"
},
{
rating: "★★★★★"
},
{
rating: "★★★☆☆"
},
{
rating: "★★★☆☆"
}
]
into this:
[
{
rating: "★★★★★"
},
{
rating: "★★★☆☆"
}
]
I'm using the REST API to query my Products model (above is a filtered view of just the rating field). If there is some sort of filter that I can use without modifying the server that I somehow just missed in the documentation, that would be the best choice.
Is there any way I can add a distinct field like:
/Products?filter[fields][rating]=true?distinct=true
or how can I go about solving this?
Also, I've seen another answer talking about adding a remote method to solve this (something like this for mySQL):
Locations.regions = function (cb) {
var ds = Locations.app.datasources.myDS;
var sql = "SELECT DISTINCT region FROM Locations ORDER BY region"; // here you write your sql query.
ds.connector.execute(sql, [], function (err, regions) {
if (err) {
cb(err, null);
} else {
cb(null, regions);
}
});
};
Locations.remoteMethod(
'regions', {
http: {
path: '/regions',
verb: 'get'
},
returns: {
root: true,
type: 'object'
}
}
);
If this would work, how would I implement it with the Cloudant NoSQL DB connector?
Thanks!
If your documents looked like this:
{
"name": "Star Wars",
"year": 1978,
"rating": "*****"
}
You can create a MapReduce view, which emits doc.rating as the key and uses the build-in _count reducer:
function(doc) {
emit(doc.rating,null);
}
When you query this view with group=true, distinct values of rating will be presented with counts of their occurrence in the data set.