Search code examples
vaporvapor-fluent

Vapor 4 fluent. Complex query, filter and create if doesn't exist


I'm trying to query a Chat with two users. If it exists I'd like to return it. If not I'd like to create new chat with those two users and return it.

Comment shows where it fails if chat exists.

Also would be helpful if you can show how to create new chat with those two users in this scenario.

func create(req: Request) throws -> EventLoopFuture<Chat> {
  
  let currentUser = try req.auth.require(User.self)
  
  guard
    let userID = req.parameters.get("userID"),
    let uuid = UUID(userID)
  else { throw Abort(.badRequest) }
  
  return User
    .query(on: req.db)
    .filter(\.$id == uuid)
    .with(\.$chats)
    .first()
    .unwrap(or: Abort(.internalServerError))
    .flatMap({ chatUser -> EventLoopFuture<Chat> in
      
      let chat = chatUser
        .chats
        .filter({
          $0.users.contains(where: { $0.id == currentUser.id }) // FAILS HERE: Fatal error: Siblings relation not eager loaded, use $ prefix to access: Siblings<Chat, User, ChatUser>(from: [chat_id], to: [user_id]): file
        })
        .first
      
      if let chat = chat {
        
        return req
          .eventLoop
          .makeSucceededFuture(chat)
        
      } else {
        
        // create new chat with those two users and return
        
      }
    })
}

Simplified models:

final class Chat: Model, Content {
  @ID(key: .id)
  var id: UUID?
  
  @Siblings(through: ChatUser.self, from: \.$chat, to: \.$user)
  var users: [User]
}

final class User: Model, Content {
  @ID(key: .id)
  var id: UUID?
  
  @Siblings(through: ChatUser.self, from: \.$user, to: \.$chat)
  var chats: [Chat]
}

final class ChatUser: Model {
  @ID(key: .id)
  var id: UUID?

  @Parent(key: Keys.chatId)
  var chat: Chat

  @Parent(key: Keys.userId)
  var user: User
}

Solution

  • 0xTim helped me out on Discord. And I managed to make it work after reading This doc

    Here's working solution:

    func create(req: Request) throws -> EventLoopFuture<Chat> {
      
      let currentUser = try req.auth.require(User.self)
      
      guard
        let userID = req.parameters.get("userID"),
        let userUUID = UUID(userID)
        else { throw Abort(.badRequest) }
      
      return User
        .query(on: req.db)
        .filter(\.$id == userUUID)
        .with(\.$chats, { chats in
          chats.with(\.$users)
        })
        .first()
        .unwrap(or: Abort(.internalServerError))
        .flatMap({ chatUser -> EventLoopFuture<Chat> in
          
          let chat = chatUser
            .chats
            .filter({
              $0.users.contains(where: { $0.id! == currentUser.id! })
            })
            .first
          
          if let chat = chat {
            
            return req.eventLoop.makeSucceededFuture(chat)
            
          } else {
            
            let newChat = Chat()
            return newChat
              .save(on: req.db)
              .flatMap({
                
                newChat
                  .$users
                  .attach([chatUser, currentUser], on: req.db)
                  .flatMap({
                    
                    return req.eventLoop.makeSucceededFuture(newChat)
                  })
              })
          }
        })
    }