Skip to content

Commit 735a36e

Browse files
author
Robert Quander
committed
implemented searc (useful for next flagstore)
1 parent d03785b commit 735a36e

File tree

1 file changed

+213
-128
lines changed

1 file changed

+213
-128
lines changed

service/Sources/App/routes.swift

Lines changed: 213 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -996,151 +996,236 @@ app.post("register") { req async throws -> Response in
996996
body: .init(string: html))
997997
}
998998

999-
app.get("home-of", ":homeID") { req async throws -> Response in
1000-
let raw = req.parameters.get("homeID") ?? ""
1001-
guard
1002-
let lookupID = UUID(uuidString: raw),
1003-
let profile = try await Profile.query(on: req.db)
1004-
.filter(\.$homeID == lookupID)
1005-
.first()
1006-
else {
1007-
return req.redirect(to: "/usernotfound?username=\(raw.htmlEscaped())")
1008-
}
999+
app.get("home-of", ":homeID") { req async throws -> Response in
1000+
let raw = req.parameters.get("homeID") ?? ""
1001+
guard
1002+
let lookupID = UUID(uuidString: raw),
1003+
let profile = try await Profile.query(on: req.db)
1004+
.filter(\.$homeID == lookupID)
1005+
.first()
1006+
else {
1007+
return req.redirect(to: "/usernotfound?username=\(raw.htmlEscaped())")
1008+
}
10091009

1010-
// Fetch only public posts
1011-
let posts = try await Post.query(on: req.db)
1012-
.filter(\.$profile.$id == profile.id!)
1013-
.filter(\.$isPrivate == false)
1014-
.sort(\.$timestamp, .descending)
1015-
.all()
1010+
// Fetch only public posts
1011+
let posts = try await Post.query(on: req.db)
1012+
.filter(\.$profile.$id == profile.id!)
1013+
.filter(\.$isPrivate == false)
1014+
.sort(\.$timestamp, .descending)
1015+
.all()
10161016

1017-
// Build the posts HTML
1018-
var postHTML = ""
1019-
for post in posts {
1020-
let rawText = String(data: post.encryptedText, encoding: .utf8) ?? ""
1021-
let text = rawText.htmlEscaped()
1022-
let imageTag = post.imagePath.map { path -> String in
1023-
let fn = URL(fileURLWithPath: path).lastPathComponent
1024-
return "<img src='/uploads/\(fn)' style='max-width:300px'><br>"
1025-
} ?? ""
1026-
let ts = post.timestamp.map { "\($0)" } ?? "Unknown"
1027-
postHTML += """
1028-
<div class="post">
1029-
\(imageTag)
1030-
<p>\(text)</p>
1031-
<p><em>\(ts)</em> | 🌴 \(post.facepalmCount)</p>
1017+
// Build the posts HTML
1018+
var postHTML = ""
1019+
for post in posts {
1020+
let rawText = String(data: post.encryptedText, encoding: .utf8) ?? ""
1021+
let text = rawText.htmlEscaped()
1022+
let imageTag = post.imagePath.map { path -> String in
1023+
let fn = URL(fileURLWithPath: path).lastPathComponent
1024+
return "<img src='/uploads/\(fn)' style='max-width:300px'><br>"
1025+
} ?? ""
1026+
let ts = post.timestamp.map { "\($0)" } ?? "Unknown"
1027+
postHTML += """
1028+
<div class="post">
1029+
\(imageTag)
1030+
<p>\(text)</p>
1031+
<p><em>\(ts)</em> | 🌴 \(post.facepalmCount)</p>
1032+
</div>
1033+
"""
1034+
}
1035+
1036+
// Bio
1037+
let bioHTML = profile.bio?.htmlEscaped() ?? "<em>This user has no bio.</em>"
1038+
1039+
// Profile picture (if any)
1040+
let profilePicTag: String = {
1041+
let uploadsDir = req.application.directory.publicDirectory + "uploads"
1042+
let files = (try? FileManager.default.contentsOfDirectory(atPath: uploadsDir)) ?? []
1043+
if let pic = files.first(where: { $0.contains(profile.id!.uuidString) }) {
1044+
return "<img src='/uploads/\(pic)' style='max-width:150px;border-radius:10px'>"
1045+
} else {
1046+
return ""
1047+
}
1048+
}()
1049+
1050+
// Follow / Unfollow form
1051+
var followForm = ""
1052+
if
1053+
let sid = req.session.data["profileID"],
1054+
let visitorID = UUID(uuidString: sid),
1055+
visitorID != profile.id,
1056+
let visitor = try await Profile.find(visitorID, on: req.db)
1057+
{
1058+
let attached = try await visitor.$follows.isAttached(to: profile, on: req.db)
1059+
if attached {
1060+
followForm = """
1061+
<form method="POST" action="/settings/unfriend" onsubmit="return confirm('Are you sure?');">
1062+
<input type="hidden" name="unfollowID" value="\(profile.id!)">
1063+
<button type="submit" style="background-color:#f88">Unfriend</button>
1064+
</form>
1065+
"""
1066+
} else {
1067+
followForm = """
1068+
<form method="POST" action="/add-friend">
1069+
<input type="hidden" name="targetID" value="\(profile.id!)">
1070+
<button type="submit">Add Friend</button>
1071+
</form>
1072+
"""
1073+
}
1074+
}
1075+
1076+
// Assemble the page
1077+
let contentHTML = """
1078+
<div class="container">
1079+
<h2>\(profile.username)'s Public Profile</h2>
1080+
<div style="display:flex;align-items:center;gap:20px;">
1081+
\(profilePicTag)
1082+
\(followForm)
1083+
</div>
1084+
<p><strong>Bio:</strong><br>\(bioHTML)</p>
1085+
<h3>Public Posts</h3>
1086+
\(postHTML.isEmpty ? "<p>No public posts yet.</p>" : postHTML)
10321087
</div>
10331088
"""
1089+
1090+
let html = pageHead("\(profile.username)'s Profile")
1091+
+ banner("""
1092+
<a href="/feed">Feed</a>
1093+
<a href="/logout">Logout</a>
1094+
<a href="/profile">My Profile</a>
1095+
""")
1096+
+ contentHTML
1097+
+ pageFooter()
1098+
1099+
return Response(
1100+
status: .ok,
1101+
headers: ["Content-Type": "text/html; charset=utf-8"],
1102+
body: .init(string: html)
1103+
)
10341104
}
10351105

1036-
// Bio
1037-
let bioHTML = profile.bio?.htmlEscaped() ?? "<em>This user has no bio.</em>"
1038-
1039-
// Profile picture (if any)
1040-
let profilePicTag: String = {
1041-
let uploadsDir = req.application.directory.publicDirectory + "uploads"
1042-
let files = (try? FileManager.default.contentsOfDirectory(atPath: uploadsDir)) ?? []
1043-
if let pic = files.first(where: { $0.contains(profile.id!.uuidString) }) {
1044-
return "<img src='/uploads/\(pic)' style='max-width:150px;border-radius:10px'>"
1045-
} else {
1046-
return ""
1047-
}
1048-
}()
1049-
1050-
// Follow / Unfollow form
1051-
var followForm = ""
1052-
if
1053-
let sid = req.session.data["profileID"],
1054-
let visitorID = UUID(uuidString: sid),
1055-
visitorID != profile.id,
1056-
let visitor = try await Profile.find(visitorID, on: req.db)
1057-
{
1058-
let attached = try await visitor.$follows.isAttached(to: profile, on: req.db)
1059-
if attached {
1060-
followForm = """
1061-
<form method="POST" action="/settings/unfriend" onsubmit="return confirm('Are you sure?');">
1062-
<input type="hidden" name="unfollowID" value="\(profile.id!)">
1063-
<button type="submit" style="background-color:#f88">Unfriend</button>
1064-
</form>
1065-
"""
1066-
} else {
1067-
followForm = """
1068-
<form method="POST" action="/add-friend">
1069-
<input type="hidden" name="targetID" value="\(profile.id!)">
1070-
<button type="submit">Add Friend</button>
1071-
</form>
1072-
"""
1106+
1107+
// FRIENDSHIP ACTIONS
1108+
app.post("add-friend") { req async throws -> Response in
1109+
struct AF: Content { let targetID: UUID }
1110+
let d = try req.content.decode(AF.self)
1111+
guard
1112+
let sid = req.session.data["profileID"],
1113+
let pid = UUID(uuidString: sid),
1114+
let source = try await Profile.find(pid, on: req.db),
1115+
let target = try await Profile.find(d.targetID, on: req.db)
1116+
else {
1117+
return req.redirect(to: "/login")
10731118
}
1074-
}
10751119

1076-
// Assemble the page
1077-
let contentHTML = """
1078-
<div class="container">
1079-
<h2>\(profile.username)'s Public Profile</h2>
1080-
<div style="display:flex;align-items:center;gap:20px;">
1081-
\(profilePicTag)
1082-
\(followForm)
1083-
</div>
1084-
<p><strong>Bio:</strong><br>\(bioHTML)</p>
1085-
<h3>Public Posts</h3>
1086-
\(postHTML.isEmpty ? "<p>No public posts yet.</p>" : postHTML)
1087-
</div>
1088-
"""
1120+
if !(try await source.$follows.isAttached(to: target, on: req.db)) {
1121+
try await source.$follows.attach(target, on: req.db)
1122+
}
10891123

1090-
let html = pageHead("\(profile.username)'s Profile")
1091-
+ banner("""
1092-
<a href="/feed">Feed</a>
1093-
<a href="/logout">Logout</a>
1094-
<a href="/profile">My Profile</a>
1095-
""")
1096-
+ contentHTML
1097-
+ pageFooter()
1124+
return req.redirect(to: "/home-of/\(target.homeID.uuidString)")
1125+
}
10981126

1099-
return Response(
1100-
status: .ok,
1101-
headers: ["Content-Type": "text/html; charset=utf-8"],
1102-
body: .init(string: html)
1103-
)
1104-
}
1127+
app.post("unfriend") { req async throws -> Response in
1128+
struct UF: Content { let targetID: UUID }
1129+
let d = try req.content.decode(UF.self)
1130+
guard
1131+
let sid = req.session.data["profileID"],
1132+
let pid = UUID(uuidString: sid),
1133+
let source = try await Profile.find(pid, on: req.db),
1134+
let target = try await Profile.find(d.targetID, on: req.db)
1135+
else {
1136+
return req.redirect(to: "/login")
1137+
}
11051138

1139+
if try await source.$follows.isAttached(to: target, on: req.db) {
1140+
try await source.$follows.detach(target, on: req.db)
1141+
}
11061142

1107-
// FRIENDSHIP ACTIONS
1108-
app.post("add-friend") { req async throws -> Response in
1109-
struct AF: Content { let targetID: UUID }
1110-
let d = try req.content.decode(AF.self)
1111-
guard
1112-
let sid = req.session.data["profileID"],
1113-
let pid = UUID(uuidString: sid),
1114-
let source = try await Profile.find(pid, on: req.db),
1115-
let target = try await Profile.find(d.targetID, on: req.db)
1116-
else {
1117-
return req.redirect(to: "/login")
1143+
return req.redirect(to: "/home-of/\(target.homeID.uuidString)")
11181144
}
11191145

1120-
if !(try await source.$follows.isAttached(to: target, on: req.db)) {
1121-
try await source.$follows.attach(target, on: req.db)
1122-
}
1146+
app.get("search") { req async throws -> Response in
1147+
// 1) Guard session like in /feed, /settings, etc.
1148+
guard let sid = req.session.data["profileID"],
1149+
UUID(uuidString: sid) != nil
1150+
else {
1151+
return req.redirect(to: "/login")
1152+
}
11231153

1124-
return req.redirect(to: "/home-of/\(target.homeID.uuidString)")
1125-
}
1154+
// 2) Grab & sanitize the query
1155+
let q = req.query[String.self, at: "q"]?
1156+
.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
11261157

1127-
app.post("unfriend") { req async throws -> Response in
1128-
struct UF: Content { let targetID: UUID }
1129-
let d = try req.content.decode(UF.self)
1130-
guard
1131-
let sid = req.session.data["profileID"],
1132-
let pid = UUID(uuidString: sid),
1133-
let source = try await Profile.find(pid, on: req.db),
1134-
let target = try await Profile.find(d.targetID, on: req.db)
1135-
else {
1136-
return req.redirect(to: "/login")
1137-
}
1158+
// 3) Build the page head + banner + search form
1159+
var html = pageHead("Search Profiles – Facepalm")
1160+
+ banner("""
1161+
<a href="/profile">Profile</a>
1162+
<a href="/feed">Feed</a>
1163+
""")
1164+
+ """
1165+
<main>
1166+
<div class="container">
1167+
<h1>Search Profiles</h1>
1168+
<form method="GET" action="/search" style="margin-bottom:20px;">
1169+
<input
1170+
type="text"
1171+
name="q"
1172+
value="\(q.htmlEscaped())"
1173+
placeholder="Username…"
1174+
style="width:70%; padding:8px;"
1175+
>
1176+
<button type="submit" class="action-btn">Search</button>
1177+
</form>
1178+
"""
11381179

1139-
if try await source.$follows.isAttached(to: target, on: req.db) {
1140-
try await source.$follows.detach(target, on: req.db)
1141-
}
1180+
// 4) If there's a query, run the DB lookup & render results
1181+
if !q.isEmpty {
1182+
let pattern = "%\(q)%"
1183+
let matches = try await Profile.query(on: req.db)
1184+
.filter(\.$username, .custom("ILIKE"), pattern)
1185+
.limit(50)
1186+
.all()
1187+
1188+
html += "<ul style=\"list-style:none; padding:0;\">"
1189+
1190+
// reuse the same uploads‐dir lookup as in your settings/friends section
1191+
let uploadsDir = req.application.directory.publicDirectory + "uploads"
1192+
let files = (try? FileManager.default.contentsOfDirectory(atPath: uploadsDir)) ?? []
1193+
1194+
for p in matches {
1195+
// pick the profile pic if it exists, otherwise use the Facepalm logo
1196+
let picFile = files.first { $0.contains(p.id!.uuidString) } ?? ""
1197+
let picURL = picFile.isEmpty
1198+
? "/facepalm_logo.png"
1199+
: "/uploads/\(picFile)"
1200+
html += """
1201+
<li style="display:flex;align-items:center;margin-bottom:16px;">
1202+
<a href="/home-of/\(p.homeID.uuidString)" style="margin-right:12px;">
1203+
<img
1204+
src="\(picURL)"
1205+
alt="\(p.username.htmlEscaped())"
1206+
style="width:48px;height:48px;border-radius:50%;object-fit:cover;"
1207+
>
1208+
</a>
1209+
<a href="/home-of/\(p.homeID.uuidString)" style="font-weight:500;">
1210+
\(p.username.htmlEscaped())
1211+
</a>
1212+
</li>
1213+
"""
1214+
}
1215+
html += "</ul>"
1216+
}
11421217

1143-
return req.redirect(to: "/home-of/\(target.homeID.uuidString)")
1144-
}
1218+
// 5) Close tags and footer
1219+
html += """
1220+
</div>
1221+
</main>
1222+
""" + pageFooter()
11451223

1224+
// 6) Return the response
1225+
return Response(
1226+
status: .ok,
1227+
headers: ["Content-Type":"text/html; charset=utf-8"],
1228+
body: .init(string: html)
1229+
)
1230+
}
11461231
}

0 commit comments

Comments
 (0)