feat: Begins views, login is currently not working.
This commit is contained in:
4
Public/css/main.css
Normal file
4
Public/css/main.css
Normal file
@@ -0,0 +1,4 @@
|
||||
body {
|
||||
background-color: #1e1e2e;
|
||||
color: #ff66ff;
|
||||
}
|
||||
@@ -2,11 +2,17 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
||||
<link rel="stylesheet" href="css/main.css">
|
||||
<title>#(title)</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>#(title)</h1>
|
||||
<div id="body"
|
||||
hx-get="/body"
|
||||
hx-trigger="load"
|
||||
hx-swap="outerHTML">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
3
Resources/Views/logged-in.leaf
Normal file
3
Resources/Views/logged-in.leaf
Normal file
@@ -0,0 +1,3 @@
|
||||
<div id="body">
|
||||
<p>We're in!</p>
|
||||
</div>
|
||||
15
Resources/Views/login.leaf
Normal file
15
Resources/Views/login.leaf
Normal file
@@ -0,0 +1,15 @@
|
||||
<div id="body" class="container">
|
||||
<form class="login-form">
|
||||
<label for="email">Email</label>
|
||||
<input type="text" id="email" placeholder="Email" name="email" autocomplete="email" required autofocus>
|
||||
<br>
|
||||
<label for="password">Password</label>
|
||||
<input type="password" id="password" placeholder="Password" name="password" autocomplete="current-password" required>
|
||||
<br>
|
||||
<button hx-post="/login"
|
||||
hx-target="#body"
|
||||
hx-trigger="click">
|
||||
Sign In
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -4,21 +4,20 @@ import Vapor
|
||||
struct ApiController: RouteCollection {
|
||||
func boot(routes: RoutesBuilder) throws {
|
||||
let api = routes.grouped("api", "v1")
|
||||
let passwordProtected = api.grouped(User.authenticator(), User.guardMiddleware())
|
||||
|
||||
// Allows basic or token authentication.
|
||||
let tokenProtected = api.grouped(
|
||||
let protected = api.grouped(
|
||||
User.authenticator(),
|
||||
UserToken.authenticator(),
|
||||
User.guardMiddleware()
|
||||
)
|
||||
|
||||
let employees = tokenProtected.grouped("employees")
|
||||
let purchaseOrders = tokenProtected.grouped("purchase-orders")
|
||||
let employees = protected.grouped("employees")
|
||||
let purchaseOrders = protected.grouped("purchase-orders")
|
||||
|
||||
// TODO: Need to handle the intial creation of users somehow.
|
||||
let users = tokenProtected.grouped("users")
|
||||
let vendors = tokenProtected.grouped("vendors")
|
||||
let users = protected.grouped("users")
|
||||
let vendors = protected.grouped("vendors")
|
||||
let vendorBranches = vendors.grouped(":vendorID", "branches")
|
||||
|
||||
employees.get(use: employeesIndex(req:))
|
||||
@@ -32,7 +31,7 @@ struct ApiController: RouteCollection {
|
||||
purchaseOrders.post(use: createPurchaseOrder(req:))
|
||||
|
||||
users.post(use: createUser(req:))
|
||||
passwordProtected.group("users", "login") {
|
||||
users.group("login") {
|
||||
$0.get(use: self.login(req:))
|
||||
}
|
||||
|
||||
@@ -106,6 +105,7 @@ struct ApiController: RouteCollection {
|
||||
.with(\.$vendorBranch) {
|
||||
$0.with(\.$vendor)
|
||||
}
|
||||
.sort(\.$id, .descending)
|
||||
.all()
|
||||
.map { $0.toDTO() }
|
||||
}
|
||||
@@ -115,11 +115,20 @@ struct ApiController: RouteCollection {
|
||||
try PurchaseOrder.Create.validate(content: req)
|
||||
let createdById = try req.auth.require(User.self).requireID()
|
||||
let create = try req.content.decode(PurchaseOrder.Create.self)
|
||||
|
||||
guard let employee = try await Employee.find(create.createdForID, on: req.db) else {
|
||||
throw Abort(.notFound, reason: "Employee not found.")
|
||||
}
|
||||
|
||||
guard employee.active else {
|
||||
throw Abort(.badRequest, reason: "Employee is not active, unable to generate a PO for in-active employees")
|
||||
}
|
||||
|
||||
let purchaseOrder = create.toModel(createdByID: createdById)
|
||||
try await purchaseOrder.save(on: req.db)
|
||||
|
||||
guard let loaded = try await PurchaseOrder.query(on: req.db)
|
||||
.filter(\.$id == purchaseOrder.id!)
|
||||
.filter(\.$id == purchaseOrder.requireID())
|
||||
.with(\.$createdBy)
|
||||
.with(\.$createdFor)
|
||||
.with(\.$vendorBranch, { branch in
|
||||
@@ -127,7 +136,8 @@ struct ApiController: RouteCollection {
|
||||
})
|
||||
.first()
|
||||
else {
|
||||
throw Abort(.noContent)
|
||||
// This should really never happen, since we just created the purchase order.
|
||||
throw Abort(.noContent, reason: "Failed to load purchase order after creation")
|
||||
}
|
||||
return loaded.toDTO()
|
||||
}
|
||||
|
||||
@@ -2,6 +2,13 @@ import Fluent
|
||||
import struct Foundation.UUID
|
||||
import Vapor
|
||||
|
||||
/// The employee database model.
|
||||
///
|
||||
/// An employee is someone that PO's can be generated for. They can be either a field
|
||||
/// employee / technician, an office employee, or an administrator.
|
||||
///
|
||||
/// # NOTE: Only `User` types can login and generate po's for employees.
|
||||
///
|
||||
final class Employee: Model, @unchecked Sendable {
|
||||
|
||||
static let schema = "employee"
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import Fluent
|
||||
import Vapor
|
||||
|
||||
/// The purchase order database model.
|
||||
///
|
||||
/// # NOTE: An initial purchase order should be created with an `id` higher than our current PO
|
||||
/// so that subsequent PO's are generated with higher values than our current system produces.
|
||||
/// once the first one is set, the rest will auto-increment from there.
|
||||
final class PurchaseOrder: Model, Content, @unchecked Sendable {
|
||||
static let schema = "purchase_order"
|
||||
|
||||
@@ -80,6 +85,7 @@ final class PurchaseOrder: Model, Content, @unchecked Sendable {
|
||||
extension PurchaseOrder {
|
||||
|
||||
struct Create: Content {
|
||||
let id: Int?
|
||||
let workOrder: Int?
|
||||
let materials: String
|
||||
let customer: String
|
||||
@@ -89,7 +95,7 @@ extension PurchaseOrder {
|
||||
|
||||
func toModel(createdByID: User.IDValue) -> PurchaseOrder {
|
||||
.init(
|
||||
id: nil,
|
||||
id: id,
|
||||
workOrder: workOrder,
|
||||
materials: materials,
|
||||
customer: customer,
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import Fluent
|
||||
import Vapor
|
||||
|
||||
/// The user database model.
|
||||
///
|
||||
/// A user is someone who is able to login and generate PO's for employees. Generally a user should also
|
||||
/// have an employee profile, but not all employees are users. Users are generally restricted to office workers
|
||||
/// and administrators.
|
||||
///
|
||||
///
|
||||
final class User: Model, @unchecked Sendable {
|
||||
static let schema = "user"
|
||||
|
||||
@@ -82,6 +89,7 @@ extension User: ModelAuthenticatable {
|
||||
}
|
||||
|
||||
extension User: ModelSessionAuthenticatable {}
|
||||
extension User: ModelCredentialsAuthenticatable {}
|
||||
|
||||
extension User.Create: Validatable {
|
||||
static func validations(_ validations: inout Validations) {
|
||||
|
||||
@@ -11,7 +11,12 @@ public func configure(_ app: Application) async throws {
|
||||
app.middleware.use(app.sessions.middleware)
|
||||
app.middleware.use(User.sessionAuthenticator())
|
||||
|
||||
app.databases.use(DatabaseConfigurationFactory.sqlite(.file("db.sqlite")), as: .sqlite)
|
||||
switch app.environment {
|
||||
case .production, .development:
|
||||
app.databases.use(DatabaseConfigurationFactory.sqlite(.file("db.sqlite")), as: .sqlite)
|
||||
default:
|
||||
app.databases.use(DatabaseConfigurationFactory.sqlite(.memory), as: .sqlite)
|
||||
}
|
||||
|
||||
app.migrations.add(Vendor.Migrate())
|
||||
app.migrations.add(VendorBranch.Migrate())
|
||||
@@ -24,4 +29,8 @@ public func configure(_ app: Application) async throws {
|
||||
|
||||
// register routes
|
||||
try routes(app)
|
||||
|
||||
if app.environment != .production {
|
||||
try await app.autoMigrate()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,36 @@ import Fluent
|
||||
import Vapor
|
||||
|
||||
func routes(_ app: Application) throws {
|
||||
app.get { req async throws in
|
||||
try await req.view.render("index", ["title": "Hello Vapor!"])
|
||||
let redirectMiddleware = User.redirectMiddleware { req in
|
||||
"login?next=\(req.url.path)"
|
||||
}
|
||||
|
||||
let protected = app.grouped(User.sessionAuthenticator(), redirectMiddleware, User.guardMiddleware())
|
||||
let credentialsProtected = protected.grouped(User.credentialsAuthenticator())
|
||||
|
||||
app.get { req async throws in
|
||||
try await req.view.render("index", ["title": "HHE - Purchase Orders"])
|
||||
}
|
||||
|
||||
app.get("login") { req async throws in
|
||||
req.logger.info("login")
|
||||
return try await req.view.render("login")
|
||||
}
|
||||
|
||||
credentialsProtected.post("login") { req async throws -> View in
|
||||
req.logger.info("login POST")
|
||||
return try await req.view.render("logged-in")
|
||||
}
|
||||
|
||||
credentialsProtected.get("body") { req async throws in
|
||||
req.logger.info("body")
|
||||
return try await req.view.render("logged-in")
|
||||
}
|
||||
|
||||
// app.get("index") { req async throws -> View in
|
||||
//
|
||||
// }
|
||||
|
||||
app.get("hello") { _ async -> String in
|
||||
"Hello, world!"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user