Vapor 3 - 如何在保存对象之前检查类似的电子邮件 [英] Vapor 3 - How to check for similar email before saving object

查看:43
本文介绍了Vapor 3 - 如何在保存对象之前检查类似的电子邮件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想创建一个路由来让用户更新他们的数据(例如更改他们的电子邮件或用户名).为确保用户不能使用与其他用户相同的用户名,我想检查数据库中是否已存在具有相同用户名的用户.

I would like to create a route to let users update their data (e.g. changing their email or their username). To make sure a user cannot use the same username as another user, I would like to check if a user with the same username already exists in the database.

我已经在迁移中设置了唯一的用户名.

I have already made the username unique in the migrations.

我有一个如下所示的用户模型:

I have a user model that looks like this:

struct User: Content, SQLiteModel, Migration {
    var id: Int?
    var username: String
    var name: String
    var email: String
    var password: String

    var creationDate: Date?

    // Permissions
    var staff: Bool = false
    var superuser: Bool = false

    init(username: String, name: String, email: String, password: String) {
        self.username = username
        self.name = name
        self.email = email
        self.password = password
        self.creationDate = Date()
    }
}

这是我想使用它的一段代码:

This is the piece of code where I want to use it:

func create(_ req: Request) throws -> EventLoopFuture<User> {
    return try req.content.decode(UserCreationRequest.self).flatMap { userRequest in

        // Check if `userRequest.email` already exists
        // If if does -> throw Abort(.badRequest, reason: "Email already in use")
        // Else -> Go on with creation

        let digest = try req.make(BCryptDigest.self)
        let hashedPassword = try digest.hash(userRequest.password)
        let persistedUser = User(name: userRequest.name, email: userRequest.email, password: hashedPassword)

        return persistedUser.save(on: req)
    }
}

我可以这样做(请参阅下一个代码段),但这似乎是一个奇怪的选择,因为在进行更多检查时,它需要大量嵌套,例如必须执行唯一性(例如在更新用户的情况下).

I could do it like this (see next snippet) but it seems a strange option as it requires a lot of nesting when more checks for e.g. uniqueness would have to be performed (for instance in the case of updating a user).

func create(_ req: Request) throws -> EventLoopFuture<User> {
    return try req.content.decode(UserCreationRequest.self).flatMap { userRequest in
        let userID = userRequest.email
        return User.query(on: req).filter(\.userID == userID).first().flatMap { existingUser in
            guard existingUser == nil else {
                throw Abort(.badRequest, reason: "A user with this email already exists")
            }

            let digest = try req.make(BCryptDigest.self)
            let hashedPassword = try digest.hash(userRequest.password)
            let persistedUser = User(name: userRequest.name, email: userRequest.email, password: hashedPassword)

            return persistedUser.save(on: req)
        }
    }
}

<小时>

由于其中一个答案建议我尝试添加错误中间件(请参阅下一个片段),但这并没有正确捕获错误(也许我在代码中做错了 - 刚开始使用 Vapor).


As one of the answers suggested I've tried to add Error middleware (see next snippet) but this does not correctly catch the error (maybe I am doing something wrong in the code - just started with Vapor).

import Vapor
import FluentSQLite

enum InternalError: Error {
    case emailDuplicate
}

struct EmailDuplicateErrorMiddleware: Middleware {
    func respond(to request: Request, chainingTo next: Responder) throws -> EventLoopFuture<Response> {
        let response: Future<Response>

        do {
            response = try next.respond(to: request)
        } catch is SQLiteError {
            response = request.eventLoop.newFailedFuture(error: InternalError.emailDuplicate)
        }

        return response.catchFlatMap { error in
            if let response = error as? ResponseEncodable {
                do {
                    return try response.encode(for: request)
                } catch {
                    return request.eventLoop.newFailedFuture(error: InternalError.emailDuplicate)
                }
            } else {
                return request.eventLoop.newFailedFuture(error: error)
            }
        }
    }
}

推荐答案

我会使用 Migration 使模型中的字段 unique 如:

I would make the field unique in the model using a Migration such as:

extension User: Migration {
  static func prepare(on connection: SQLiteConnection) -> Future<Void> {
    return Database.create(self, on: connection) { builder in
      try addProperties(to: builder)
      builder.unique(on: \.email)
    }
  }
}

如果您使用默认的 String 作为 email 的字段类型,那么您将需要减少它,因为这会创建一个字段 VARCHAR(255) 对于 UNIQUE 键来说太大了.然后,我会使用一些自定义 Middleware 来捕获使用同一电子邮件再次尝试保存记录时出现的错误.

If you use a default String as the field type for email, then you will need to reduce it as this creates a field VARCHAR(255) which is too big for a UNIQUE key. I would then use a bit of custom Middleware to trap the error that arises when a second attempt to save a record is made using the same email.

struct DupEmailErrorMiddleware: Middleware
{
    func respond(to request: Request, chainingTo next: Responder) throws -> EventLoopFuture<Response>
    {
        let response: Future<Response>
        do {
            response = try next.respond(to: request)
        } catch is MySQLError {
            // needs a bit more sophistication to check the specific error
            response = request.eventLoop.newFailedFuture(error: InternalError.dupEmail)
        }
        return response.catchFlatMap
        {
            error in
            if let response = error as? ResponseEncodable
            {
                do
                {
                    return try response.encode(for: request)
                }
                catch
                {
                    return request.eventLoop.newFailedFuture(error: InternalError.dupEmail)
                }
            } else
            {
                return request.eventLoop.newFailedFuture(error: error   )
            }
        }
    }
}

您的自定义错误需要类似于:

Your custom error needs to be something like:

enum InternalError: Debuggable, ResponseEncodable
{
    func encode(for request: Request) throws -> EventLoopFuture<Response>
    {
        let response = request.response()
        let eventController = EventController()
        //TODO make this return to correct view
        eventController.message = reason
        return try eventController.index(request).map
        {
            html in
            try response.content.encode(html)
            return response
        }
    }

    case dupEmail

    var identifier:String
    {
        switch self
        {
            case .dupEmail: return "dupEmail"
        }
    }

    var reason:String
    {
       switch self
       {
            case .dupEmail: return "Email address already used"
        }
    }
}

在上面的代码中,通过在控制器中设置一个值来向用户显示实际错误,然后在视图中获取该值并显示警报.此方法允许通用错误处理程序负责显示错误消息.但是,在您的情况下,您可能只需在 catchFlatMap 中创建响应.

In the code above, the actual error is displayed to the user by setting a value in the controller, which is then picked up in the view and an alert displayed. This method allows a general-purpose error handler to take care of displaying the error messages. However, in your case, it might be that you could just create the response in the catchFlatMap.

这篇关于Vapor 3 - 如何在保存对象之前检查类似的电子邮件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆