Vapor 3 and Fluent - nested query - vapor

I'm trying to do a nested query in Vapor 3 and Fluent. The point is I need to get all the users, from each team where the teams have a specific eventID. Teams are children of Event. Users are children of Teams. Thanks in advance for your help.
There are only 15 teams per event, but 12 users per team
Here is the Event model:
final class Event: Codable {
var id: Int?
var name: String
}
extension Event {
var teams: Children<Event, Team> {
return children(\.eventID)
}
}
Here is the Team model
final class Team: Codable {
var id: Int?
var name: String
var icon: String
var eventID: Event.ID
}
extension Team {
var user: Parent<Team, Event> {
return parent(\.eventID)
}
}
extension Team {
var users: Children<Team, User> {
return children(\.teamID)
}
}
Here is the User model.
final class User: Codable {
var id: UUID?
var name: String
var email: String
var eventID: Event.ID
var teamID: Team.ID
}
extension User {
var user: Parent<User, Team> {
return parent(\.teamID)
}
}
I need to send an event ID and I want it to return all the users in all the teams
func getUsersForEvent(_ req: Request) throws -> Future<[User]> {
return try req.parameters.next(Event.self).flatMap(to: [User].self) { event in
return try event.teams.query(on: req).all().flatMap(to: [User].self) { team in
return try team.users.query(on: req).all()
}
}
}

You'd query that easily with raw SQL query or using SwifQL lib
Here is an example with SwifQL
struct TeamWithUsers: Content {
let id: UUID
let name, icon: String
let users: [User]
}
func getCategoriesWithProducts(_ req: Request) throws -> Future<[TeamWithUsers]> {
return try req.parameters.next(Event.self).flatMap { event in
let usersSubquery = SwifQL
.select(Fn.coalesce(Fn.array_agg(Fn.to_jsonb(User.table)), PgArray() => .jsonbArray))
.from(User.table)
.where(\User.teamID == \Team.id)
let query = try SwifQL
.select(\Team.id, \Team.name, \Team.icon, |usersSubquery | => "users")
.from(Team.table)
.where(\Team.eventID == event.requireID())
// here you could print the raw query for debugging
// print(query.prepare(.psql).plain)
return query.execute(on: req, as: .psql).all(decoding: TeamWithUsers.self)
}
}

Here's what I came up with, with help from the Ray Wenderlich book. In my task I don't need to return all the users and only need to see teams for 1 event at a time, so I pass in the eventID as a parameter.
Any guidance on how to sort the result by teamScore?
func getTeamsWithUsersForEvent(_ req: Request) throws -> Future<[TeamWithUsers]> {
let currentID = try req.parameters.next(Int.self)
print("currentID \(currentID)")
return Team.query(on: req).filter(\Team.eventID == currentID).all().flatMap(to: [TeamWithUsers].self) { team in
try team.map { team in
try team.users.query(on: req).all().map { users in
TeamWithUsers(
id: team.id,
name: team.name,
icon: team.icon,
eventID: team.eventID,
//rawScore: team.rawScore,
//users: users,
count: users.count,
teamScore: team.rawScore / users.count
)
}
}.flatten(on: req)
}
}
struct TeamWithUsers: Content {
let id: Int?
let name: String
let icon: String
let eventID: Event.ID
//let rawScore: Int
//let users: [User]
let count: Int
let teamScore: Int
}

Related

Filter two struct arrays

What is the best way to filter two Struct Arrays to match by ID and added the information in a specific property.
Example
Struct User {
let id: Int
let name: String
var arts: [Article]?
}
Struct Article {
let userId: Int
let id: Int
let title: String
let body: String
}
I have an array with all Users and other array with all Post for all Users. I need to add into User Array all post by user (User.id == Article.userId)
I'm trying to do with this.
var art = [Article]()
var users = [User]()
self?.art.forEach({ art in
guard let userId = self?.users.firstIndex(where: { $0.id == art.userId }) else {
print("Failed to find a Art by UserID")
return
}
self?.users[userId].arts?.append(art)
})
The idea is added into User Struct all Articles corresponding by user
I think your code was in the right direction. Try this approach (works for me):
var arts = [Article(userId: 1, id: 1, title: "title1"),
Article(userId: 6, id: 1, title: "title6")]
var users = [User(id: 1, name: "user1"),
User(id: 2, name: "user2")]
print("---> before users: \n \(users)")
arts.forEach{ art in
if let userNdx = users.firstIndex(where: { $0.id == art.userId }) {
if let _ = users[userNdx].arts {} else {
users[userNdx].arts = []
}
users[userNdx].arts!.append(art)
}
}
print("\n---> after users: \n \(users)")
class Article {
let userId: Int
let id: Int
let title: String
let body: String
}
class User {
let id: Int
let name: String
var arts: [Post]?
}
I think the best possible way is to convert it to a dictionary. I think the below code is well explonary.
var dict = [Int: [Article]]()
var arts = [Article]()
for art in arts {
dict[art.userId, default: []].append(art)
}
var users = [User]()
for case let user in users {
let articles = dict[user.id]
user.atrs = articles
}

Vapor 4 authentication

Hey I'm having some problems with the login controllers.My code is:
func login(_ req: Request) throws -> EventLoopFuture<UserToken>{
let user = try req.auth.require(User.self)
let token = try user.generateToken()
return token.save(on: req.db).map { token }
}
But I don't really know that how the function work in postman.This is my usermodel :
import Foundation
import Fluent
import Vapor
import FluentPostgresDriver
final class User:Model,Content{
static let schema = "user"
#ID(key: .id)
var id:UUID?
#Field(key:"帳號")
var account:String
#Field(key: "密碼")
var password:String
init() {}
init(id: UUID?=nil, account:String, password:String){
self.id=id
self.account=account
self.password=password
}
}
extension User: ModelAuthenticatable {
// 要取帳號的欄位
static var usernameKey: KeyPath<User, Field<String>> = \User.$account
// 要取雜湊密碼的欄位
static var passwordHashKey: KeyPath<User, Field<String>> = \User.$password
// 驗證
func verify(password: String) throws -> Bool {
try Bcrypt.verify(password, created: self.password)
}
}
extension User {
struct Create: Content {
var account: String
var password: String
var confirmPassword: String // 確認密碼
}
}
extension User.Create: Validatable {
static func validations(_ validations: inout Validations) {
validations.add("account", as: String.self, is: .count(10...10))
// password需為8~16碼
validations.add("password", as: String.self, is: .count(8...16))
}
}
extension User {
func generateToken() throws -> UserToken {
// 產生一組新Token, 有效期限為一天
let calendar = Calendar(identifier: .gregorian)
let expiryDate = calendar.date(byAdding: .day, value: 1, to: Date())
return try UserToken(value: [UInt8].random(count: 16).base64, expireTime: expiryDate, userID: self.requireID())
}
}
And this is my usertoken:
import Foundation
import Vapor
import Fluent
final class UserToken: Content, Model {
static let schema: String = "user_tokens"
#ID(key: .id)
var id: UUID?
#Field(key: "value")
var value: String
// oken過期時間
#Field(key: "expireTime")
var expireTime: Date?
// 關聯到User
#Parent(key: "user_id")
var user: User
init() { }
init(id: UUID? = nil, value: String, expireTime: Date?, userID: User.IDValue) {
self.id = id
self.value = value
self.expireTime = expireTime
self.$user.id = userID
}
}
extension UserToken: ModelTokenAuthenticatable {
//Token的欄位
static var valueKey = \UserToken.$value
//要取對應的User欄位
static var userKey = \UserToken.$user
// 驗證,這裡只檢查是否過期
var isValid: Bool {
guard let expireTime = expireTime else { return false }
return expireTime > Date()
}
}
While I'm typing the value of "account","password" and "confirmPassword", but it kept telling me that "User not authenticated." ,which I've already have the value in my database.
enter image description here
And I'm sure that the password was right. Is there anything that I missed? I'm pretty new in vapor.
And I followed the article below: https://ken-60401.medium.com/vapor-4-authentication-server-side-swift-1f96b035a117
I think the tutorial linked uses HTTP Basic authentication for the login route and I'm guessing that's the case judging by the code shown (it would be good to show how you're registering the login route).
If that's the case then you need to send the username and password in the request as basic authentication credentials in the Authorization header. The value should be Basic <Credentials> where Credentials is username:password Base 64 encoded. However you can get Postman to do it for you

How to filter other parent of child with Vapor?

I have this request:
router.get("/fetchOngoingReleases") { (request) -> Future<[ReleaseOut]> in
return Release.query(on: request).filter(\.inprogress == true).all().map { releases in
var result: [ReleaseOut] = []
for r in releases {
var pageEvents: [Event] = []
let num = r.releaseUsers.query(on: request).filter(\.user.fbId ~~ "something").count()
var needAuthentication: Bool
if num == 0 {
needAuthentication = true
} else {
needAuthentication = false
}
let rOut = ReleaseOut(fbId: r.fbId, name: r.name, purpose: r.purpose, needAuthentication: needAuthentication)
result.append(rOut)
}
return result
}
}
}
It says I can not access (???) releaseUser.user.fbId in the query?
Here the data model:
and in code
final class Release: Content {
var id: Int?
var fbId: String
var inprogress: Bool?
var name: String
var purpose: String
/// Creates a new `Release`.
init(id: Int? = nil, fbId: String, name: String, purpose: String = "normal selling") {
self.id = id
self.fbId = fbId
self.name = name
self.purpose = purpose
}
}
extension Release {
var releaseUsers: Children<Release, ReleaseUser> {
return children(\.releaseId)
}
}
final class ReleaseUser: Content {
var id: Int?
var releaseId: Release.ID
var userId: User.ID
init(id: Int? = nil, releaseId: Release.ID, userId: User.ID) {
self.id = id
self.releaseId = releaseId
self.userId = userId
}
}
extension ReleaseUser {
var user: Parent<ReleaseUser, User> {
return parent(\.userId)
}
}
final class User: Content {
var id: Int?
var fbId: String
var name: String
init(id: Int? = nil, fbId: String, name: String) {
self.id = id
self.fbId = fbId
self.name = name
}
}
Ok so there are several things going on here, but the main concept is that you can't just jump across different tables like that - you need to use a JOIN to join the ReleaseUser table to the User table so you can then query on the fbId
Try changing your query to:
Release.query(on: request).filter(\.inprogress == true).join(\ReleaseUser.releaseId, to:\Release.Id).join(\ReleaseUser.userId, to:\User.Id).alsoDecode(User.self).all()
The alsoDecode will give you a tuple with the first position containing your original Release instance and the second containing the corresponding User instance. So, fbId should be available as:
r.1.fbId
In your case.

Swift: Filter nested arrays

I'm trying to filter an array of Deals with a status DealStaus which has a nested array of Bookings, each one with a BookingStatus.
I want to filter Deals with status .won and Bookings according to the statuses given when calling the function. BookingStatus and DealStatus are both enums.
Deal and Booking look like this:
public struct Deal: Decodable {
public let identifier: String?
public let status: DealStatus
public let bookings: [Booking]?
}
public struct Booking: Decodable {
public let identifier: String?
public let status: BookingStatus
public let startDate: Date?
public let endDate: Date?
}
To do so I wrote the following snippet:
private func getDeals(with bookingStatus: [BookingStatus]) -> [Deal] {
guard let user = currentUser, let deals = user.deals else { return [Deal]() } // Note: user is a class attribute
return deals.filter { $0.status == .won && $0.bookings?.filter { bookingStatus.contains($0.status) }}
}
This does not work. The compiler gives the following error:
Optional type '[Booking]?' cannot be used as a boolean; test for '!=
nil' instead
Following the instructions of #matt, I broke it down:
private func getDeals(with bookingStatus: [BookingStatus]) -> [Deal] {
guard let user = currentUser, let deals = user.deals else { return [Deal]() }
return deals
.filter { $0.status == .won }
.filter { $0.bookings?.contains(where: { bookingStatus.contains($0.status)} ) ?? false }
}

Getting response from AWS | Appsync

I've Successfully Integrated the AWS-Client-App Sample couple of days ago then i started something from scratch. Let me give you my project specificaitons:-
Xcode version : Version 9.4 (9F1027a)
Language : Swift 4.0
Database : DynamoDB
And Using AppSync for syncing database
I Have a schema
type Mutation {
#------ User ------#
CreateUser(
FirstName: String,
Username: String,
Password: String,
CreatedOn: Int
): User
UpdateUser(UserId: ID!, FirstName: String!, Username: String!): User
DeleteUser(UserId: ID!): User
}
type Query {
####------ User ------#
GetUser(UserId: ID!): User
CheckUsername(Username: String): [User]
CheckPhoneNumber(Phone: String): [User]
#------ Login ------#
CheckUserLogin(Username: String, Password: String): [User]
}
type User {
UserId: ID!
FirstName: String
Username: String
#Password: String
Bio: String
Phone: String
Email: String
Address: String
WebsiteUrl: String
ProfilePicture: String
CreatedOn: Int
Updated: Int
Status: Boolean
LastLoggedIn: Int
IpAddress: String
}
schema {
query: Query
mutation: Mutation
}
A simple query in .graphql file
query CheckUsername($Username:String){
CheckUsername(Username:$Username)
{
Username
}
}
and generated a file called check Queries.swift
import AWSAppSync
public final class CheckUsernameQuery: GraphQLQuery {
public static let operationString =
"query CheckUsername($Username: String) {\n CheckUsername(Username: $Username) {\n __typename\n Username\n }\n}"
public var Username: String?
public init(Username: String? = nil) {
self.Username = Username
}
public var variables: GraphQLMap? {
return ["Username": Username]
}
public struct Data: GraphQLSelectionSet {
public static let possibleTypes = ["Query"]
public static let selections: [GraphQLSelection] = [
GraphQLField("CheckUsername", arguments: ["Username": GraphQLVariable("Username")], type: .list(.object(CheckUsername.selections))),
]
public var snapshot: Snapshot
public init(snapshot: Snapshot) {
self.snapshot = snapshot
}
public init(checkUsername: [CheckUsername?]? = nil) {
self.init(snapshot: ["__typename": "Query", "CheckUsername": checkUsername.flatMap { $0.map { $0.flatMap { $0.snapshot } } }])
}
public var checkUsername: [CheckUsername?]? {
get {
return (snapshot["CheckUsername"] as? [Snapshot?]).flatMap { $0.map { $0.flatMap { CheckUsername(snapshot: $0) } } }
}
set {
snapshot.updateValue(newValue.flatMap { $0.map { $0.flatMap { $0.snapshot } } }, forKey: "CheckUsername")
}
}
public struct CheckUsername: GraphQLSelectionSet {
public static let possibleTypes = ["User"]
public static let selections: [GraphQLSelection] = [
GraphQLField("__typename", type: .nonNull(.scalar(String.self))),
GraphQLField("Username", type: .scalar(String.self)),
]
public var snapshot: Snapshot
public init(snapshot: Snapshot) {
self.snapshot = snapshot
}
public init(username: String? = nil) {
self.init(snapshot: ["__typename": "User", "Username": username])
}
public var __typename: String {
get {
return snapshot["__typename"]! as! String
}
set {
snapshot.updateValue(newValue, forKey: "__typename")
}
}
public var username: String? {
get {
return snapshot["Username"] as? String
}
set {
snapshot.updateValue(newValue, forKey: "Username")
}
}
}
}
}
Then i called this block of code to run the query
let query = CheckUsernameQuery(Username:"testuser1")
AppSyncManager.Client().fetch(query: query, cachePolicy: .fetchIgnoringCacheData) {
(result, error) in
if error != nil {
print(error?.localizedDescription ?? "")
return
}
let username = result?.data?.checkUsername![0]?.username
}
And I'm Getting the following response
▿ Optional<GraphQLResult<Data>>
▿ some : GraphQLResult<Data>
- data : nil
- errors : nil
- source : AWSAppSync.GraphQLResult<AppName.CheckUsernameQuery.Data>.Source.server
- dependentKeys : nil
The query works well when i test it in Appsync Api panel but not working in the app. No Error, No Data nothing at all? What am i possibly doing wrong if any of you have an idea just feel free to let me know, it would be a great help for me. Thanks.

Resources