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">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<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>
|
<title>#(title)</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<h1>#(title)</h1>
|
<h1>#(title)</h1>
|
||||||
|
<div id="body"
|
||||||
|
hx-get="/body"
|
||||||
|
hx-trigger="load"
|
||||||
|
hx-swap="outerHTML">
|
||||||
|
</div>
|
||||||
</body>
|
</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 {
|
struct ApiController: RouteCollection {
|
||||||
func boot(routes: RoutesBuilder) throws {
|
func boot(routes: RoutesBuilder) throws {
|
||||||
let api = routes.grouped("api", "v1")
|
let api = routes.grouped("api", "v1")
|
||||||
let passwordProtected = api.grouped(User.authenticator(), User.guardMiddleware())
|
|
||||||
|
|
||||||
// Allows basic or token authentication.
|
// Allows basic or token authentication.
|
||||||
let tokenProtected = api.grouped(
|
let protected = api.grouped(
|
||||||
User.authenticator(),
|
User.authenticator(),
|
||||||
UserToken.authenticator(),
|
UserToken.authenticator(),
|
||||||
User.guardMiddleware()
|
User.guardMiddleware()
|
||||||
)
|
)
|
||||||
|
|
||||||
let employees = tokenProtected.grouped("employees")
|
let employees = protected.grouped("employees")
|
||||||
let purchaseOrders = tokenProtected.grouped("purchase-orders")
|
let purchaseOrders = protected.grouped("purchase-orders")
|
||||||
|
|
||||||
// TODO: Need to handle the intial creation of users somehow.
|
// TODO: Need to handle the intial creation of users somehow.
|
||||||
let users = tokenProtected.grouped("users")
|
let users = protected.grouped("users")
|
||||||
let vendors = tokenProtected.grouped("vendors")
|
let vendors = protected.grouped("vendors")
|
||||||
let vendorBranches = vendors.grouped(":vendorID", "branches")
|
let vendorBranches = vendors.grouped(":vendorID", "branches")
|
||||||
|
|
||||||
employees.get(use: employeesIndex(req:))
|
employees.get(use: employeesIndex(req:))
|
||||||
@@ -32,7 +31,7 @@ struct ApiController: RouteCollection {
|
|||||||
purchaseOrders.post(use: createPurchaseOrder(req:))
|
purchaseOrders.post(use: createPurchaseOrder(req:))
|
||||||
|
|
||||||
users.post(use: createUser(req:))
|
users.post(use: createUser(req:))
|
||||||
passwordProtected.group("users", "login") {
|
users.group("login") {
|
||||||
$0.get(use: self.login(req:))
|
$0.get(use: self.login(req:))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,6 +105,7 @@ struct ApiController: RouteCollection {
|
|||||||
.with(\.$vendorBranch) {
|
.with(\.$vendorBranch) {
|
||||||
$0.with(\.$vendor)
|
$0.with(\.$vendor)
|
||||||
}
|
}
|
||||||
|
.sort(\.$id, .descending)
|
||||||
.all()
|
.all()
|
||||||
.map { $0.toDTO() }
|
.map { $0.toDTO() }
|
||||||
}
|
}
|
||||||
@@ -115,11 +115,20 @@ struct ApiController: RouteCollection {
|
|||||||
try PurchaseOrder.Create.validate(content: req)
|
try PurchaseOrder.Create.validate(content: req)
|
||||||
let createdById = try req.auth.require(User.self).requireID()
|
let createdById = try req.auth.require(User.self).requireID()
|
||||||
let create = try req.content.decode(PurchaseOrder.Create.self)
|
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)
|
let purchaseOrder = create.toModel(createdByID: createdById)
|
||||||
try await purchaseOrder.save(on: req.db)
|
try await purchaseOrder.save(on: req.db)
|
||||||
|
|
||||||
guard let loaded = try await PurchaseOrder.query(on: req.db)
|
guard let loaded = try await PurchaseOrder.query(on: req.db)
|
||||||
.filter(\.$id == purchaseOrder.id!)
|
.filter(\.$id == purchaseOrder.requireID())
|
||||||
.with(\.$createdBy)
|
.with(\.$createdBy)
|
||||||
.with(\.$createdFor)
|
.with(\.$createdFor)
|
||||||
.with(\.$vendorBranch, { branch in
|
.with(\.$vendorBranch, { branch in
|
||||||
@@ -127,7 +136,8 @@ struct ApiController: RouteCollection {
|
|||||||
})
|
})
|
||||||
.first()
|
.first()
|
||||||
else {
|
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()
|
return loaded.toDTO()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,13 @@ import Fluent
|
|||||||
import struct Foundation.UUID
|
import struct Foundation.UUID
|
||||||
import Vapor
|
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 {
|
final class Employee: Model, @unchecked Sendable {
|
||||||
|
|
||||||
static let schema = "employee"
|
static let schema = "employee"
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import Fluent
|
import Fluent
|
||||||
import Vapor
|
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 {
|
final class PurchaseOrder: Model, Content, @unchecked Sendable {
|
||||||
static let schema = "purchase_order"
|
static let schema = "purchase_order"
|
||||||
|
|
||||||
@@ -80,6 +85,7 @@ final class PurchaseOrder: Model, Content, @unchecked Sendable {
|
|||||||
extension PurchaseOrder {
|
extension PurchaseOrder {
|
||||||
|
|
||||||
struct Create: Content {
|
struct Create: Content {
|
||||||
|
let id: Int?
|
||||||
let workOrder: Int?
|
let workOrder: Int?
|
||||||
let materials: String
|
let materials: String
|
||||||
let customer: String
|
let customer: String
|
||||||
@@ -89,7 +95,7 @@ extension PurchaseOrder {
|
|||||||
|
|
||||||
func toModel(createdByID: User.IDValue) -> PurchaseOrder {
|
func toModel(createdByID: User.IDValue) -> PurchaseOrder {
|
||||||
.init(
|
.init(
|
||||||
id: nil,
|
id: id,
|
||||||
workOrder: workOrder,
|
workOrder: workOrder,
|
||||||
materials: materials,
|
materials: materials,
|
||||||
customer: customer,
|
customer: customer,
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
import Fluent
|
import Fluent
|
||||||
import Vapor
|
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 {
|
final class User: Model, @unchecked Sendable {
|
||||||
static let schema = "user"
|
static let schema = "user"
|
||||||
|
|
||||||
@@ -82,6 +89,7 @@ extension User: ModelAuthenticatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension User: ModelSessionAuthenticatable {}
|
extension User: ModelSessionAuthenticatable {}
|
||||||
|
extension User: ModelCredentialsAuthenticatable {}
|
||||||
|
|
||||||
extension User.Create: Validatable {
|
extension User.Create: Validatable {
|
||||||
static func validations(_ validations: inout Validations) {
|
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(app.sessions.middleware)
|
||||||
app.middleware.use(User.sessionAuthenticator())
|
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(Vendor.Migrate())
|
||||||
app.migrations.add(VendorBranch.Migrate())
|
app.migrations.add(VendorBranch.Migrate())
|
||||||
@@ -24,4 +29,8 @@ public func configure(_ app: Application) async throws {
|
|||||||
|
|
||||||
// register routes
|
// register routes
|
||||||
try routes(app)
|
try routes(app)
|
||||||
|
|
||||||
|
if app.environment != .production {
|
||||||
|
try await app.autoMigrate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,36 @@ import Fluent
|
|||||||
import Vapor
|
import Vapor
|
||||||
|
|
||||||
func routes(_ app: Application) throws {
|
func routes(_ app: Application) throws {
|
||||||
app.get { req async throws in
|
let redirectMiddleware = User.redirectMiddleware { req in
|
||||||
try await req.view.render("index", ["title": "Hello Vapor!"])
|
"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
|
app.get("hello") { _ async -> String in
|
||||||
"Hello, world!"
|
"Hello, world!"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user