feat: Begins creating an auth client and integrates into view controller routes.

This commit is contained in:
2026-01-16 17:04:05 -05:00
parent 761ba29c1e
commit 3ec1ee2814
5 changed files with 78 additions and 31 deletions

View File

@@ -7,6 +7,7 @@ let package = Package(
products: [ products: [
.executable(name: "App", targets: ["App"]), .executable(name: "App", targets: ["App"]),
.library(name: "ApiController", targets: ["ApiController"]), .library(name: "ApiController", targets: ["ApiController"]),
.library(name: "AuthClient", targets: ["AuthClient"]),
.library(name: "DatabaseClient", targets: ["DatabaseClient"]), .library(name: "DatabaseClient", targets: ["DatabaseClient"]),
.library(name: "ProjectClient", targets: ["ProjectClient"]), .library(name: "ProjectClient", targets: ["ProjectClient"]),
.library(name: "ManualDCore", targets: ["ManualDCore"]), .library(name: "ManualDCore", targets: ["ManualDCore"]),
@@ -32,6 +33,7 @@ let package = Package(
name: "App", name: "App",
dependencies: [ dependencies: [
.target(name: "ApiController"), .target(name: "ApiController"),
.target(name: "AuthClient"),
.target(name: "DatabaseClient"), .target(name: "DatabaseClient"),
.target(name: "ViewController"), .target(name: "ViewController"),
.product(name: "Dependencies", package: "swift-dependencies"), .product(name: "Dependencies", package: "swift-dependencies"),
@@ -54,6 +56,15 @@ let package = Package(
.product(name: "Vapor", package: "vapor"), .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( .target(
name: "DatabaseClient", name: "DatabaseClient",
dependencies: [ dependencies: [
@@ -112,6 +123,7 @@ let package = Package(
.target( .target(
name: "ViewController", name: "ViewController",
dependencies: [ dependencies: [
.target(name: "AuthClient"),
.target(name: "DatabaseClient"), .target(name: "DatabaseClient"),
.target(name: "ProjectClient"), .target(name: "ProjectClient"),
.target(name: "ManualDClient"), .target(name: "ManualDClient"),

View File

@@ -12,11 +12,7 @@ extension ViewController {
.init( .init(
route: route, route: route,
isHtmxRequest: request.isHtmxRequest, isHtmxRequest: request.isHtmxRequest,
logger: request.logger, logger: request.logger
authenticateUser: { request.session.authenticate($0) },
currentUser: {
try request.auth.require(User.self)
}
) )
) )
return AnyHTMLResponse(value: html) return AnyHTMLResponse(value: html)

View File

@@ -1,12 +1,13 @@
import ApiController import ApiController
import AuthClient
import DatabaseClient import DatabaseClient
import Dependencies import Dependencies
import ManualDCore
import Vapor import Vapor
import ViewController import ViewController
// Taken from discussions page on `swift-dependencies`. // Taken from discussions page on `swift-dependencies`.
// FIX: Use live view controller.
struct DependenciesMiddleware: AsyncMiddleware { struct DependenciesMiddleware: AsyncMiddleware {
private let values: DependencyValues.Continuation private let values: DependencyValues.Continuation
@@ -29,6 +30,7 @@ struct DependenciesMiddleware: AsyncMiddleware {
try await values.yield { try await values.yield {
try await withDependencies { try await withDependencies {
$0.apiController = apiController $0.apiController = apiController
$0.authClient = .live(on: request)
$0.database = database $0.database = database
// $0.dateFormatter = .liveValue // $0.dateFormatter = .liveValue
$0.viewController = viewController $0.viewController = viewController

View File

@@ -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) }
)
}
}

View File

@@ -1,3 +1,4 @@
import AuthClient
import Dependencies import Dependencies
import DependenciesMacros import DependenciesMacros
import Elementary import Elementary
@@ -15,10 +16,6 @@ public typealias AnySendableHTML = (any HTML & Sendable)
@DependencyClient @DependencyClient
public struct ViewController: Sendable { public struct ViewController: Sendable {
public typealias AuthenticateHandler = @Sendable (User) -> Void
public typealias CurrentUserHandler = @Sendable () throws -> User
public var view: @Sendable (Request) async throws -> AnySendableHTML public var view: @Sendable (Request) async throws -> AnySendableHTML
} }
@@ -29,21 +26,15 @@ extension ViewController {
public let route: SiteRoute.View public let route: SiteRoute.View
public let isHtmxRequest: Bool public let isHtmxRequest: Bool
public let logger: Logger public let logger: Logger
public let authenticateUser: AuthenticateHandler
public let currentUser: CurrentUserHandler
public init( public init(
route: SiteRoute.View, route: SiteRoute.View,
isHtmxRequest: Bool, isHtmxRequest: Bool,
logger: Logger, logger: Logger
authenticateUser: @escaping AuthenticateHandler,
currentUser: @escaping CurrentUserHandler
) { ) {
self.route = route self.route = route
self.isHtmxRequest = isHtmxRequest self.isHtmxRequest = isHtmxRequest
self.logger = logger self.logger = logger
self.authenticateUser = authenticateUser
self.currentUser = currentUser
} }
} }
@@ -62,28 +53,23 @@ extension ViewController: DependencyKey {
extension ViewController.Request { extension ViewController.Request {
func currentUser() throws -> User {
@Dependency(\.authClient.currentUser) var currentUser
return try currentUser()
}
func authenticate( func authenticate(
_ login: User.Login _ login: User.Login
) async throws -> User { ) async throws -> User {
@Dependency(\.database.users) var users @Dependency(\.authClient) var auth
let token = try await users.login(login) return try await auth.login(login)
let user = try await users.get(token.userID)!
authenticateUser(user)
logger.debug("Logged in user: \(user.id)")
return user
} }
@discardableResult @discardableResult
func createAndAuthenticate( func createAndAuthenticate(
_ signup: User.Create _ signup: User.Create
) async throws -> User { ) async throws -> User {
@Dependency(\.database.users) var users @Dependency(\.authClient) var auth
let user = try await users.create(signup) return try await auth.createAndLogin(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
} }
} }