feat: Better styling on web pages, bad-words check now has less edge cases.
This commit is contained in:
@@ -1,6 +1,13 @@
|
|||||||
|
* {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
body {
|
body {
|
||||||
background-color: #1E1E2E;
|
background-color: #1E1E2E;
|
||||||
color: white;
|
color: white;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
@@ -42,27 +49,72 @@ label {
|
|||||||
|
|
||||||
li {
|
li {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
|
padding: 10px;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
padding: 10px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pros {
|
.pros {
|
||||||
border 2px solid green;
|
flex 1 300px;
|
||||||
border-radius: 10px;
|
width: 50%;
|
||||||
background-color: MediumSeaGreen;
|
min-height: 200px;
|
||||||
margin: 10px;
|
padding: 5px;
|
||||||
opacity: 0.7;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.cons {
|
.cons {
|
||||||
|
flex 1 300px;
|
||||||
|
min-height: 200px;
|
||||||
|
width: 50%;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.counter-container {
|
||||||
|
position: relative;
|
||||||
|
/* border 5px solid MediumSeaGreen; */
|
||||||
|
/* border-radius: 5px 5px 0px 0px; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.counter {
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.counter-label {
|
||||||
|
position: relative;
|
||||||
|
top: 2px;
|
||||||
|
left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pros-list {
|
||||||
|
position: relative;
|
||||||
|
border 2px solid green;
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: MediumSeaGreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cons-list {
|
||||||
|
position: relative;
|
||||||
border 2px solid green;
|
border 2px solid green;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background-color: Tomato;
|
background-color: Tomato;
|
||||||
margin: 10px;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.listHeader {
|
.listHeader {
|
||||||
margin: auto;
|
/* margin: auto; */
|
||||||
padding: 20px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.center {
|
.center {
|
||||||
|
|||||||
@@ -2,42 +2,49 @@
|
|||||||
#export("body"):
|
#export("body"):
|
||||||
<div class="center">
|
<div class="center">
|
||||||
<h1>Welcome #capitalized(name)</h1>
|
<h1>Welcome #capitalized(name)</h1>
|
||||||
|
<div class="description">
|
||||||
<p>
|
<p>
|
||||||
Please add your pro's and cons during the talk to the list below.
|
<small>Please add your pro's and cons during the talk to the list below.</small>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<small>You can add as many pros and cons as you would like.</small>
|
<small>You can add as many pros and cons as you would like.</small>
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
<form id="proconForm" action="submitProOrCon">
|
<form id="proconForm" action="submitProOrCon">
|
||||||
<select id="type", name="type">
|
<select id="type", name="type">
|
||||||
<option value="pro">Pro</option>
|
<option value="pro">Pro</option>
|
||||||
<option value="con">Con</option>
|
<option value="con">Con</option>
|
||||||
</select>
|
</select>
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<input type="text" id="description" name="description" placeholder="Description" required>
|
||||||
<label for="description">Description:</label>
|
|
||||||
<br>
|
|
||||||
<input type="text" id="description" name="description" required>
|
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
<input class="loginButton" type="submit" value="Submit">
|
<input class="loginButton" type="submit" value="Submit">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="container">
|
||||||
<div class="pros">
|
<div class="pros">
|
||||||
<p class="listHeader">Pros - <small>Count: #count(pros)</small></p>
|
<div class="counter-container">
|
||||||
<ul>
|
<h3 class="counter-label">Pros</h3>
|
||||||
|
<h3 class="counter">Count: #count(pros)</h3>
|
||||||
|
</div>
|
||||||
|
<ul class="pros-list">
|
||||||
#for(item in pros):
|
#for(item in pros):
|
||||||
<li>#(item.description)</li>
|
<li>#(item.description)</li>
|
||||||
#endfor
|
#endfor
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="cons">
|
<div class="cons">
|
||||||
<p class="listHeader">Cons - <small>Count: #count(cons)</small></p>
|
<div class="counter-container">
|
||||||
<ul>
|
<h3 class="counter-label">Cons</h3>
|
||||||
|
<h3 class="counter">Count: #count(cons)</h3>
|
||||||
|
</div>
|
||||||
|
<ul class="cons-list">
|
||||||
#for(item in cons):
|
#for(item in cons):
|
||||||
<li>#(item.description)</li>
|
<li>#(item.description)</li>
|
||||||
#endfor
|
#endfor
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
#endexport
|
#endexport
|
||||||
#endextend
|
#endextend
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
<script src="js/site.js"></script>
|
<script src="js/site.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header><h1>Chiller Pro vs. Cons<h1></header>
|
|
||||||
#import("body")
|
#import("body")
|
||||||
<footer>
|
<footer>
|
||||||
<span>2025 Symposium</span>
|
<span>2025 Symposium</span>
|
||||||
|
|||||||
@@ -5,13 +5,18 @@ import Vapor
|
|||||||
struct ApiController: RouteCollection {
|
struct ApiController: RouteCollection {
|
||||||
|
|
||||||
func boot(routes: any RoutesBuilder) throws {
|
func boot(routes: any RoutesBuilder) throws {
|
||||||
let users = routes.grouped("api", "users")
|
let api = routes.grouped("api")
|
||||||
|
let users = api.grouped("users")
|
||||||
|
let proCon = api.grouped("procons")
|
||||||
|
let utils = api.grouped("utils")
|
||||||
|
|
||||||
users.get(use: usersIndex(req:))
|
users.get(use: usersIndex(req:))
|
||||||
users.post(use: createUser(req:))
|
users.post(use: createUser(req:))
|
||||||
|
|
||||||
let proCon = routes.grouped("api", "procons")
|
|
||||||
proCon.get(use: prosAndConsIndex(req:))
|
proCon.get(use: prosAndConsIndex(req:))
|
||||||
proCon.post(use: createProCon(req:))
|
proCon.post(use: createProCon(req:))
|
||||||
|
|
||||||
|
utils.post("check-words", use: checkWords(req:))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Sendable
|
@Sendable
|
||||||
@@ -39,6 +44,17 @@ struct ApiController: RouteCollection {
|
|||||||
return proCon
|
return proCon
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Sendable
|
||||||
|
func checkWords(req: Request) async throws -> HTTPStatus {
|
||||||
|
let input = try req.content.decode(CheckWords.self)
|
||||||
|
try checkForBadWords(in: input.string)
|
||||||
|
return .ok
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CheckWords: Content {
|
||||||
|
let string: String
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ProConDTO: Content {
|
struct ProConDTO: Content {
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
func checkForBadWords(in string: String) throws {
|
import Foundation
|
||||||
if badWords.contains(string) {
|
import Vapor
|
||||||
throw BadWordError()
|
|
||||||
} else if string.contains(" ") {
|
|
||||||
let parts = string.split(separator: " ")
|
|
||||||
for part in parts {
|
|
||||||
if badWords.contains(String(part)) {
|
|
||||||
throw BadWordError()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct BadWordError: Error {}
|
func checkForBadWords(in string: String) throws {
|
||||||
|
let split = string.split(separator: "\n")
|
||||||
|
for string in split {
|
||||||
|
for word in badWords where string.contains(word) {
|
||||||
|
throw Abort(.badRequest, reason: "Stop using such naughty language.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let badWords: [String] = [
|
let badWords: [String] = [
|
||||||
|
"420",
|
||||||
|
"69",
|
||||||
"puppy",
|
"puppy",
|
||||||
|
"kitty",
|
||||||
"2g1c",
|
"2g1c",
|
||||||
"a-hole",
|
"a-hole",
|
||||||
"a-holes",
|
"a-holes",
|
||||||
|
|||||||
@@ -8,64 +8,59 @@ func routes(_ app: Application) throws {
|
|||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
app.get("loggedIn") { req in
|
app.get("loggedIn") { req -> View in
|
||||||
guard let userIdString = req.session.data["userId"],
|
guard let user = try await req.currentUser(withProsAndCons: true) else {
|
||||||
let displayName = req.session.data["displayName"],
|
throw Abort(.badRequest)
|
||||||
let userId = UUID(uuidString: userIdString)
|
|
||||||
else {
|
|
||||||
return try await req.view.render("/")
|
|
||||||
}
|
}
|
||||||
guard let user = try await User.query(on: req.db)
|
|
||||||
.filter(\.$id == userId)
|
|
||||||
.with(\.$prosAndCons)
|
|
||||||
.first()
|
|
||||||
else {
|
|
||||||
throw Abort(.unauthorized)
|
|
||||||
}
|
|
||||||
// let prosAndCons = try await user.$prosAndCons.get(on: req.db)
|
|
||||||
return try await req.view.render(
|
return try await req.view.render(
|
||||||
"loggedIn",
|
"loggedIn",
|
||||||
LoggedInContext(name: displayName, prosAndCons: user.prosAndCons)
|
LoggedInContext(name: user.displayName, prosAndCons: user.prosAndCons)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
app.get("submitProOrCon") { req in
|
app.get("submitProOrCon") { req in
|
||||||
let params = try req.query.decode(SubmitProOrCon.self)
|
let params = try req.query.decode(SubmitProOrCon.self)
|
||||||
guard let userIdString = req.session.data["userId"],
|
guard let userId = req.userId else {
|
||||||
let userId = UUID(uuidString: userIdString)
|
|
||||||
else {
|
|
||||||
throw Abort(.unauthorized)
|
throw Abort(.unauthorized)
|
||||||
}
|
}
|
||||||
|
try checkForBadWords(in: params.description)
|
||||||
let proOrCon = ProCon(type: params.type, description: params.description, userId: userId)
|
let proOrCon = ProCon(type: params.type, description: params.description, userId: userId)
|
||||||
_ = try await req.db.transaction {
|
try await proOrCon.save(on: req.db)
|
||||||
proOrCon.save(on: $0)
|
|
||||||
}
|
|
||||||
.get()
|
|
||||||
|
|
||||||
return req.redirect(to: "loggedIn")
|
return req.redirect(to: "loggedIn")
|
||||||
}
|
}
|
||||||
|
|
||||||
app.get("login") { req in
|
app.get("login") { req in
|
||||||
let params = try req.query.decode(LoginParams.self)
|
let params = try req.query.decode(LoginParams.self)
|
||||||
req.logger.info("params: \(params)")
|
req.logger.info("params: \(params)")
|
||||||
|
|
||||||
do {
|
|
||||||
try checkForBadWords(in: params.displayName)
|
try checkForBadWords(in: params.displayName)
|
||||||
} catch {
|
let user = User(displayName: params.displayName)
|
||||||
throw Abort(.unauthorized, reason: "Stop using such naughty language.")
|
try await user.save(on: req.db)
|
||||||
|
req.session.data["userId"] = user.id?.uuidString
|
||||||
|
return req.redirect(to: "loggedIn")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let user = User(displayName: params.displayName)
|
private extension Request {
|
||||||
_ = try await req.db.transaction {
|
|
||||||
user.save(on: $0)
|
|
||||||
}.get()
|
|
||||||
|
|
||||||
let userId = user.id?.uuidString ?? "nil"
|
var userId: UUID? {
|
||||||
req.session.data["userId"] = userId
|
guard let userIdString = session.data["userId"],
|
||||||
req.session.data["displayName"] = user.displayName
|
let userId = UUID(uuidString: userIdString)
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return userId
|
||||||
|
}
|
||||||
|
|
||||||
// return try await req.view.render("loggedIn", ["name": user.displayName])
|
func currentUser(withProsAndCons: Bool) async throws -> User? {
|
||||||
return req.redirect(to: "loggedIn")
|
guard let userId = userId else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var query = User.query(on: db).filter(\.$id == userId)
|
||||||
|
if withProsAndCons {
|
||||||
|
query = query.with(\.$prosAndCons)
|
||||||
|
}
|
||||||
|
return try await query.first()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user