feat: Cleans up some file names, adds generate admin user command.
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
55
Sources/App/GenerateAdminUserCommand.swift
Normal file
55
Sources/App/GenerateAdminUserCommand.swift
Normal 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)")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
4
env.example
Normal file
@@ -0,0 +1,4 @@
|
||||
ADMIN_USERNAME="admin"
|
||||
ADMIN_EMAIL="admin@development.com"
|
||||
ADMIN_PASSWORD="super-secret"
|
||||
SQLITE_FILENAME="db.sqlite"
|
||||
Reference in New Issue
Block a user