I am working on a small vapor project with fluent. Right now I should be able to create a user, create a token for said created user and return the user with the session token. Afterwards I should be able to login and create a new token.
The problem right now is that whenever I create a token, the id
value of said token is set to 1
, even though I set it to nil
. This does not happen when creating a user, then the auto increment is working as intended.
The database I am using is MySQL.
My migration CreateTokens.swift
:
import Fluent
struct CreateTokens: Migration {
func prepare(on database: Database) -> EventLoopFuture<Void> {
database.schema(Token.schema)
.field("id", .int, .identifier(auto: true))
.unique(on: "id")
.field("user_id", .int, .references("users", "id"))
.field("value", .string, .required)
.unique(on: "value")
.field("created_at", .datetime, .required)
.field("expires_at", .datetime)
.create()
}
func revert(on database: Database) -> EventLoopFuture<Void> {
database.schema(Token.schema).delete()
}
}
My Token (Token.swift
):
import Vapor
import Fluent
final class Token: Model {
static let schema = "tokens"
@ID(custom: "id", generatedBy: .database)
var id: Int?
@Parent(key: "id")
var user: User
@Field(key: "value")
var value: String
@Field(key: "expires_at")
var expiresAt: Date?
@Timestamp(key: "created_at", on: .create)
var createdAt: Date?
init() {}
init(id: Int? = nil,
userId: User.IDValue,
token: String,
expiresAt: Date?
) {
self.id = id
self.$user.id = userId
self.value = token
self.expiresAt = expiresAt
}
}
extension Token: ModelTokenAuthenticatable {
static let valueKey = \Token.$value
static let userKey = \Token.$user
var isValid: Bool {
guard let expiryDate = expiresAt else {
return true
}
return expiryDate > Date()
}
}
In my User struct, when I create a token:
func createToken() throws -> Token {
let calendar = Calendar(identifier: .gregorian)
let expiryDate = calendar.date(byAdding: .year, value: 1, to: Date())
let token = try Token(userId: requireID(), token: [UInt8].random(count: 16).base64, expiresAt: expiryDate)
return token
}
If I set a breakpoint on the return token
and print out token, I get the following:
(lldb) po token
Token(input: [expires_at: Optional(2021-11-11 21:01:10 +0000), value: "wn8kvw/GYSfqL280RLCDbQ==", id: 1])
The ID value is set to 1
, however in the code it is (to me) clear that I've set it to nil
. Even if I add token.id = nil
before the return, it is still set to 1
.
What am I doing wrong here? Or did I stumble upon a fluent bug?
As you mentioned, the key "id" seems to be used for both the id
and the user
properties, which is a common type of mistake in this kind of stringly-typed API.
One way I tried to mitigate this is to extend FieldKey
and force myself to always use these static properties instead of string, that way the IDE's auto-complete provides an opportunity for me to think about the right field key to use—something that is missing when you type these manually.
extension FieldKey {
static var userID: FieldKey { "user_id" }
}
// And in the model
@Parent(key: .userID)
var user: User
Hope this helps in the future!