diff --git a/Package.swift b/Package.swift index 1e29c81..c4961b9 100644 --- a/Package.swift +++ b/Package.swift @@ -7,6 +7,7 @@ let package = Package( products: [ .executable(name: "App", targets: ["App"]), .library(name: "ApiController", targets: ["ApiController"]), + .library(name: "AuthClient", targets: ["AuthClient"]), .library(name: "DatabaseClient", targets: ["DatabaseClient"]), .library(name: "ProjectClient", targets: ["ProjectClient"]), .library(name: "ManualDCore", targets: ["ManualDCore"]), @@ -32,6 +33,7 @@ let package = Package( name: "App", dependencies: [ .target(name: "ApiController"), + .target(name: "AuthClient"), .target(name: "DatabaseClient"), .target(name: "ViewController"), .product(name: "Dependencies", package: "swift-dependencies"), @@ -54,6 +56,15 @@ let package = Package( .product(name: "Vapor", package: "vapor"), ] ), + .target( + name: "AuthClient", + dependencies: [ + .target(name: "DatabaseClient"), + .target(name: "ManualDCore"), + .product(name: "Dependencies", package: "swift-dependencies"), + .product(name: "DependenciesMacros", package: "swift-dependencies"), + ] + ), .target( name: "DatabaseClient", dependencies: [ @@ -112,6 +123,7 @@ let package = Package( .target( name: "ViewController", dependencies: [ + .target(name: "AuthClient"), .target(name: "DatabaseClient"), .target(name: "ProjectClient"), .target(name: "ManualDClient"), diff --git a/Sources/App/Extensions/ViewController+respond.swift b/Sources/App/Extensions/ViewController+respond.swift index 2787a84..be79b95 100644 --- a/Sources/App/Extensions/ViewController+respond.swift +++ b/Sources/App/Extensions/ViewController+respond.swift @@ -12,11 +12,7 @@ extension ViewController { .init( route: route, isHtmxRequest: request.isHtmxRequest, - logger: request.logger, - authenticateUser: { request.session.authenticate($0) }, - currentUser: { - try request.auth.require(User.self) - } + logger: request.logger ) ) return AnyHTMLResponse(value: html) diff --git a/Sources/App/Middleware/DependenciesMiddleware.swift b/Sources/App/Middleware/DependenciesMiddleware.swift index 8c6665b..23b676c 100644 --- a/Sources/App/Middleware/DependenciesMiddleware.swift +++ b/Sources/App/Middleware/DependenciesMiddleware.swift @@ -1,12 +1,13 @@ import ApiController +import AuthClient import DatabaseClient import Dependencies +import ManualDCore import Vapor import ViewController // Taken from discussions page on `swift-dependencies`. -// FIX: Use live view controller. struct DependenciesMiddleware: AsyncMiddleware { private let values: DependencyValues.Continuation @@ -29,6 +30,7 @@ struct DependenciesMiddleware: AsyncMiddleware { try await values.yield { try await withDependencies { $0.apiController = apiController + $0.authClient = .live(on: request) $0.database = database // $0.dateFormatter = .liveValue $0.viewController = viewController diff --git a/Sources/AuthClient/Interface.swift b/Sources/AuthClient/Interface.swift new file mode 100644 index 0000000..d8817b8 --- /dev/null +++ b/Sources/AuthClient/Interface.swift @@ -0,0 +1,51 @@ +import DatabaseClient +import Dependencies +import DependenciesMacros +import ManualDCore +import Vapor + +extension DependencyValues { + public var authClient: AuthClient { + get { self[AuthClient.self] } + set { self[AuthClient.self] = newValue } + } +} + +@DependencyClient +public struct AuthClient: Sendable { + public var createAndLogin: @Sendable (User.Create) async throws -> User + public var currentUser: @Sendable () throws -> User + public var login: @Sendable (User.Login) async throws -> User + public var logout: @Sendable () throws -> Void +} + +extension AuthClient: TestDependencyKey { + public static let testValue = Self() + + public static func live(on request: Request) -> Self { + @Dependency(\.database) var database + + return .init( + createAndLogin: { createForm in + let user = try await database.users.create(createForm) + _ = try await database.users.login( + .init(email: createForm.email, password: createForm.password) + ) + request.auth.login(user) + request.logger.debug("LOGGED IN: \(user.id)") + return user + }, + currentUser: { + try request.auth.require(User.self) + }, + login: { loginForm in + let token = try await database.users.login(loginForm) + let user = try await database.users.get(token.userID)! + request.session.authenticate(user) + request.logger.debug("LOGGED IN: \(user.id)") + return user + }, + logout: { request.auth.logout(User.self) } + ) + } +} diff --git a/Sources/ViewController/Interface.swift b/Sources/ViewController/Interface.swift index abcdab3..36012b5 100644 --- a/Sources/ViewController/Interface.swift +++ b/Sources/ViewController/Interface.swift @@ -1,3 +1,4 @@ +import AuthClient import Dependencies import DependenciesMacros import Elementary @@ -15,10 +16,6 @@ public typealias AnySendableHTML = (any HTML & Sendable) @DependencyClient public struct ViewController: Sendable { - - public typealias AuthenticateHandler = @Sendable (User) -> Void - public typealias CurrentUserHandler = @Sendable () throws -> User - public var view: @Sendable (Request) async throws -> AnySendableHTML } @@ -29,21 +26,15 @@ extension ViewController { public let route: SiteRoute.View public let isHtmxRequest: Bool public let logger: Logger - public let authenticateUser: AuthenticateHandler - public let currentUser: CurrentUserHandler public init( route: SiteRoute.View, isHtmxRequest: Bool, - logger: Logger, - authenticateUser: @escaping AuthenticateHandler, - currentUser: @escaping CurrentUserHandler + logger: Logger ) { self.route = route self.isHtmxRequest = isHtmxRequest self.logger = logger - self.authenticateUser = authenticateUser - self.currentUser = currentUser } } @@ -62,28 +53,23 @@ extension ViewController: DependencyKey { extension ViewController.Request { + func currentUser() throws -> User { + @Dependency(\.authClient.currentUser) var currentUser + return try currentUser() + } + func authenticate( _ login: User.Login ) async throws -> User { - @Dependency(\.database.users) var users - let token = try await users.login(login) - let user = try await users.get(token.userID)! - authenticateUser(user) - logger.debug("Logged in user: \(user.id)") - return user + @Dependency(\.authClient) var auth + return try await auth.login(login) } @discardableResult func createAndAuthenticate( _ signup: User.Create ) async throws -> User { - @Dependency(\.database.users) var users - let user = try await users.create(signup) - let _ = try await users.login( - .init(email: signup.email, password: signup.password) - ) - authenticateUser(user) - logger.debug("Created and logged in user: \(user.id)") - return user + @Dependency(\.authClient) var auth + return try await auth.createAndLogin(signup) } }