feat: Rename items in database client for consistency.
All checks were successful
CI / Linux Tests (push) Successful in 5m24s

This commit is contained in:
2026-01-29 15:47:24 -05:00
parent 6723f7a410
commit 18a5ef06d3
17 changed files with 144 additions and 341 deletions

View File

@@ -0,0 +1,272 @@
import Dependencies
import DependenciesMacros
import Fluent
import ManualDCore
import Vapor
extension DatabaseClient.UserProfiles: TestDependencyKey {
public static let testValue = Self()
public static func live(database: any Database) -> Self {
.init(
create: { profile in
try profile.validate()
let model = profile.toModel()
try await model.save(on: database)
return try model.toDTO()
},
delete: { id in
guard let model = try await UserProfileModel.find(id, on: database) else {
throw NotFoundError()
}
try await model.delete(on: database)
},
fetch: { userID in
try await UserProfileModel.query(on: database)
.with(\.$user)
.filter(\.$user.$id == userID)
.first()
.map { try $0.toDTO() }
},
get: { id in
try await UserProfileModel.find(id, on: database)
.map { try $0.toDTO() }
},
update: { id, updates in
guard let model = try await UserProfileModel.find(id, on: database) else {
throw NotFoundError()
}
try updates.validate()
model.applyUpdates(updates)
if model.hasChanges {
try await model.save(on: database)
}
return try model.toDTO()
}
)
}
}
extension User.Profile.Create {
func validate() throws(ValidationError) {
guard !firstName.isEmpty else {
throw ValidationError("User first name should not be empty.")
}
guard !lastName.isEmpty else {
throw ValidationError("User last name should not be empty.")
}
guard !companyName.isEmpty else {
throw ValidationError("User company name should not be empty.")
}
guard !streetAddress.isEmpty else {
throw ValidationError("User street address should not be empty.")
}
guard !city.isEmpty else {
throw ValidationError("User city should not be empty.")
}
guard !state.isEmpty else {
throw ValidationError("User state should not be empty.")
}
guard !zipCode.isEmpty else {
throw ValidationError("User zip code should not be empty.")
}
}
func toModel() -> UserProfileModel {
.init(
userID: userID,
firstName: firstName,
lastName: lastName,
companyName: companyName,
streetAddress: streetAddress,
city: city,
state: state,
zipCode: zipCode,
theme: theme
)
}
}
extension User.Profile.Update {
func validate() throws(ValidationError) {
if let firstName {
guard !firstName.isEmpty else {
throw ValidationError("User first name should not be empty.")
}
}
if let lastName {
guard !lastName.isEmpty else {
throw ValidationError("User last name should not be empty.")
}
}
if let companyName {
guard !companyName.isEmpty else {
throw ValidationError("User company name should not be empty.")
}
}
if let streetAddress {
guard !streetAddress.isEmpty else {
throw ValidationError("User street address should not be empty.")
}
}
if let city {
guard !city.isEmpty else {
throw ValidationError("User city should not be empty.")
}
}
if let state {
guard !state.isEmpty else {
throw ValidationError("User state should not be empty.")
}
}
if let zipCode {
guard !zipCode.isEmpty else {
throw ValidationError("User zip code should not be empty.")
}
}
}
}
extension User.Profile {
struct Migrate: AsyncMigration {
let name = "Create UserProfile"
func prepare(on database: any Database) async throws {
try await database.schema(UserProfileModel.schema)
.id()
.field("firstName", .string, .required)
.field("lastName", .string, .required)
.field("companyName", .string, .required)
.field("streetAddress", .string, .required)
.field("city", .string, .required)
.field("state", .string, .required)
.field("zipCode", .string, .required)
.field("theme", .string)
.field("userID", .uuid, .references(UserModel.schema, "id", onDelete: .cascade))
.field("createdAt", .datetime)
.field("updatedAt", .datetime)
.unique(on: "userID")
.create()
}
func revert(on database: any Database) async throws {
try await database.schema(UserProfileModel.schema).delete()
}
}
}
final class UserProfileModel: Model, @unchecked Sendable {
static let schema = "user_profile"
@ID(key: .id)
var id: UUID?
@Parent(key: "userID")
var user: UserModel
@Field(key: "firstName")
var firstName: String
@Field(key: "lastName")
var lastName: String
@Field(key: "companyName")
var companyName: String
@Field(key: "streetAddress")
var streetAddress: String
@Field(key: "city")
var city: String
@Field(key: "state")
var state: String
@Field(key: "zipCode")
var zipCode: String
@Field(key: "theme")
var theme: String?
@Timestamp(key: "createdAt", on: .create, format: .iso8601)
var createdAt: Date?
@Timestamp(key: "updatedAt", on: .update, format: .iso8601)
var updatedAt: Date?
init() {}
init(
id: UUID? = nil,
userID: User.ID,
firstName: String,
lastName: String,
companyName: String,
streetAddress: String,
city: String,
state: String,
zipCode: String,
theme: Theme? = nil
) {
self.id = id
$user.id = userID
self.firstName = firstName
self.lastName = lastName
self.companyName = companyName
self.streetAddress = streetAddress
self.city = city
self.state = state
self.zipCode = zipCode
self.theme = theme?.rawValue
}
func toDTO() throws -> User.Profile {
try .init(
id: requireID(),
userID: $user.id,
firstName: firstName,
lastName: lastName,
companyName: companyName,
streetAddress: streetAddress,
city: city,
state: state,
zipCode: zipCode,
theme: self.theme.flatMap(Theme.init),
createdAt: createdAt!,
updatedAt: updatedAt!
)
}
func applyUpdates(_ updates: User.Profile.Update) {
if let firstName = updates.firstName, firstName != self.firstName {
self.firstName = firstName
}
if let lastName = updates.lastName, lastName != self.lastName {
self.lastName = lastName
}
if let companyName = updates.companyName, companyName != self.companyName {
self.companyName = companyName
}
if let streetAddress = updates.streetAddress, streetAddress != self.streetAddress {
self.streetAddress = streetAddress
}
if let city = updates.city, city != self.city {
self.city = city
}
if let state = updates.state, state != self.state {
self.state = state
}
if let zipCode = updates.zipCode, zipCode != self.zipCode {
self.zipCode = zipCode
}
if let theme = updates.theme, theme.rawValue != self.theme {
self.theme = theme.rawValue
}
}
}