@@ -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