Swift Vapor add additional info to custom response - swift

I've two Models, Trip and Location. I would return a custom response with some field of trip and the number of Location that has the tripID equal to id of Trip. There is my code(not working). The field locationCount is always empty.
func getList(_ request: Request)throws -> Future<Response> {
let deviceIdReq = request.parameters.values[0].value
let queryTrips = Trip.query(on: request).filter(\.deviceId == deviceIdReq).all()
var tripsR = [TripCustomContent]()
var trips = [Trip]()
return queryTrips.flatMap { (result) -> (Future<Response>) in
trips = result
var count = 0
for t in trips {
let tripIdString = String(t.id!)
let v = Location.query(on: request).filter(\.tripID == tripIdString).count().map({ (res) -> Int in
return res
})/*.map{ (result) -> (Int) in
count = result
return result
}*/
let tripCustomContent = TripCustomContent.init(startTimestamp: t.startTimestamp, endTimestamp: t.endTimestamp, deviceId: t.deviceId, locationCount: v)
tripsR.append(tripCustomContent)
}
let jsonEncoder = JSONEncoder()
let data = try jsonEncoder.encode(tripsR)
let response = HTTPResponse.init(status: .ok, version: HTTPVersion.init(major: x, minor: y), headers: HTTPHeaders.init(), body: data)
let finalResponse = Response.init(http: response, using: request)
return try g.encode(for: request)
}
}
and this is my custom content struct:
struct TripCustomContent: Encodable {
var startTimestamp: String?
var endTimestamp: String?
var deviceId: String
var locationCount: Future<Int>
}
any suggestions?

You're trying to use a value which isn't available yet. When you're returning a Future, you aren't returning the value inside it.
So you want your TripCustomContent to be like this (use in vapor Content instead of Codable:
struct TripCustomContent: Content {
var startTimestamp: String?
var endTimestamp: String?
var deviceId: String
var locationCount: Int
}
You queried the Trip correctly, but not the Location. You could maybe try something like this:
return queryTrips.flatMap { trips -> Future<[TripCustomContent]> in
let tripIds = trips.map({ String($0.id!) })
return Location.query(on: request).filter(\.tripID ~~ tripIds).all().map { locations in
return trips.map { trip in
let locationCount = locations.filter({ $0.tripId == String(trip.id!) }).count
return TripCustomContent(... locationCount: locationCount)
}
}
}
What did I do here?
Map the trips to their tripIds to get an array of tripIds
Get all locations with a tripId of one of the tripIds in the above array
Map each of the trips to an instance of TripCustomContent, using the locations of the database filtered by tripId
Finally, you don't need to encode the JSON yourself, just return objects conforming Content:
func getList(_ request: Request) throws -> Future<[TripCustomContent]>
The above could be a solution to your strategy. But maybe you take a look at relations if they can be a more efficient, easier and faster way.

Related

Converting app from SQLite.swift to GRDB.swift Second Question

I am continuing the conversion of a number of my apps from SQLite.sift to GRDB.swift.
I converted my structs to add Codable, FetchableRecord so as to have them work better with GRDB. This was suggested in a reply to my first post on this subject.
struct FigureList: Codable, FetchableRecord
{
let figID: Int64
var notes: String?
var theDescription: String
var obrienNum: String
var manufNum: String?
var catagory: String?
}
This is the piece of code I'm looking for help to redo so it will work with GRDB. My apps use this type of code to build arrays from the database table. Pretty standard process.
static func getFigureList() -> [FigureList]
{
var theArray = [FigureList]()
let theTable = Table(gTheCollection)
let figID = Expression<Int64>("FigureID")
let notes = Expression<String>("Notes")
let theDescription = Expression<String>("theDescription")
let obrienNum = Expression<String>("ObrienNum")
let manufNum = Expression<String>("ManufNum")
let theCatagory = Expression<String>("Category")
do {
for figures in try Database.shared.databaseConnection!.prepare(theTable.order(obrienNum)) {
theArray.append(FigureList(figID: figures[figID],
notes: figures[notes],
theDescription: figures[theDescription],
obrienNum: figures[obrienNum],
manufNum: figures[manufNum],
catagory: figures[theCatagory]))
}
} catch {
print("Fetching figure list failed: \(error)")
}
return theArray
}
This is what I have come up with so far. It doesn't produce any warnings or errors but then again I'm pretty sure it's not total correct. Again, thanks in advance for any help.
static func getFigList() -> [FigureList]
{
var theArray = [FigureList]()
let theTable = gTheCollection
let figID = Column("FigureID")
let notes = Column("Notes")
let theDescription = Column("theDescription")
let obrienNum = Column("ObrienNum")
let manufNum = Column("ManufNum")
let theCatagory = Column("Category")
do {
try Database.shared.databaseConnection!.read { db in
let figures = try Row.fetchOne(db, sql: "SELECT * FROM = ? ORDER BY ObrienNum", arguments: [theTable])
theArray.append(FigureList.init(figID: figures![figID],
notes: figures![notes],
theDescription: figures![theDescription],
obrienNum: figures![obrienNum],
manufNum: figures![manufNum],
catagory: figures![theCatagory]))
}
} catch {
print("Fetching figure list failed: \(error)")
}
return theArray
}
I converted my structs to add Codable, FetchableRecord so as to have them work better with GRDB.
Good. Now it's very simple:
static func getFigList() throws -> [FigureList] {
return try Database.shared.databaseConnection!.read { db in
// SELECT * FROM FigureList ORDER BY ObrienNum
return FigureList.order(Column("ObrienNum")).fetchAll(db)
}
}

decoding an array of objects in swift

I have an array of objects
{"total_rows":5,"offset":0,"rows":[
{"id":"index","key":"index","value":{"rev":"4-8655b9538706fc55e1c52c913908f338"}},
{"id":"newpage","key":"newpage","value":{"rev":"1-7a27fd343ff98672236996b3fe3abe4f"}},
{"id":"privacy","key":"privacy","value":{"rev":"2-534b0021f8ba81d09ad01fc32938ce15"}},
{"id":"secondpage","key":"secondpage","value":{"rev":"2-65847da61d220f8fc128a1a2f1e21e89"}},
{"id":"third page","key":"third page","value":{"rev":"1-d3be434b0d3157d7023fca072e596fd3"}}
]}
that I need too fit in struct and then decode in swift. My current code is:
struct Index: Content {
var total_rows: Int
var offset: Int
// var rows: [String: String] // I don't really know what I am doing here
}
and the router (using vapor)
router.get("/all") { req -> Future<View> in
let docId = "_all_docs"
print(docId)
let couchResponse = couchDBClient.get(dbName: "pages", uri: docId, worker: req)
guard couchResponse != nil else {
throw Abort(.notFound)
}
print("one")
return couchResponse!.flatMap { (response) -> EventLoopFuture<View> in
guard let data = response.body.data else { throw Abort(.notFound) }
print(data)
let decoder = JSONDecoder()
let doc = try decoder.decode(Index.self, from: data)
let allDocs = Index(
total_rows: doc.total_rows,
offset: doc.offset
//rows: doc.rows
)
print("test after allDocs")
return try req.view().render("index", allDocs)
}
}
to summarise all is fine for the first level (total rows and offset are int and properly decoded) but how can I include in my structure the rows: array and assign thee parsed values to it ?
You're on the right road, you just need to keep going.
struct Index: Decodable {
var total_rows: Int
var offset: Int
var rows: [Row]
}
Then you define a Row:
struct Row: Decodable {
var id: String
var key: String
var value: Value
}
It's not really clear what a Value is in this context, but just to keep the structure.
struct Value: Decodable {
var rev: String
}
And that's all.
let index = try JSONDecoder().decode(Index.self, from: jsonData)

how to get single variable name from struct

I have a core data framework to handle everything you can do with coredata to make it more cooperateable with codable protocol. Only thing i have left is to update the data. I store and fetch data by mirroring the models i send as a param in their functions. Hence i need the variable names in the models if i wish to only update 1 specific value in the model that i request.
public func updateObject(entityKey: Entities, primKey: String, newInformation: [String: Any]) {
let request = NSFetchRequest<NSFetchRequestResult>(entityName: entityKey.rawValue)
do {
request.predicate = NSPredicate.init(format: "\(entityKey.getPrimaryKey())==%#", primKey)
let fetchedResult = try delegate.context.fetch(request)
print(fetchedResult)
guard let results = fetchedResult as? [NSManagedObject],
results.count > 0 else {
return
}
let key = newInformation.keys.first!
results[0].setValue(newInformation[key],
forKey: key)
try delegate.context.save()
} catch let error {
print(error.localizedDescription)
}
}
As you can see the newInformation param contains the key and new value for the value that should be updated. However, i dont want to pass ("first": "newValue") i want to pass spots.first : "newValue"
So if i have a struct like this:
struct spots {
let first: String
let second: Int
}
How do i only get 1 name from this?
i've tried:
extension Int {
var name: String {
return String.init(describing: self)
let mirror = Mirror.init(reflecting: self)
return mirror.children.first!.label!
}
}
I wan to be able to say something similar to:
spots.first.name
But can't figure out how
Not sure that I understood question, but...what about this?
class Spots: NSObject {
#objc dynamic var first: String = ""
#objc dynamic var second: Int = 0
}
let object = Spots()
let dictionary: [String: Any] = [
#keyPath(Spots.first): "qwerty",
#keyPath(Spots.second): 123,
]
dictionary.forEach { key, value in
object.setValue(value, forKeyPath: key)
}
print(object.first)
print(object.second)
or you can try swift keypath:
struct Spots {
var first: String = ""
var second: Int = 0
}
var spots = Spots()
let second = \Spots.second
let first = \Spots.first
spots[keyPath: first] = "qwerty"
spots[keyPath: second] = 123
print(spots)
however there will be complex (or impossible) problem to solve if you will use dictionary:
let dictionary: [AnyKeyPath: Any] = [
first: "qwerty",
second: 123
]
you will need to cast AnyKeyPath back to WritableKeyPath<Root, Value> and this seems pretty complex (if possible at all).
for path in dictionary.keys {
print(type(of: path).rootType)
print(type(of: path).valueType)
if let writableKeyPath = path as? WritableKeyPath<Root, Value>, let value = value as? Value { //no idea how to cast this for all cases
spots[keyPath: writableKeyPath] = value
}
}

Filter data From vapor client get

i'm using vapor's client to fetch a get request.
func sendGetRequest(req: Request) throws -> Future<Response> {
let client = try req.make(FoundationClient.self)
return client.get("http://example.vapor.codes/json", headers: ["Accept-Language" : "ar"])
.map(to: Response.self, { clientResponse in
let response = req.makeResponse()
response.http.status = clientResponse.http.status
response.http.body = clientResponse.http.body
return response
})
}
this returns all the json data , i want to filter it to just return 2 attributes, for example in this case (dict,number)
i've created a model for the data
struct ExampleData: Codable {
// var array : [Int]
var dict : [String : String]
var number : Int
// var string : String
}
the function expects me to return a Future< Response>, but if i change it to Future< ExampleData> and set the mapping to .map(to: ExampleData.self ..)
i get
Cannot convert return expression of type 'Response' to return type
'TodoController.ExampleData'
i figured it out
func sendGetRequest(req: Request) throws -> Future<ExampleData> {
let client = try req.make(Client.self)
let ans = client.get("http://example.vapor.codes/json", headers: ["Accept-Language" : "ar"]).flatMap { exampleResponse in
return try exampleResponse.content.decode(ExampleData.self)
}
return ans
}

What's the best way to return a collection of response representable objects in Swift Vapor?

Context:
Recently, I've decided to take up Swift server side development because I think the Vapor framework is extremely cool. I've gotten a bit stuck while experimenting and would like some advice on templating with leaf and vapor.
I've reviewed the documentation several times when it comes to rendering views. Rendering a templated view with variables requires the name of the leaf template and a Response Representable node object containing the variables.
Trying to work out a scenario with templating and the framework itself (because that's how I learn best), I tried to mock a blog format. This is my class/get request:
// MARK: Blog Post Object
final class BlogPost: NodeRepresentable {
var postId: Int
var postTitle: String
var postContent: String
var postPreview: String
func makeNode(context: Context) throws -> Node {
return try Node(node: [
"postId":self.postId,
"postTitle":self.postTitle,
"postContent":self.postContent,
"postPreview":self.postPreview
])
}
init(_ postId: Int, _ postTitle: String, _ postContent: String) {
self.postId = postId
self.postTitle = postTitle
self.postContent = postContent
self.postPreview = postContent.trunc(100)
}
}
// MARK: Blog view request; iterate over blog objects
drop.get("blog") { request in
let result = try drop.database?.driver.raw("SELECT * FROM Posts;")
guard let posts = result?.nodeArray else {
throw Abort.serverError
}
var postCollection = [BlogPost]()
for post in posts {
guard let postId = post["postId"]?.int,
let postTitle = post["postTitle"]?.string,
let postContent = post["postPreview"]?.string else {
throw Abort.serverError
}
let post = BlogPost(postId, postTitle, postContent)
postCollection.append(post)
}
// Pass posts to be tokenized
/* THIS CODE DOESN'T WORK BECAUSE "CANNOT CONVERT VALUE OF TYPE
* '[BLOGPOST]' TO EXPECTED DICTIONARY VALUE OF TYPE "NODE"
* LOOKING FOR THE BEST METHOD TO PASS THIS LIST OF OBJECTS
*/
drop.view.make("blog", [
"posts":postCollection
])
}
and this is my blog.leaf file:
#extend("base")
#export("head") {
<title>Blog</title>
}
#export("body") {
<h1 class="page-header">Blog Posts</h1>
<div class="page-content-container">
#loop(posts, "posts") {
<div class="post-container">
<h3 style="post-title">#(posts["postTitle"])</h3>
<p style="post-preview">#(posts["postPreview"])</h3>
</div>
}
</div>
}
Problem:
As you can see, I'm a bit stuck on finding the best method for iterating over objects and templating their properties into the leaf file. Anyone have any suggestions? Sorry for the bad programming conventions, by the way. I'm fairly new in Object/Protocol Oriented Programming.
What I ended up doing is, making the Post model conform to the Model protocol.
import Foundation
import HTTP
import Vapor
// MARK: Post Class
final class Post: Model {
var id: Node?
var title: String
var content: String
var date: Date
var isVisible: Bool
// TODO: Implement truncate extension for String and set preview
// to content truncated to 100 characters
var preview = "placeholder"
var exists: Bool = false
init(title: String, content: String, isVisible: Bool = true) {
self.title = title
self.content = content
self.date = Date()
self.isVisible = isVisible
}
init(node: Node, in context: Context) throws {
let dateInt: Int = try node.extract("date")
let isVisibleInt: Int = try node.extract("isVisible")
id = try node.extract("id")
title = try node.extract("title")
content = try node.extract("content")
date = Date(timeIntervalSinceNow: TimeInterval(dateInt))
isVisible = Bool(isVisibleInt as NSNumber)
exists = false
}
func makeNode(context: Context) throws -> Node {
return try Node(node: [
"id": id,
"title": title,
"content": content,
"date": Int(date.timeIntervalSince1970),
"isVisible": Int(isVisible as NSNumber)
])
}
static func prepare(_ database: Database) throws {
try database.create("Posts") { posts in
posts.id()
posts.string("title", optional: false)
posts.string("content", optional: false)
posts.int("date", optional: false)
posts.int("isVisible", optional: false)
}
}
static func revert(_ database: Database) throws {
try database.delete("posts")
}
}
Then to return/create instances of the Post object:
import Vapor
import Foundation
import HTTP
final class BlogController {
func addRoutes(_ drop: Droplet) {
let blogRouter = drop.grouped("blog")
let blogAPIRouter = drop.grouped("api","blog")
blogRouter.get("posts", handler: getPostsView)
blogAPIRouter.get("posts", handler: getPosts)
blogAPIRouter.post("newPost", handler: newPost)
}
// MARK: Get Posts
func getPosts(_ request: Request) throws -> ResponseRepresentable {
let posts = try Post.all().makeNode()
return try JSON(node: [
"Posts":posts
])
}
// Mark: New Post
func newPost(_ request: Request) throws -> ResponseRepresentable {
guard let title = request.data["title"]?.string,
let content = request.data["content"]?.string else {
throw Abort.badRequest
}
var post = Post(title: title, content: content)
try post.save()
return "success"
}
// Mark: Get Posts Rendered
func getPostsView(_ request: Request) throws -> ResponseRepresentable {
return try getPosts(request)
}
}
I'm not an expert on Vapor yet, but I think you need to use .makeNode() so your postCollection object get converted to something you can later use on the template.
Something like this:
drop.view.make("blog", ["posts":postCollection.makeNode()])
func list(_ req: Request) throws -> ResponseRepresentable {
let list = try User.all()
let node = try list.makeNode(in: nil)
let json = try JSON(node: [ "list":node ])
return json
}