How can I get nested refs in OpenAPI? - openapi

I'm generating an API in Stoplight and I'm pretty new at it so I'll describe what I'd like to do in words first.
This is a contrived example but here goes. I have a User object with properties: ID, Name, Password, and an array of EmailAddress objects. The EmailAddress object has the following properties: ID, Address, IsPrimary.
When I create my schema for both User and EmailAddress, I'm leaving the ID fields off. The reason I'm doing this is because in the POST for /users, the ID won't be included for either object type in the request body. So the request for the POST looks like so (responses omitted for now):
post:
summary: ''
operationId: post-users
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/User'
So far so good. But in the response, I'd like to return the full User object with an ID attached. Easy enough, I says (requestBody omitted):
post:
summary: ''
operationId: post-users
responses:
'201':
description: Created
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/User'
- $ref: '#/components/schemas/Id'
(where the Id schema contains just a string property named id)
Where I'm confused is for the EmailAddress objects. I'd like to return those with IDs attached to them but I don't know how to "inject" the IDs in there. I.e. I want to send a User without an ID and with an array of EmailAddress objects also without IDs. But I want to return a User with an ID and with an array of EmailAddress objects with IDs.
Do I need to create a separate UserRequest and UserResponse schema in this case, one with EmailAddress objects with IDs and one without?
Larger question: is returning the entire object in a POST a common thing? I've seen references that say to just return the ID of the new object and others to say return the entire thing to save an extra call to the server to GET it again after the POST completes.

Related

Using another table field as owner field for DataStore Authentication

Which Category is your question related to?
DataStore (GraphQL API)
Amplify CLI Version
7.6.23
What AWS Services are you utilizing?
DataStore (GraphQL API)
Provide additional details e.g. code snippets. Be sure to remove any sensitive data.
I am trying to build a service where people can buy subscriptions to certain "Persons" and consume their information. I want to restrict people so that they can only access the data of a certain medium when they are subscribed to it.
Here is the basic structure:
type Post #model {
id: ID!
text: String!
personID: ID! #index(name: "byPerson")
person: Person! #belongsTo(fields: ["personID"])
}
type Person #model {
id: ID!
name: String!
posts: [Post] #hasMany(indexName: "byPerson", fields: ["id"])
}
type Subscription #model {
id: ID!
personID: ID! #index(name: "byPerson")
person: Person! #belongsTo(fields: ["personID"])
userSub: String! // or whatever data we need to reference the user
}
So we have Subscriptions to Persons and Persons can have multiple posts. It is not necessary to fetch a Person if you want to fetch the Posts that a user should be able to see.
What should be possible:
Users should only be able to fetch the posts of the persons that they are subscribed to. There are two ways that I can think of doing but they all require me to change/update data. Since all the data is present, I am not a fan of such solutions.
Solution #1:
Add a group to each user, attach it to the post and add the user to the group as soon as he subscribed
type Post #model #auth(rules: [{ allow: groups, groupsField: "groups" }]) {
id: ID!
text: String!
personID: ID! #index(name: "byPerson")
person: Person! #belongsTo(fields: ["personID"])
groups: String!
}
Not a fan, it requires me to create a group each time a Person is created and I basically have duplicated information here with each post.
Solution #2:
Use an owner field and attach the user as soon as he subscribes
type Post #model #auth(rules: [{ allow: owner, ownerField: "subscribers" }]) {
id: ID!
text: String!
personID: ID! #index(name: "byPerson")
person: Person! #belongsTo(fields: ["personID"])
subscribers: [String]
}
Not a fan as well, I need to edit all the postings as soon as a user subscribes/cancels his subscriptions. The margin of error and amount of calculations here could be huge
I have thought about using a custom resolver (no idea if that works, I don't fully understand it yet) or a custom lambda auth check. The custom lambda auth check causes some issues in the frontend with DataStore. Apparently I need to manually refresh the token for the API or something like that.
What do I want to do?
I would love to use the subscription userSub field as an owner field for the posts. Is that possible (with DataSync) in any way?

RESTful API naming convention

I have two collections: persons and pets. A person may have as many pets as they want.
Person document:
person {
id: person-id
data: person-info
}
Pet document:
pet {
id: pet-id
data: pet-info
personId: person-id
}
This is my API naming design
GET all pets from a person: /api/pets/:personId
GET all pets with condition: /api/pets/:personId?age_greater_than=4
POST create new pet: /api/pets with the request body that contains person-id
PUT update pet info: /api/pets/:petId with the request body that contains person-id and updated info
DELETE delete pet: /api/pets/:personId with request body that contains pet-ids
Is there something wrong with my API naming convention and how can this be improved? I think that passing person-id directly to /api/pets is kind of weird.
Naming conventions can be found here: https://restfulapi.net/resource-naming/
Please also check again HTTP methods and REST.
Basic concept is that URLs represent resources and HTTP methods you apply to those URLs indicate what you want to do with those resources.
GET - Read resource(s)
PUT/POST - Create resource(s)
PATCH - Update resource(s)
DELTE - Delete resource(s)
So maybe you could use PATCH instead of PUT for updating resources if you only change a part of the resource's attributes.
Also in the DELETE example you should use the pet id as path variable and not person id.
For getting all pets of a person, I think /api/person/id/pets could be more straightforward as /api/pets/personid because when you see the URL /api/pets/23 you don't know if it is pet 23 or all pets of person with id 23.
Think about the best practices again (just use a search engine of your choice) ;)

Reuse OpenAPI/Swagger definition with different validation rules?

Let’s say I have a REST API with a User resource. There are three methods that work with the User resource: POST to create, GET to download and PATCH to change. All methods operate on the same User type, but have different required properties – when creating the user using POST, all request fields except the user ID are mandatory, when downloading using GET, all response fields are mandatory including the ID, when changing using PATCH, all request fields are optional and nullable.
Can I succintly describe this in OpenAPI/Swagger? I would like to describe the User type just once and then only say which fields are required/nullable for each method. A bit like this, in pseudocode:
definitions:
User:
properties:
id:
type: integer
name:
type: string
…
paths:
/users
post:
request:
schema: User
required: [name, …]
nullable: […]
response:
schema: User
required: [id, name, …]
nullable: […]
/users/{id}:
get:
response:
schema: User
required: [id, name, …]
nullable: […]
patch:
request:
schema: User
required: []
nullable: [name, …]
This way I would not have to repeat all the field definitions while having the particular constraints described for each method. Is that possible? Does it make sense?

RESTful principles for "magic fields"

In a RESTful API backend for a mobile app client we have a resource, User, with the database field birthday.
When Sam is authenticated an request his own profile, GET /user/1, we receive a resource like this:
{
id: 1,
name: "Sam Smith",
birthday: "1990-01-01",
age: 27
}
In this scenario, age is merely a convenience for the client, that won't have to calculate Sam's age.
But when a stranger requests Sam's profile, GET /user/1, he receives a slightly different resource:
{
id: 1,
name: "Sam Smith",
age: 27
}
Here, birthday is omitted for privacy, and now age is not just about convenience, it is needed, because it's not possible for the client to calculate age without the birthday.
I am already instructing the client that some fields may be omitted depending on permissions.
But when Sam wants to update his profile, he takes the resource he received before, alters it, and PUT /user/1.
{
id: 1,
name: "Sam Smith",
birthday: "2000-02-02", // Birthday was changed
age: 27 // Should he update the age? It's the same data source as birthday.
}
Server-side I could choose to ignore age, but a client might think they can alter the age field directly. I could forbid the age field on PUT resources, but then the client would have to modify resource schema that it received from the GET request.
What are best principles in a situation like this?
Should the client really calculate the new age in the PUT request, even though it will then be ignored by the server, otherwise returning a HTTP 409 conflict response?

Loopback extending built in User model issues

I am inheriting the builtin User model in my own Customer model. The Customer model is having extra parameters like first-name, last-name etc. To create an User and Customer I am using the following code:
// create a Customer
User.create({
email: email,
password: userPassword,
cellnumber: cellDetails.cellnumber
},
function (error, userDet) {
I get an id in response to this call: 59c4c5845dc8de4730645963. But when I am trying to get the account by id i.e. accounts/{id} and pass it the above id, it gives the following error:
the "Unknown \"customer\" id \"59c4c5845dc8de4730645963\"."
So this means that id of the User model and Customer model are somehow not same. How do I resolve this ? Also, in the mongo db database all the properties are getting visible under the User model and not under the Customer model. What am I doing wrong here ? Could anyone let me know.
Thanks
I believe you should create like this: Account.create({ email, password, ... }) , using the Account model rather than User model.
You should use the model you created document with, Account in this case. The thing is, each model works only with it's own MongoDB collection and they are isolated from each other.