import DatabaseClient import Fluent import Foundation import SharedModels import Vapor public extension DatabaseClient.Employees { static func live(database: any Database) -> Self { Self { create in let model = try create.toModel() try await model.save(on: database) return try model.toDTO() } delete: { id in guard let model = try await EmployeeModel.find(id, on: database) else { throw NotFoundError() } try await model.delete(on: database) } fetchAll: { request in var query = EmployeeModel.query(on: database) .sort(\EmployeeModel.$lastName) switch request { case .active: query = query.filter(\EmployeeModel.$active == true) case .inactive: query = query.filter(\EmployeeModel.$active == false) case .all: break } return try await query.all().map { try $0.toDTO() } } get: { id in try await EmployeeModel.find(id, on: database).map { try $0.toDTO() } } update: { id, updates in guard let model = try await EmployeeModel.find(id, on: database) else { throw NotFoundError() } database.logger.debug("Applying updates to employee: \(updates)") try model.applyUpdate(updates) try await model.save(on: database) return try model.toDTO() } } } private extension Employee.Create { func toModel() throws -> EmployeeModel { try validate() return .init(firstName: firstName, lastName: lastName, active: active ?? true) } func validate() throws { guard !firstName.isEmpty else { throw ValidationError(message: "Employee first name should not be empty.") } guard !lastName.isEmpty else { throw ValidationError(message: "Employee first name should not be empty.") } } } extension Employee.Update { func validate() throws { if let firstName { guard !firstName.isEmpty else { throw ValidationError(message: "Employee first name should not be empty.") } } if let lastName { guard !lastName.isEmpty else { throw ValidationError(message: "Employee first name should not be empty.") } } } } extension Employee { struct Migrate: AsyncMigration { let name = "CreateEmployee" func prepare(on database: any Database) async throws { try await database.schema(EmployeeModel.schema) .id() .field("first_name", .string, .required) .field("last_name", .string, .required) .field("is_active", .bool, .required) .field("created_at", .datetime) .field("updated_at", .datetime) .unique(on: "first_name", "last_name") .create() } func revert(on database: any Database) async throws { try await database.schema(EmployeeModel.schema).delete() } } } /// The employee database model. /// /// An employee is someone that PO's can be generated for. They can be either a field /// employee / technician, an office employee, or an administrator. /// /// # NOTE: Only `User` types can login and generate po's for employees. /// final class EmployeeModel: Model, @unchecked Sendable { static let schema = "employee" // @ID(key: ") // var id: UUID? @ID(key: .id) var id: UUID? @Field(key: "first_name") var firstName: String @Field(key: "last_name") var lastName: String @Field(key: "is_active") var active: Bool @Timestamp(key: "created_at", on: .create, format: .iso8601) var createdAt: Date? @Timestamp(key: "updated_at", on: .update, format: .iso8601) var updatedAt: Date? init() {} init( id: UUID? = nil, firstName: String, lastName: String, active: Bool, createdAt: Date? = nil, updatedAt: Date? = nil ) { self.id = id self.firstName = firstName self.lastName = lastName self.active = active self.createdAt = createdAt self.updatedAt = updatedAt } func toDTO() throws -> Employee { try .init( id: requireID(), active: active, createdAt: createdAt!, firstName: firstName, lastName: lastName, updatedAt: updatedAt! ) } func applyUpdate(_ updates: Employee.Update) throws { try updates.validate() if let firstName = updates.firstName { self.firstName = firstName } if let lastName = updates.lastName { self.lastName = lastName } // NB: When html forms are submitted with a checkbox then active is nil // in the update context. if active, updates.active == nil || updates.active == false { active = false } } }