Creating REST API in Swift using vapor unable to make a POST request - swift

While learning how to use Swift and Vapor to create a REST api, while testing this API using postman, I am unable to make an API Post request, the error I receive is:
{
"error": true,
"reason": "Value of type 'String' required for key 'email'."
}
I am using a PostgreSQL database reference the model. I feel something is wrong with my model.
Here is the model I am using:
final class Todo: PostgreSQLModel {
var id: Int?
var email: String
var password: String
var name_first: String
var name_last: String
var username: String
init(id: Int?, email: String, name_first: String, name_last: String, username:String, password: String) {
self.id = id
self.email = email
self.name_first = name_first
self.name_last = name_last
self.username = username
self.password = password
}
}

It turns out I was using postman wrong. I was passing things through the header the body my mistake.

Related

How can I fetch a json file using Vapor for my leaf template to show the data?

I have a JSON hosted somewhere and I want to fetch the content, put it in a context for my leaf template to read.
However, I cannot make it work. I get the code to compile, but I get an error in the localhost
{"error":true,"reason":"Unsupported Media Type"}
Can somebody help me please! Happy holidays for all.
struct WebsiteController: RouteCollection {
func boot(routes: RoutesBuilder) throws {
routes.get(use: indexHandler)
}
func indexHandler(_ req: Request) -> EventLoopFuture<View> {
return req.client.get("https://streeteasydaily.s3.us-west-2.amazonaws.com/streeteasy1.json").flatMap { res in
do {
let json = try res.content.decode([Listing].self)
print(json[0].photos[0])
let context = IndexContext(title: "Homepage", listings: json)
return try req.view.render("index", context)
} catch {
// Handle error
print("cayo en error")
return req.eventLoop.makeFailedFuture(error)
}
}
}
}
struct IndexContext: Encodable {
let title: String
let listings: [Listing]
}
Model
final class Listing: Model {
static let schema = "listings" //basically the table name
#ID
var id: UUID?
#Field(key: "address")
var address: String
#Field(key: "description")
var description: String
#Field(key: "photos")
var photos: [String]
init() {}
//to initialize the db
init(id: UUID? = nil, address: String, description: String, photos: [String]) {
self.id = id
self.address = address
self.description = description
self.photos = photos
}
}
//to make acronym conform to CONTENT, and use it in Vapor
extension Listing: Content {}
This error is because the decode is failing to identify all the fields in your JSON to match against those defined in Listing and/or the array of such objects. The filenames must match those in the JSON exactly - i.e. case-sensitive and every field in the structure/model must exist in the JSON. Additional fields in the JSON that are not needed/included in the structure/model are fine.

How do you define multiple UUID in a model

I am using Vapor and Fluent. I want to define a user model like below, but I get an error saying:
Fatal error: Error raised at top level: previousError(server: multiple primary keys for table "users" are not allowed
Is it not possible to define multiple UUIDs in one model?
import Vapor
import FluentPostgresDriver
final class User: Model, Content {
static let schema = "users"
#ID(custom: "id")
var id: Int?
#Field(key: "email")
var email: String
#Field(key: "password")
var password: String
#ID(custom: "public_id")
var public_id: UUID?
init() { }
init(id: Int? = nil,email: String, password:String, public_id: UUID? = nil) {
self.id = id
self.email = email
self.password = password
self.public_id = public_id
}
}
struct CreateUser: Migration {
func prepare(on database: Database) -> EventLoopFuture<Void> {
database.schema("users")
.field("id", .int, .identifier(auto: true))
.field("email", .string)
.field("password", .string)
.field("public_id", .uuid,?????)
.create()
}
.....
}
Instead of marking the public UUID as #ID, just mark it as another #Field (string type is easiest, but you can also do binary [16 bytes] if you're feeling like wearing a propeller beanie), and make it required.
During the create, you'll have to actually invoke a UUID generation function, but that ought to be easy enough.
But, why not make the primary key the UUID, instead of having two identifiers? It takes a little more room in the database, but might be worth avoiding the headache of having several different ids.

Vapor 4: Children relation not eager loaded, use $ prefix to acces

Currently I am working on a school assignment where we have to build an API using Vapor. I have a few basic API calls working and I am trying a bit more advanced API calls now but I can't get this to work.
I have this function addToParty that is being called when the URL /party/join/:partyID is called with a body
{
"id": "CC1FAC6B-A2B3-471C-A488-147300196981",
"username": "string",
"is_ready": true
}
I am trying to find a party by the partyId and add the user to the list of users of the party.
func addToParty (req: Request) throws -> EventLoopFuture<Party.Output> {
guard let id = req.parameters.get("partyID", as: UUID.self) else {
throw Abort(.badRequest)
}
let input = try req.content.decode(Party.JoinParty.self)
return Party.find(id, on: req.db)
.unwrap(or: Abort(.notFound))
.flatMap { element in
element.users.append(User(id: UUID(input.id), username: input.username, is_ready: input.is_ready))
return element.save(on: req.db)
.map{ Party.Output(code: "200") }
}
}
When I try the code above I get the error Fatal error: Children relation not eager loaded, use $ prefix to access: Children<Party, User>(for: [party_id]): file FluentKit/Children.swift, line 33 from the line
element.users.append(User(id: UUID(input.id), username: input.username, is_ready: input.is_ready))
When I comment this line the code runs and I get a return code.
I tried adding the prefix to element.$users and $User but then it complains about not being able to find element.$users and $User in scope.
Party model
import Fluent
import Vapor
final class Party: Model, Content {
static let schema = "parties"
struct JoinParty: Content {
let id: String
let username: String
let is_ready: Bool
}
struct Output: Content {
let code: String
}
#ID(key: .id)
var id: UUID?
#Field(key: "party_code")
var party_code: String
#Field(key: "host_id")
var host_id: UUID
#Field(key: "is_active")
var is_active: Bool
// change to Game when model is made
#Field(key: "selected_games")
var selected_games: [String]?
// change to Setting when model is made
#Field(key: "settings")
var settings: String
#Field(key: "results")
var results: Array<GameResult>?
#Children(for: \.$party)
var users: [User]
init() { }
init(id: UUID? = nil,
party_code: String,
host_id: UUID,
is_active: Bool,
selected_games: [String]? = nil,
settings: String,
results: Array<GameResult>? = nil) {
self.id = id
self.party_code = party_code
self.host_id = host_id
self.is_active = is_active
self.selected_games = selected_games
self.settings = settings
self.results = results
}
}
User model
import Fluent
import Vapor
final class User: Model, Content {
static let schema = "users"
struct Input: Content {
let id: UUID
let username: String
}
struct Output: Content {
let id: String
let username: String
}
#ID(key: .id)
var id: UUID?
#Field(key: "username")
var username: String
#Field(key: "is_ready")
var is_ready: Bool
#OptionalParent(key: "party_id")
var party: Party?
#Children(for: \.$user)
var gameResults: [GameResult]
init() {}
init(id: UUID? = nil, username: String, is_ready: Bool, partyID: UUID? = nil) {
self.id = id
self.username = username
self.is_ready = is_ready
self.$party.id = partyID
}
}
I have a similar function to update a username from a user already working which is almost the same thing.
func update(req: Request) throws -> EventLoopFuture<User.Output> {
let input = try req.content.decode(User.Input.self)
return User.find(input.id, on: req.db)
.unwrap(or: Abort(.notFound))
.flatMap { user in
user.username = input.username
return user.save(on: req.db)
.map { User.Output(id: user.id!.uuidString, username: user.username) }
}
}
Any help would be really appreciated.
Thank you in advance.
Unfortunately, adding children to a parent model is not that intuitive yet. I hope that someday we can get that added, but it's not there yet. Fluent 5 maybe?
Anyway, what you'll need to do instead is create your new User model, passing in the party's ID value to the partyID initializer parameter, and then save the User model.
let user = User(id: UUID(input.id), username: input.username, is_ready: input.is_ready, partyID: element.id)
return user.save(on: request.db)
So your method should end up looking like this:
func addToParty(req: Request) throws -> EventLoopFuture<Party.Output> {
guard let id = req.parameters.get("partyID", as: UUID.self) else {
throw Abort(.badRequest)
}
let input = try req.content.decode(Party.JoinParty.self)
return Party.find(id, on: req.db).unwrap(or: Abort(.notFound)).flatMap { element in
return User(
id: UUID(input.id),
username: input.username,
is_ready: input.is_ready,
partyID: element.id
).save(on: req.db)
}.transform(to: Party.Output(code: "200"))
}

How do I add an additional property to a Model?

I have a Model which conforms to Content:
import Vapor
import Fluent
final class User: Model, Content {
static let schema = "user"
#ID(key: .id)
var id: UUID?
#Field(key: "email")
var email: String
init() { }
init(id: UUID? = nil, email: String) {
self.id = id
self.email = email
}
}
This can be fetched from the database and directly returned from a route, so the client receives the users as JSON.
Now I want to add an additional property to the user, which isn't stored in the database but a fixed value or just added after the user is loaded from the database:
final class User: Model, Content {
// ...
var someOther: String = "hello"
// ...
}
or
final class User: Model, Content {
// ...
var someOther: String? // set later
// ...
}
Now the problem is that this property is never added to the JSON, so the client only gets the user's id and email and NOT the someProperty. How do I fix this?
When you want to send or receive data from your client that has a different representation from what your model has, it's common practice to define a custom type that you can then convert to and from the model type.
Given this, you would define a new User.Response type that conforms to Content instead of the User model, and return that from your route instead.
extension User {
struct Response: Content {
let id: UUID
let email: String
let otherValue = "Hello World"
}
}
func user(request: Request) throws -> EventLoopFuture<User.Response> {
return User.find(UUID(), on: request.db).flatMapThrowing { user in
guard let id = user.id else {
throw Abort(.internalServerError, "User is missing ID")
}
return User.Response(id: id, email: user.email)
}
}
The reason the additional value that you added wasn't encoded from your model is because the default encoder for model types iterates over the field properties and encodes those properties, so if you have a property that doesn't use one of the ID, Field, Parent, Children, or other field property wrappers, it won't be encoded.

Using vapor-fluent to upsert models

I am currently struggling with doing an upsert with vapor/fluent. I have a model something like this:
struct DeviceToken: PostgreSQLModel {
var id: Int?
var token: String
var updatedAt: Date = Date()
init(id: Int? = nil, token: String, updatedAt: Date = Date()) {
self.id = id
self.token = token
self.updatedAt = updatedAt
}
}
struct Account: PostgreSQLModel {
var id: Int?
let username: String
let service: String
...
let deviceTokenId: DeviceToken.ID
init(id: Int? = nil, service: String, username: String, ..., deviceTokenId: DeviceToken.ID) {
self.id = id
self.username = username
....
self.deviceTokenId = deviceTokenId
}
}
From the client something like
{
"deviceToken": {
"token": "ab123",
"updatedAt": "01-01-2019 10:10:10"
},
"account": {
"username": "user1",
"service": "some service"
}
}
is send.
What I'd like to do is to insert the new models if they do not exist else update them. I saw the create(orUpdate:) method however this will only update if the id is the same (in my understanding). Since the client does not send the id i am not quite sure how to handle this.
Also I can't decode the model since the account is send without the deviceTokenId and therefore the decoding will fail. I guess I can address the latter problem by overriding NodeCovertible or by using two different models (one for decoding the json without the id and the actual model from above). However the first problem still remains.
What I exactly want to do is:
Update a DeviceToken if an entry with token already exists else create it
If an account with the combination of username and service already exists update its username, service and deviceTokenId else create it. DeviceTokenId is the id returned from 1.
Any chance you can help me out here ?
For everyone who is interested:
I solved it by writing an extension on PostgreSQLModel to supply an upsert method. I added a gist for you to have a look at: here.
Since these kind of links sometimes are broken when you need the information here a quick overview:
Actual upsert implementation:
extension QueryBuilder
where Result: PostgreSQLModel, Result.Database == Database {
/// Creates the model or updates it depending on whether a model
/// with the same ID already exists.
internal func upsert(_ model: Result,
columns: [PostgreSQLColumnIdentifier]) -> Future<Result> {
let row = SQLQueryEncoder(PostgreSQLExpression.self).encode(model)
/// remove id from row if not available
/// otherwise the not-null constraint will break
row = row.filter { (key, value) -> Bool in
if key == "id" && value.isNull { return false }
return true
}
let values = row
.map { row -> (PostgreSQLIdentifier, PostgreSQLExpression) in
return (.identifier(row.key), row.value)
}
self.query.upsert = .upsert(columns, values)
return create(model)
}
}
Convenience methods
extension PostgreSQLModel {
/// Creates the model or updates it depending on whether a model
/// with the same ID already exists.
internal func upsert(on connection: DatabaseConnectable) -> Future<Self> {
return Self
.query(on: connection)
.upsert(self, columns: [.keyPath(Self.idKey)])
}
internal func upsert<U>(on connection: DatabaseConnectable,
onConflict keyPath: KeyPath<Self, U>) -> Future<Self> {
return Self
.query(on: connection)
.upsert(self, columns: [.keyPath(keyPath)])
}
....
}
I solved the other problem I had that my database model could not be decoded since the id was not send from the client, by using a inner struct which would hold only the properties the client would send. The id and other database generated properties are in the outer struct. Something like:
struct DatabaseModel: PostgreSQLModel {
var id: Int?
var someProperty: String
init(id: Int? = nil, form: DatabaseModelForm) {
self.id = id
self.someProperty = form.someProperty
}
struct DatabaseModelForm: Content {
let someProperty: String
}
}