Vapor 3 - Попытка сопоставить запрос входа в систему с моделью, которая не существует в базе данных. - PullRequest
1 голос
/ 21 мая 2019

Я пытаюсь реализовать простой API запроса входа в систему. Как вы можете видеть в примере кода ниже, у меня есть LoginRequest, то есть данные, которые я получаю от клиента (iOS, Android и т. Д.). При этом я проверяю, существует ли User в БД, затем я проверяю, правильный ли пароль пользователя.

Однако я борюсь с тем, как вернуть модель, которой нет в БД. В приведенном ниже примере у меня есть LoginResponse. Я не хочу раскрывать полные данные User & AuthToken (модели в БД) клиенту, поэтому я создал модель LoginResponse. Единственные данные, которые я хочу раскрыть клиенту, это LoginResponse authToken, если все прошло успешно.

Что я ожидаю от пользователя: имя пользователя и пароль

struct LoginRequest : Codable, Content {
    var username: String
    var password : String
}

Ответ, который я возвращаю пользователю: успех (с authToken), неверный пароль или пользователь не существует.

/// Response model for user login.
struct LoginResponse : Codable, Content {

    enum LoginResultType : String, Codable {
        case success
        case incorrectPassword
        case noUser
    }

    var authToken : String?
    var state : LoginResultType

    init(state : LoginResultType) {
        authToken  = nil
        self.state = state
    }
}}
func login(_ req : Request) throws -> Future<LoginResponse> {
        return try req.content.decode(LoginRequest.self).flatMap(to: LoginResponse.self) { loginRequest in
            return User.query(on: req).filter(\.username == loginRequest.username)
                .first().map(to: LoginResponse.self) { user in

                    /// check if we have found a user, if not then return LoginResponse with noUser
                    guard let u = user else {
                        return LoginResponse(state: .noUser)
                    }
                    /// if the password isn't the same, then we tell the user
                    /// that the password is incorrect.
                    if u.password != loginRequest.password {
                        return LoginResponse(state: . incorrectPassword)
                    }


                    /// If username and password are the same then we create a random authToken and save this to the DB.
/// Then I need to return LoginResponse with success and authToken.
                    let authToken = AuthToken(token: "<Random number>")
                    authToken.user = u.id
                    return authToken.create(on: req).flatMap(to: LoginResponse.self){ auth in
                        var lr       = LoginResponse(state: .success)
                        lr.authToken = auth.token
                        return lr
                    }
            }
        }
    }

Приведенная ниже кодировка является головной болью, она не позволит мне вернуть LoginResponse после того, как я создал новый authToken в БД.

                    return authToken.create(on: req).flatMap(to: LoginResponse.self){ auth in
                        var lr       = LoginResponse(state: .success)
                        lr.authToken = auth.token
                        return lr
                    }

Как только я создал authToken, я хочу показать authToken в модели LoginResponse с состоянием успеха, как мне этого добиться?

1 Ответ

2 голосов
/ 22 мая 2019

Просто используйте map вместо flatMap.

map - чтобы вернуть что-то не будущее

flatMap - чтобы вернуть Future

Итакже вы можете вернуть какой-нибудь объект в будущем, используя

req.eventLoop.newSucceededFuture(result: someObject)

Код ниже должен работать как шарм

func login(_ req : Request) throws -> Future<LoginResponse> {
    return try req.content.decode(LoginRequest.self).flatMap { loginRequest in
        return User.query(on: req).filter(\.username == loginRequest.username).first().flatMap { user in
                    /// check if we have found a user, if not then return LoginResponse with noUser
            guard let u = user else {
                return req.eventLoop.newSucceededFuture(result: LoginResponse(state: .noUser))
            }
            /// if the password isn't the same, then we tell the user
            /// that the password is incorrect.
            if u.password != loginRequest.password {
                return req.eventLoop.newSucceededFuture(result: LoginResponse(state: .incorrectPassword))
            }
            /// If username and password are the same then we create a random authToken and save this to the DB.
            /// Then I need to return LoginResponse with success and authToken.
            let authToken = AuthToken(token: "<Random number>")
            authToken.user = u.id
            return authToken.create(on: req).map { auth in
                var lr = LoginResponse(state: .success)
                lr.authToken = auth.token
                return lr
            }
        }
    }
}

Но, честно говоря, возвращать ошибки с 200 OK - плохая практикаhttp code.

Лучше всего использовать вместо ошибок HTTP-коды, и Vapor использует этот способ специально.

Таким образом, ваш код может выглядеть просто так

struct LoginResponse: Content {
    let authToken: String?
}
func login(_ req : Request) throws -> Future<LoginResponse> {
    return try req.content.decode(LoginRequest.self).flatMap { loginRequest in
        return User.query(on: req).filter(\.username == loginRequest.username).first().flatMap { user in
            /// check if we have found a user, if not then throw 404
            guard let u = user else {
                throw Abort(.notFound, reason: "User not found")
            }
            /// if the password isn't the same, then throw 400
            if u.password != loginRequest.password {
                throw Abort(.badRequest, reason: "Incorrect password")
            }
            /// If username and password are the same then we create a random authToken and save this to the DB.
            /// Then I need to return LoginResponse with success and authToken.
            let authToken = AuthToken(token: "<Random number>")
            authToken.user = u.id
            return authToken.create(on: req).map { auth in
                var lr = LoginResponse(state: .success)
                lr.authToken = auth.token
                return lr
            }
        }
    }
}

Выглядит чисто и нет необходимости разбирать дополнительный enum на стороне клиента.

...