Vapor 3 - 如何在保存对象之前检查类似的电子邮件 [英] Vapor 3 - How to check for similar email before saving object
问题描述
我想创建一个路由来让用户更新他们的数据(例如更改他们的电子邮件或用户名).为确保用户不能使用与其他用户相同的用户名,我想检查数据库中是否已存在具有相同用户名的用户.
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屋!