manageable-users/Sources/ManageableUsers/Controllers/AdminController.swift

133 lines
4.6 KiB
Swift

import Vapor
import Leaf
import PostgresKit
import SwiftSMTPVapor
public struct AdminController<User: ManagedUser>: Sendable where User.SessionID == ExpiringUserId {
public init() {}
public func routes (_ router: any RoutesBuilder, api apiRouter: any RoutesBuilder) {
let admin = router
.grouped ("admin")
.grouped (RoleMiddleware<User> (role: "admin"))
let api = apiRouter
.grouped ("admin")
.grouped (RoleAPIMiddleware<User> (role: "admin"))
admin.get (use: adminPage (request:))
api.get (use: list (request:))
api.get (":id", use: fetch (request:))
api.put (use: invite (request:))
api.post (":id", use: save(request:))
}
struct AdminContext: Encodable {
let roles: [String]
let section: String
}
@Sendable
private func adminPage (request: Request) async throws -> View {
let roles = try await request.db.withSQLConnection { connection in
return try await UserRole.all (connection: connection)
}
return try await request.view.render ("admin", AdminContext (roles: roles.map (\.name), section: "useradmin"))
}
struct UserItem: Content {
let id: UUID
let email: String
let fullName: String
let isActive: Bool
let roles: [String]
init(user: User) {
self.id = user.id
self.email = user.email
self.fullName = user.fullName
self.isActive = user.isActive
self.roles = user.roles
}
}
@Sendable
private func list (request: Request) async throws -> [UserItem] {
return try await request.db.withSQLConnection { connection in
let users = try await User.all(on: connection)
return users.map { UserItem (user: $0) }
}
}
@Sendable
private func fetch (request: Request) async throws -> UserItem {
guard let idString = request.parameters.get ("id"), let id = UUID (uuidString: idString) else {
throw Abort (.badRequest)
}
return try await request.db.withSQLConnection { connection in
guard let user = try await User.fetch (id, on: connection) else {
throw Abort (.notFound)
}
return UserItem (user: user)
}
}
struct Invitation: Decodable {
let email: String
let fullname: String
let roles: [String]
}
@Sendable
private func invite (request: Request) async throws -> Response {
let invitation = try request.content.decode(Invitation.self)
return try await request.db.withConnection { connection in
try await connection.transaction { connection in
let token = try await UserToken.create (connection: connection).token
try await User.create (email: invitation.email, fullname: invitation.fullname, roles: invitation.roles, token: token, on: connection)
let host = try Environment.baseURL.host() ?? ""
let body = try await request.view.render ("email/invite", ["token": token, "host": host])
.data
let message = Email (sender: Email.Contact (emailAddress: try Environment.emailSender),
recipients: [Email.Contact(emailAddress: invitation.email)],
subject: "Aktivera ditt konto på \(host)",
body: .plain (String (buffer: body)))
try await request.swiftSMTP.mailer.send(message)
return Response (status: .created)
}
}
}
struct Save: Decodable {
let email: String
let fullname: String
let roles: [String]
let isActive: Bool
}
@Sendable
private func save (request: Request) async throws -> Response {
guard let userId = request.parameters.get("id", as: UUID.self) else {
throw Abort (.badRequest)
}
let save = try request.content.decode(Save.self)
return try await request.db.withSQLConnection (user: try request.auth.require (BasicUser.self)) { connection in
guard (try await User.fetch (userId, on: connection)) != nil else {
throw Abort (.notFound)
}
guard !save.email.isEmpty && !save.fullname.isEmpty else {
throw Abort (.badRequest)
}
try await User.save (id: userId, email: save.email, fullname: save.fullname, roles: save.roles, isActive: save.isActive, on: connection)
return Response (status: .ok)
}
}
}