diff --git a/public/css/profile.css b/public/css/profile.css
index 8f212dd..112b1bf 100644
--- a/public/css/profile.css
+++ b/public/css/profile.css
@@ -119,7 +119,7 @@ body {
border-bottom: 3px solid var(--logo) !important;
}
-#about {
+#about, #friends {
margin-top: 2em;
align-self: center;
padding: 0;
@@ -160,4 +160,36 @@ body {
.logout {
flex: 1;
+}
+
+.follow {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 40px;
+ width: 175px;
+ background-color: var(--secondary);
+ border-radius: 10px;
+ cursor: pointer;
+}
+
+.follow>span {
+ color: var(--medium);
+}
+
+.friend {
+ background-color: var(--logo);
+ border: 1px solid var(#ffffff)
+}
+
+.friend>span {
+ color: #ffffff;
+}
+
+.right {
+ flex: 1;
+ display: flex;
+ justify-content: end;
+ align-items: center;
+ padding-right: 50px;
}
\ No newline at end of file
diff --git a/public/js/api.js b/public/js/api.js
index 176ae59..5a55460 100644
--- a/public/js/api.js
+++ b/public/js/api.js
@@ -79,6 +79,18 @@ export const loadself = async () => {
return await request("/users/self", {})
}
+export const follow = async (state, user_id) => {
+ return await request('/users/follow', {state, user_id}, 'PUT')
+}
+
+export const follow_status = async (user_id) => {
+ return await request('/users/follow', {user_id})
+}
+
+export const friends = async (user_id) => {
+ return await request('/users/friends', {user_id})
+}
+
export const postcomment = async (post_id, content) => {
return await request('/posts/comment', {post_id, content}, 'PATCH')
}
diff --git a/public/js/people.js b/public/js/people.js
index 0a97e93..99890d9 100644
--- a/public/js/people.js
+++ b/public/js/people.js
@@ -34,22 +34,7 @@ const data = {
}
async function load() {
- let request = await loadself()
- if (request.status === 429) {
- let new_body =
- body({},
- ...header(false, true)
- )
-
- document.body.replaceWith(new_body)
- throw new Error("Rate limited");
- }
-
-
- const self = request.json
-
- header(false, true, self.user_id)
const users = (await loaduserspage(page)).json
if (users.length === 0) {
diff --git a/public/js/profile.js b/public/js/profile.js
index dd5216d..4e4f44f 100644
--- a/public/js/profile.js
+++ b/public/js/profile.js
@@ -1,31 +1,55 @@
import { div, pfp, banner, parse, button, body, a, span, crawl, parseDate, parseMonth } from './main.js'
-import { loadself, loadusers, loadusersposts, updateavatar, updatebanner, logout } from './api.js'
-import { parsePost, header } from './components.js'
+import { loadself, loadusers, loadusersposts, updateavatar, updatebanner, logout, follow, follow_status, friends } from './api.js'
+import { parsePost, parseUser, header } from './components.js'
-function swap(value) {
- let postsb = document.getElementById("profilepostbutton");
- let aboutb = document.getElementById("profileaboutbutton");
- let posts = document.getElementById("posts");
- let about = document.getElementById("about");
- let load = document.getElementsByClassName("loadp")[0];
+function swap(tab) {
+ let post_button = document.querySelector("#profilepostbutton");
+ let about_button = document.querySelector("#profileaboutbutton");
+ let friends_button = document.querySelector("#profilefriendsbutton");
- if (value) {
+ let posts_section = document.querySelector("#posts");
+ let about_section = document.querySelector("#about");
+ let friends_section = document.querySelector("#friends");
+
+ let load = document.querySelector(".loadp");
- postsb.classList.add("selected")
- aboutb.classList.remove("selected")
- about.classList.add("hidden")
- posts.classList.remove("hidden")
+ if (tab === 0) {
+
+ post_button.classList.add("selected")
+ about_button.classList.remove("selected")
+ friends_button.classList.remove("selected")
+
+ posts_section.classList.remove("hidden")
+ about_section.classList.add("hidden")
+ friends_section.classList.add("hidden")
if (load) {
load.classList.remove("hidden")
}
- } else {
+ } else if (tab === 1) {
- postsb.classList.remove("selected")
- aboutb.classList.add("selected")
- about.classList.remove("hidden")
- posts.classList.add("hidden")
+ post_button.classList.remove("selected")
+ about_button.classList.add("selected")
+ friends_button.classList.remove("selected")
+
+ posts_section.classList.add("hidden")
+ about_section.classList.remove("hidden")
+ friends_section.classList.add("hidden")
+
+ if (load) {
+ load.classList.add("hidden")
+ }
+
+ } else if (tab === 2) {
+
+ post_button.classList.remove("selected")
+ about_button.classList.remove("selected")
+ friends_button.classList.add("selected")
+
+ posts_section.classList.add("hidden")
+ about_section.classList.add("hidden")
+ friends_section.classList.remove("hidden")
if (load) {
load.classList.add("hidden")
@@ -67,9 +91,37 @@ function changeimage(fn) {
input.click();
}
-function render() {
+function status_text(status) {
+ switch (status) {
+ case 1:
+ return 'Following ✓'
+ case 2:
+ return 'Follow Back'
+ case 3:
+ return 'Friends ✓'
+ default:
+ return 'Follow'
+ }
+}
- let new_body =
+async function render() {
+
+ let status;
+ if (!isself) {
+ let response = await follow_status(data.user.user_id)
+ if (response.status == 200) {
+ status = parseInt(response.msg)
+ } else {
+ status = 0;
+ }
+ }
+
+ let friends_arr = (await friends(data.user.user_id)).json
+ if (friends_arr == undefined) {
+ friends_arr = []
+ }
+
+ let new_body =
body({},
...header(false, false, data.self.user_id),
div({id: 'top'},
@@ -91,16 +143,53 @@ function render() {
span({class: 'gtext'},
parse('Joined ' + parseDate(new Date(data.user.date)))
)
+ ),
+ !isself ?
+ div({class: 'right'},
+ div({class: `follow ${status == 3 ? 'friend' : ''}`, onclick: async (event) => {
+ let button = event.target
+ if (button.tagName == 'SPAN') {
+ button = button.parentElement
+ }
+
+ let response
+ if (status % 2 == 0) {
+ response = await follow(true, data.user.user_id);
+ } else {
+ response = await follow(false, data.user.user_id);
+ }
+
+ if (response.status == 200) {
+ status = parseInt(response.msg)
+ } else {
+ return
+ }
+
+ button.firstChild.innerHTML = status_text(status)
+ if (status == 3) {
+ button.classList.add('friend')
+ } else {
+ button.classList.remove('friend')
+ }
+ }},
+ span({class: 'gtext'},
+ parse(status_text(status))
+ )
+ )
)
+ : parse('')
),
div({class: 'fullline', style: 'width: 80em; margin-bottom: 0; z-index: 0;'}),
div({class: 'profilebuttons'},
- button({id: 'profilepostbutton', class: 'selected', onclick: () => swap(true)},
+ button({id: 'profilepostbutton', class: 'selected', onclick: () => swap(0)},
parse('Posts')
),
- button({id: 'profileaboutbutton', onclick: () => swap(false)},
+ button({id: 'profileaboutbutton', onclick: () => swap(1)},
parse('About')
),
+ button({id: 'profilefriendsbutton', onclick: () => swap(2)},
+ parse('Friends')
+ ),
div({style: 'flex: 20'}),
isself ? button({class: 'logout', onclick: async () => {
const response = await logout()
@@ -154,6 +243,9 @@ function render() {
)
)
),
+ div({id: 'friends', class: 'hidden'},
+ ...friends_arr.map(u => parseUser(u))
+ ),
div({id: 'popup', class: 'hidden'},
div({class: 'createpost'},
div({class: 'close', onclick: () => document.getElementById('popup').classList.add('hidden')}),
@@ -207,7 +299,6 @@ async function load(id) {
if (el) {
el.remove()
}
- return []
} else {
page++
}
@@ -269,7 +360,7 @@ async function init() {
const posts = await load(id)
data.posts.push(... posts)
-
+
data.user = data.users[id]
render()
diff --git a/public/profile.html b/public/profile.html
index 35debf8..6d4b117 100644
--- a/public/profile.html
+++ b/public/profile.html
@@ -18,6 +18,7 @@
+
diff --git a/src/api/mod.rs b/src/api/mod.rs
index 12563e3..cd2190c 100644
--- a/src/api/mod.rs
+++ b/src/api/mod.rs
@@ -16,7 +16,7 @@ pub use auth::RegistrationRequet;
pub fn router() -> Router {
let governor_conf = Box::new(
GovernorConfigBuilder::default()
- .burst_size(10)
+ .burst_size(15)
.per_second(1)
.key_extractor(SmartIpKeyExtractor)
.finish()
diff --git a/src/api/posts.rs b/src/api/posts.rs
index ca459cd..ee590ec 100644
--- a/src/api/posts.rs
+++ b/src/api/posts.rs
@@ -138,7 +138,7 @@ pub const COMMENTS_PAGE: EndpointDocumentation = EndpointDocumentation {
#[derive(Deserialize)]
struct CommentsPageRequest {
page: u64,
- post_id: u64
+ post_id: u64,
}
impl Check for CommentsPageRequest {
diff --git a/src/api/users.rs b/src/api/users.rs
index 0ce9988..082926e 100644
--- a/src/api/users.rs
+++ b/src/api/users.rs
@@ -1,7 +1,7 @@
use crate::{
public::docs::{EndpointDocumentation, EndpointMethod},
types::{
- extract::{AuthorizedUser, Check, CheckResult, Json, Png},
+ extract::{AuthorizedUser, Check, CheckResult, Json, Log, Png},
http::ResponseCode,
user::User,
},
@@ -116,7 +116,7 @@ pub const USERS_SELF: EndpointDocumentation = EndpointDocumentation {
cookie: Some("auth"),
};
-async fn load_self(AuthorizedUser(user): AuthorizedUser) -> Response {
+async fn load_self(AuthorizedUser(user): AuthorizedUser, _: Log) -> Response {
let Ok(json) = serde_json::to_string(&user) else {
return ResponseCode::InternalServerError.text("Failed to fetch user")
};
@@ -172,6 +172,143 @@ async fn banner(AuthorizedUser(user): AuthorizedUser, Png(img): Png) -> Response
ResponseCode::Success.text("Successfully updated banner")
}
+pub const USERS_FOLLOW: EndpointDocumentation = EndpointDocumentation {
+ uri: "/api/users/follow",
+ method: EndpointMethod::Put,
+ description: "Set following status of another user",
+ body: Some(
+ r#"
+ {
+ "user_id": 13,
+ "status": false
+ }
+ "#,
+ ),
+ responses: &[
+ (200, "Returns new follow status if successfull, see below"),
+ (400, "Body does not match parameters"),
+ (401, "Unauthorized"),
+ (500, "Failed to change follow status"),
+ ],
+ cookie: Some("auth"),
+};
+
+#[derive(Deserialize)]
+struct UserFollowRequest {
+ user_id: u64,
+ state: bool,
+}
+
+impl Check for UserFollowRequest {
+ fn check(&self) -> CheckResult {
+ Ok(())
+ }
+}
+
+async fn follow(
+ AuthorizedUser(user): AuthorizedUser,
+ Json(body): Json,
+) -> Response {
+ if body.state {
+ if let Err(err) = User::add_following(user.user_id, body.user_id) {
+ return err;
+ }
+ } else if let Err(err) = User::remove_following(user.user_id, body.user_id) {
+ return err;
+ }
+
+ match User::get_following(user.user_id, body.user_id) {
+ Ok(status) => ResponseCode::Success.text(&format!("{status}")),
+ Err(err) => err,
+ }
+}
+
+pub const USERS_FOLLOW_STATUS: EndpointDocumentation = EndpointDocumentation {
+ uri: "/api/users/follow",
+ method: EndpointMethod::Post,
+ description: "Get following status of another user",
+ body: Some(
+ r#"
+ {
+ "user_id": 13
+ }
+ "#,
+ ),
+ responses: &[
+ (
+ 200,
+ "Returns 0 if no relation, 1 if following, 2 if followed, 3 if both",
+ ),
+ (400, "Body does not match parameters"),
+ (401, "Unauthorized"),
+ (500, "Failed to retrieve follow status"),
+ ],
+ cookie: Some("auth"),
+};
+
+#[derive(Deserialize)]
+struct UserFollowStatusRequest {
+ user_id: u64,
+}
+
+impl Check for UserFollowStatusRequest {
+ fn check(&self) -> CheckResult {
+ Ok(())
+ }
+}
+
+async fn follow_status(
+ AuthorizedUser(user): AuthorizedUser,
+ Json(body): Json,
+) -> Response {
+ match User::get_following(user.user_id, body.user_id) {
+ Ok(status) => ResponseCode::Success.text(&format!("{status}")),
+ Err(err) => err,
+ }
+}
+
+pub const USERS_FRIENDS: EndpointDocumentation = EndpointDocumentation {
+ uri: "/api/users/friends",
+ method: EndpointMethod::Post,
+ description: "Returns friends of a user",
+ body: Some(
+ r#"
+ {
+ "user_id": 13
+ }
+ "#,
+ ),
+ responses: &[
+ (200, "Returns users in application/json"),
+ (401, "Unauthorized"),
+ (500, "Failed to fetch friends"),
+ ],
+ cookie: Some("auth"),
+};
+
+#[derive(Deserialize)]
+struct UserFriendsRequest {
+ user_id: u64,
+}
+
+impl Check for UserFriendsRequest {
+ fn check(&self) -> CheckResult {
+ Ok(())
+ }
+}
+
+async fn friends(AuthorizedUser(_user): AuthorizedUser, Json(body): Json) -> Response {
+ let Ok(users) = User::get_friends(body.user_id) else {
+ return ResponseCode::InternalServerError.text("Failed to fetch user")
+ };
+
+ let Ok(json) = serde_json::to_string(&users) else {
+ return ResponseCode::InternalServerError.text("Failed to fetch user")
+ };
+
+ ResponseCode::Success.json(&json)
+}
+
pub fn router() -> Router {
Router::new()
.route("/load", post(load_batch))
@@ -179,4 +316,6 @@ pub fn router() -> Router {
.route("/page", post(load_page))
.route("/avatar", put(avatar))
.route("/banner", put(banner))
+ .route("/follow", put(follow).post(follow_status))
+ .route("/friends", post(friends))
}
diff --git a/src/database/friends.rs b/src/database/friends.rs
new file mode 100644
index 0000000..0b78488
--- /dev/null
+++ b/src/database/friends.rs
@@ -0,0 +1,97 @@
+use tracing::instrument;
+
+use crate::{
+ database::{self, users::user_from_row},
+ types::user::{User, FOLLOWED, FOLLOWING, NO_RELATION},
+};
+
+pub fn init() -> Result<(), rusqlite::Error> {
+ let sql = "
+ CREATE TABLE IF NOT EXISTS friends (
+ follower_id INTEGER NOT NULL,
+ followee_id INTEGER NOT NULL,
+ FOREIGN KEY(follower_id) REFERENCES users(user_id),
+ FOREIGN KEY(followee_id) REFERENCES users(user_id),
+ PRIMARY KEY (follower_id, followee_id)
+ );
+ ";
+ let conn = database::connect()?;
+ conn.execute(sql, ())?;
+ Ok(())
+}
+
+#[instrument()]
+pub fn get_friend_status(user_id_1: u64, user_id_2: u64) -> Result {
+ tracing::trace!("Retrieving friend status");
+ let conn = database::connect()?;
+ let mut stmt = conn.prepare("SELECT * FROM friends WHERE (follower_id = ? AND followee_id = ?) OR (follower_id = ? AND followee_id = ?);")?;
+ let mut status = NO_RELATION;
+ let rows: Vec = stmt
+ .query_map([user_id_1, user_id_2, user_id_2, user_id_1], |row| {
+ let id: u64 = row.get(0)?;
+ Ok(id)
+ })?
+ .into_iter()
+ .flatten()
+ .collect();
+
+ for follower in rows {
+ if follower == user_id_1 {
+ status |= FOLLOWING;
+ }
+
+ if follower == user_id_2 {
+ status |= FOLLOWED;
+ }
+ }
+
+ Ok(status)
+}
+
+#[instrument()]
+pub fn get_friends(user_id: u64) -> Result, rusqlite::Error> {
+ tracing::trace!("Retrieving friends");
+ let conn = database::connect()?;
+ let mut stmt = conn.prepare(
+ "
+ SELECT *
+ FROM users u
+ WHERE EXISTS (
+ SELECT NULL
+ FROM friends f
+ WHERE u.user_id = f.follower_id
+ AND f.followee_id = ?
+ )
+ AND EXISTS (
+ SELECT NULL
+ FROM friends f
+ WHERE u.user_id = f.followee_id
+ AND f.follower_id = ?
+ )
+ ",
+ )?;
+ let row = stmt.query_map([user_id, user_id], |row| {
+ let row = user_from_row(row, true)?;
+ Ok(row)
+ })?;
+ Ok(row.into_iter().flatten().collect())
+}
+
+#[instrument()]
+pub fn set_following(user_id_1: u64, user_id_2: u64) -> Result {
+ tracing::trace!("Setting following");
+ let conn = database::connect()?;
+ let mut stmt =
+ conn.prepare("INSERT OR REPLACE INTO friends (follower_id, followee_id) VALUES (?,?)")?;
+ let changes = stmt.execute([user_id_1, user_id_2])?;
+ Ok(changes == 1)
+}
+
+#[instrument()]
+pub fn remove_following(user_id_1: u64, user_id_2: u64) -> Result {
+ tracing::trace!("Removing following");
+ let conn = database::connect()?;
+ let mut stmt = conn.prepare("DELETE FROM friends WHERE follower_id = ? AND followee_id = ?")?;
+ let changes = stmt.execute([user_id_1, user_id_2])?;
+ Ok(changes == 1)
+}
diff --git a/src/database/likes.rs b/src/database/likes.rs
index 6f6939e..f6a130b 100644
--- a/src/database/likes.rs
+++ b/src/database/likes.rs
@@ -37,9 +37,7 @@ pub fn get_liked(user_id: u64, post_id: u64) -> Result {
tracing::trace!("Retrieving if liked");
let conn = database::connect()?;
let mut stmt = conn.prepare("SELECT * FROM likes WHERE user_id = ? AND post_id = ?")?;
- let liked = stmt.query_row([user_id, post_id], |_| {
- Ok(())
- }).optional()?;
+ let liked = stmt.query_row([user_id, post_id], |_| Ok(())).optional()?;
Ok(liked.is_some())
}
@@ -49,7 +47,7 @@ pub fn add_liked(user_id: u64, post_id: u64) -> Result {
let conn = database::connect()?;
let mut stmt = conn.prepare("INSERT OR REPLACE INTO likes (user_id, post_id) VALUES (?,?)")?;
let changes = stmt.execute([user_id, post_id])?;
- Ok(changes == 1)
+ Ok(changes == 1)
}
#[instrument()]
@@ -69,7 +67,7 @@ pub fn get_all_likes() -> Result, rusqlite::Error> {
let row = stmt.query_map([], |row| {
let like = Like {
user_id: row.get(0)?,
- post_id: row.get(1)?
+ post_id: row.get(1)?,
};
Ok(like)
})?;
diff --git a/src/database/mod.rs b/src/database/mod.rs
index 6d4853a..d22a350 100644
--- a/src/database/mod.rs
+++ b/src/database/mod.rs
@@ -1,6 +1,7 @@
use tracing::instrument;
pub mod comments;
+pub mod friends;
pub mod likes;
pub mod posts;
pub mod sessions;
@@ -16,6 +17,7 @@ pub fn init() -> Result<(), rusqlite::Error> {
sessions::init()?;
likes::init()?;
comments::init()?;
+ friends::init()?;
Ok(())
}
diff --git a/src/database/users.rs b/src/database/users.rs
index 15565f1..6062ea8 100644
--- a/src/database/users.rs
+++ b/src/database/users.rs
@@ -31,7 +31,7 @@ pub fn init() -> Result<(), rusqlite::Error> {
Ok(())
}
-fn user_from_row(row: &Row, hide_password: bool) -> Result {
+pub fn user_from_row(row: &Row, hide_password: bool) -> Result {
let user_id = row.get(0)?;
let firstname = row.get(1)?;
let lastname = row.get(2)?;
diff --git a/src/public/admin.rs b/src/public/admin.rs
index 25941f1..bf0a155 100644
--- a/src/public/admin.rs
+++ b/src/public/admin.rs
@@ -5,7 +5,9 @@ use tokio::sync::Mutex;
use crate::{
console::sanatize,
- types::{http::ResponseCode, post::Post, session::Session, user::User, comment::Comment, like::Like},
+ types::{
+ comment::Comment, http::ResponseCode, like::Like, post::Post, session::Session, user::User,
+ },
};
lazy_static! {
@@ -141,7 +143,11 @@ pub fn generate_comments() -> Response {
for comment in comments {
html.push_str(&format!(
"{} | {} | {} | {} | {} |
",
- comment.comment_id, comment.user_id, comment.post_id, sanatize(&comment.content), comment.date
+ comment.comment_id,
+ comment.user_id,
+ comment.post_id,
+ sanatize(&comment.content),
+ comment.date
));
}
diff --git a/src/public/console.rs b/src/public/console.rs
index 16bf4a3..251dbc1 100644
--- a/src/public/console.rs
+++ b/src/public/console.rs
@@ -84,9 +84,9 @@ impl Formatter for HtmlFormatter {
W: ?Sized + io::Write,
{
let s = if value {
- b"true" as &[u8]
+ b" true " as &[u8]
} else {
- b"false" as &[u8]
+ b" false " as &[u8]
};
writer.write_all(s)
}
@@ -95,7 +95,7 @@ impl Formatter for HtmlFormatter {
where
W: ?Sized + io::Write,
{
- let buff = format!("{value}");
+ let buff = format!(" {value} ");
writer.write_all(buff.as_bytes())
}
@@ -103,7 +103,7 @@ impl Formatter for HtmlFormatter {
where
W: ?Sized + io::Write,
{
- let buff = format!("{value}");
+ let buff = format!(" {value} ");
writer.write_all(buff.as_bytes())
}
@@ -111,7 +111,7 @@ impl Formatter for HtmlFormatter {
where
W: ?Sized + io::Write,
{
- let buff = format!("{value}");
+ let buff = format!(" {value} ");
writer.write_all(buff.as_bytes())
}
@@ -119,7 +119,7 @@ impl Formatter for HtmlFormatter {
where
W: ?Sized + io::Write,
{
- let buff = format!("{value}");
+ let buff = format!(" {value} ");
writer.write_all(buff.as_bytes())
}
@@ -127,7 +127,7 @@ impl Formatter for HtmlFormatter {
where
W: ?Sized + io::Write,
{
- let buff = format!("{value}");
+ let buff = format!(" {value} ");
writer.write_all(buff.as_bytes())
}
@@ -135,7 +135,7 @@ impl Formatter for HtmlFormatter {
where
W: ?Sized + io::Write,
{
- let buff = format!("{value}");
+ let buff = format!(" {value} ");
writer.write_all(buff.as_bytes())
}
@@ -143,7 +143,7 @@ impl Formatter for HtmlFormatter {
where
W: ?Sized + io::Write,
{
- let buff = format!("{value}");
+ let buff = format!(" {value} ");
writer.write_all(buff.as_bytes())
}
@@ -151,7 +151,7 @@ impl Formatter for HtmlFormatter {
where
W: ?Sized + io::Write,
{
- let buff = format!("{value}");
+ let buff = format!(" {value} ");
writer.write_all(buff.as_bytes())
}
@@ -159,7 +159,7 @@ impl Formatter for HtmlFormatter {
where
W: ?Sized + io::Write,
{
- let buff = format!("{value}");
+ let buff = format!(" {value} ");
writer.write_all(buff.as_bytes())
}
@@ -167,7 +167,7 @@ impl Formatter for HtmlFormatter {
where
W: ?Sized + io::Write,
{
- let buff = format!("{value}");
+ let buff = format!(" {value} ");
writer.write_all(buff.as_bytes())
}
@@ -192,7 +192,7 @@ impl Formatter for HtmlFormatter {
if first {
writer.write_all(b"")
} else {
- writer.write_all(b",")
+ writer.write_all(b",")
}
}
@@ -202,6 +202,13 @@ impl Formatter for HtmlFormatter {
{
writer.write_all(b"")
}
+
+ fn begin_object_value(&mut self, writer: &mut W) -> io::Result<()>
+ where
+ W: ?Sized + io::Write,
+ {
+ writer.write_all(b" : ")
+ }
}
pub fn sanatize(input: &str) -> String {
diff --git a/src/public/docs.rs b/src/public/docs.rs
index f4e26be..397e696 100644
--- a/src/public/docs.rs
+++ b/src/public/docs.rs
@@ -49,6 +49,7 @@ fn generate_body(body: Option<&'static str>) -> String {
"#
.to_string();
+
let body = body.trim();
if body.starts_with('{') {
return html.replace(
@@ -135,13 +136,16 @@ pub async fn init() {
users::USERS_SELF,
users::USERS_AVATAR,
users::USERS_BANNER,
+ users::USERS_FOLLOW,
+ users::USERS_FOLLOW_STATUS,
+ users::USERS_FRIENDS,
admin::ADMIN_AUTH,
admin::ADMIN_QUERY,
admin::ADMIN_POSTS,
admin::ADMIN_USERS,
admin::ADMIN_SESSIONS,
admin::ADMIN_COMMENTS,
- admin::ADMIN_LIKES
+ admin::ADMIN_LIKES,
];
let mut endpoints = ENDPOINTS.lock().await;
for doc in docs {
diff --git a/src/public/mod.rs b/src/public/mod.rs
index 76796ea..bb75ef0 100644
--- a/src/public/mod.rs
+++ b/src/public/mod.rs
@@ -25,7 +25,7 @@ pub mod pages;
pub fn router() -> Router {
let governor_conf = Box::new(
GovernorConfigBuilder::default()
- .burst_size(20)
+ .burst_size(30)
.per_second(1)
.key_extractor(SmartIpKeyExtractor)
.finish()
diff --git a/src/public/pages.rs b/src/public/pages.rs
index 6d5c0de..426727e 100644
--- a/src/public/pages.rs
+++ b/src/public/pages.rs
@@ -1,6 +1,7 @@
use axum::{
response::{IntoResponse, Redirect, Response},
- routing::get, Router
+ routing::get,
+ Router,
};
use crate::{
@@ -58,9 +59,8 @@ async fn wordpress(_: Log) -> Response {
}
async fn forgot(UserAgent(agent): UserAgent, _: Log) -> Response {
-
if agent.starts_with("curl") {
- return super::serve("/404.html").await
+ return super::serve("/404.html").await;
}
Redirect::to("https://www.youtube.com/watch?v=dQw4w9WgXcQ").into_response()
diff --git a/src/types/extract.rs b/src/types/extract.rs
index 6a01ad2..65d9f1a 100644
--- a/src/types/extract.rs
+++ b/src/types/extract.rs
@@ -7,7 +7,7 @@ use axum::{
async_trait,
body::HttpBody,
extract::{ConnectInfo, FromRequest, FromRequestParts},
- http::{request::Parts, Request, header::USER_AGENT},
+ http::{header::USER_AGENT, request::Parts, Request},
response::Response,
BoxError, RequestExt,
};
@@ -205,7 +205,7 @@ where
};
let Ok(value) = serde_json::from_str::(&body) else {
- return Err(ResponseCode::BadRequest.text("Invalid request body"))
+ return Err(ResponseCode::BadRequest.text("Body does not match paramaters"))
};
if let Err(msg) = value.check() {
@@ -256,7 +256,7 @@ where
return Err(ResponseCode::BadRequest.text("Bad Request"));
};
- Ok(UserAgent(agent.to_string()))
+ Ok(Self(agent.to_string()))
}
}
diff --git a/src/types/like.rs b/src/types/like.rs
index bf10b2d..1c113c1 100644
--- a/src/types/like.rs
+++ b/src/types/like.rs
@@ -7,13 +7,12 @@ use crate::types::http::{ResponseCode, Result};
#[derive(Serialize)]
pub struct Like {
pub user_id: u64,
- pub post_id: u64
+ pub post_id: u64,
}
impl Like {
#[instrument()]
pub fn add_liked(user_id: u64, post_id: u64) -> Result<()> {
-
let Ok(liked) = database::likes::add_liked(user_id, post_id) else {
return Err(ResponseCode::BadRequest.text("Failed to add like status"))
};
diff --git a/src/types/user.rs b/src/types/user.rs
index 835b675..245e9b7 100644
--- a/src/types/user.rs
+++ b/src/types/user.rs
@@ -19,6 +19,10 @@ pub struct User {
pub year: u32,
}
+pub const NO_RELATION: u8 = 0;
+pub const FOLLOWING: u8 = 1;
+pub const FOLLOWED: u8 = 2;
+
impl User {
#[instrument()]
pub fn from_user_id(user_id: u64, hide_password: bool) -> Result {
@@ -95,4 +99,42 @@ impl User {
Ok(user)
}
+
+ pub fn add_following(user_id_1: u64, user_id_2: u64) -> Result<()> {
+ let Ok(followed) = database::friends::set_following(user_id_1, user_id_2) else {
+ return Err(ResponseCode::BadRequest.text("Failed to add follow status"))
+ };
+
+ if !followed {
+ return Err(ResponseCode::InternalServerError.text("Failed to add follow status"));
+ }
+
+ Ok(())
+ }
+
+ pub fn remove_following(user_id_1: u64, user_id_2: u64) -> Result<()> {
+ let Ok(followed) = database::friends::remove_following(user_id_1, user_id_2) else {
+ return Err(ResponseCode::BadRequest.text("Failed to remove follow status"))
+ };
+
+ if !followed {
+ return Err(ResponseCode::InternalServerError.text("Failed to remove follow status"));
+ }
+
+ Ok(())
+ }
+
+ pub fn get_following(user_id_1: u64, user_id_2: u64) -> Result {
+ let Ok(followed) = database::friends::get_friend_status(user_id_1, user_id_2) else {
+ return Err(ResponseCode::InternalServerError.text("Failed to get follow status"))
+ };
+ Ok(followed)
+ }
+
+ pub fn get_friends(user_id: u64) -> Result> {
+ let Ok(users) = database::friends::get_friends(user_id) else {
+ return Err(ResponseCode::InternalServerError.text("Failed to fetch friends"))
+ };
+ Ok(users)
+ }
}