feat: Cleans up some file names, adds generate admin user command.

This commit is contained in:
2025-01-19 17:07:08 -05:00
parent 185ebbbc15
commit 81f0e03549
10 changed files with 159 additions and 110 deletions

View File

@@ -44,17 +44,7 @@ extension SharedModels.ViewRoute {
let user = try await users.get(token.userID)!
request.session.authenticate(user)
return await request.render {
MainPage(displayNav: true, route: .purchaseOrders) {
div(
.hx.get(login.next ?? "/purchase-orders"),
.hx.pushURL(true),
.hx.target("body"),
.hx.trigger(.event(.revealed)),
.hx.indicator(".hx-indicator")
) {
Img.spinner().attributes(.class("hx-indicator"))
}
}
MainPage.loggedIn(next: login.next)
}
}

View File

@@ -0,0 +1,55 @@
import DatabaseClientLive
import Dependencies
import FluentSQLiteDriver
import SharedModels
import Vapor
struct GenerateAdminUserCommand: AsyncCommand {
struct Signature: CommandSignature {
@Option(name: "username", short: "u")
var userame: String?
@Option(name: "email", short: "e")
var email: String?
@Option(name: "password", short: "p")
var password: String?
}
var help: String {
"Generate admin user in the database."
}
func run(using context: CommandContext, signature: Signature) async throws {
let database = DatabaseClient.live(database: context.application.db(.sqlite))
let username = signature.userame ?? Environment.get("ADMIN_USERNAME")
guard let username else {
throw Abort(.badRequest, reason: "Username not provided or found in the environment.")
}
let email = signature.email ?? Environment.get("ADMIN_EMAIL")
guard let email else {
throw Abort(.badRequest, reason: "Email not provided or found in the environment.")
}
let password = signature.password ?? Environment.get("ADMIN_PASSWORD")
guard let password else {
throw Abort(.badRequest, reason: "Password not provided or found in the environment.")
}
let adminUser = User.Create(
username: username,
email: email,
password: password,
confirmPassword: password
)
_ = try await database.users.create(adminUser)
context.console.print("Generated admin user: \(adminUser.username)")
}
}

View File

@@ -111,36 +111,3 @@ enum HXTarget {
}
}
}
// TODO: Move to MainPage
enum ViewRoute: String {
case employees
case login
case purchaseOrders
case users
case vendors
var title: String {
switch self {
case .purchaseOrders:
return "Purchase Orders"
default:
return rawValue.capitalized
}
}
var description: String {
switch self {
case .employees:
return "Employees are who purchase orders can be issued to."
case .purchaseOrders, .login:
return ""
case .users:
return "Users are who can login and issue purchase orders for employees."
case .vendors:
return "Vendors are where purchase orders can be issued to."
}
}
}

View File

@@ -1,5 +1,6 @@
import Elementary
import ElementaryHTMX
import SharedModels
struct MainPage<Inner: HTML>: SendableHTMLDocument where Inner: Sendable {
@@ -12,7 +13,7 @@ struct MainPage<Inner: HTML>: SendableHTMLDocument where Inner: Sendable {
init(
displayNav: Bool = false,
route: ViewRoute,
route: RouteHeaderView.ViewRoute,
_ inner: () -> Inner
) {
self.displayNav = displayNav
@@ -39,6 +40,29 @@ struct MainPage<Inner: HTML>: SendableHTMLDocument where Inner: Sendable {
}
}
extension MainPage where Inner == LoggedIn {
static func loggedIn(next: String?) -> Self {
MainPage(displayNav: true, route: .purchaseOrders) {
LoggedIn(next: next)
}
}
}
struct LoggedIn: HTML {
let next: String?
var content: some HTML {
div(
.hx.get(next ?? ViewRoute.router.path(for: .purchaseOrder(.index))),
.hx.pushURL(true),
.hx.target("body"),
.hx.trigger(.event(.revealed)),
.hx.indicator(".hx-indicator")
) {
Img.spinner().attributes(.class("hx-indicator"))
}
}
}
struct RouteHeaderView: HTML {
let title: String
@@ -61,6 +85,38 @@ struct RouteHeaderView: HTML {
br()
}
}
enum ViewRoute: String {
case employees
case login
case purchaseOrders
case users
case vendors
var title: String {
switch self {
case .purchaseOrders:
return "Purchase Orders"
default:
return rawValue.capitalized
}
}
var description: String {
switch self {
case .employees:
return "Employees are who purchase orders can be issued to."
case .purchaseOrders, .login:
return ""
case .users:
return "Users are who can login and issue purchase orders for employees."
case .vendors:
return "Vendors are where purchase orders can be issued to."
}
}
}
}
struct Logo: HTML, Sendable {

View File

@@ -1,10 +1,12 @@
import Elementary
enum Img {
@Sendable
static func spinner(width: Int = 30, height: Int = 30) -> some HTML<HTMLTag.img> {
img(.src("/images/spinner.svg"), .width(width), .height(height))
}
@Sendable
static func search(width: Int = 30, height: Int = 30) -> some HTML<HTMLTag.img> {
img(.src("/images/search.svg"), .width(width), .height(height))
}

View File

@@ -16,7 +16,7 @@ struct VendorTable: HTML {
.attributes(
.style("padding: 0px 10px;"),
.hx.get(route: .vendor(.form)),
.hx.target("#float"),
.hx.target(.float),
.hx.swap(.outerHTML)
)
}

View File

@@ -5,6 +5,7 @@ import FluentSQLiteDriver
import NIOSSL
import SharedModels
import Vapor
@preconcurrency import VaporRouting
// configures your application
public func configure(_ app: Application) async throws {
@@ -18,7 +19,6 @@ public func configure(_ app: Application) async throws {
let cors = CORSMiddleware(configuration: corsConfiguration)
app.middleware.use(cors, at: .beginning)
// uncomment to serve files from /Public folder
app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))
app.middleware.use(app.sessions.middleware)
app.middleware.use(DependenciesMiddleware())
@@ -38,17 +38,45 @@ public func configure(_ app: Application) async throws {
let databaseClient = DatabaseClient.live(database: app.db)
try await app.migrations.add(databaseClient.migrations())
try withDependencies {
$0.database = databaseClient
} operation: {
try routes(app)
}
app.mount(
SiteRoute.router,
middleware: { $0.middleware() },
use: siteHandler
)
// if app.environment != .production {
try await app.autoMigrate()
// }
#if DEBUG
app.asyncCommands.use(SeedCommand(), as: "seed")
#endif
app.asyncCommands.use(GenerateAdminUserCommand(), as: "generate-admin")
}
extension SiteRoute {
func middleware() -> [any Middleware]? {
switch self {
case let .api(route):
return route.middleware
case .health:
return nil
case let .view(route):
return route.middleware
}
}
}
@Sendable
func siteHandler(
request: Request,
route: SiteRoute
) async throws -> any AsyncResponseEncodable {
switch route {
case let .api(route):
return try await route.handle(request: request)
case .health:
return HTTPStatus.ok
case let .view(route):
return try await route.handle(request: request)
}
}

View File

@@ -1,56 +0,0 @@
import CasePathsCore
import DatabaseClientLive
import Dependencies
import Elementary
import ElementaryHTMX
import Fluent
import SharedModels
import Vapor
import VaporElementary
@preconcurrency import VaporRouting
func routes(_ app: Application) throws {
app.mount(
SiteRoute.router,
middleware: { route in
switch route {
case let .api(route):
return route.middleware
case .health:
return nil
case let .view(route):
return route.middleware
}
},
use: siteHandler
)
app.get { _ in
HTMLResponse {
MainPage(displayNav: false, route: .purchaseOrders) {
div(.class("container")) {
h1 { "iT WORKS" }
}
}
}
}
}
private struct LoginContext: Content {
let next: String?
}
@Sendable
func siteHandler(
request: Request,
route: SiteRoute
) async throws -> any AsyncResponseEncodable {
switch route {
case let .api(route):
return try await route.handle(request: request)
case .health:
return HTTPStatus.ok
case let .view(route):
return try await route.handle(request: request)
}
}

4
env.example Normal file
View File

@@ -0,0 +1,4 @@
ADMIN_USERNAME="admin"
ADMIN_EMAIL="admin@development.com"
ADMIN_PASSWORD="super-secret"
SQLITE_FILENAME="db.sqlite"

View File

@@ -15,3 +15,6 @@ run:
clean:
rm -rf .build
bootstrap:
cp ./env.example .env