diff options
author | Tyler Murphy <tylermurphy534@gmail.com> | 2023-02-03 15:03:16 -0500 |
---|---|---|
committer | Tyler Murphy <tylermurphy534@gmail.com> | 2023-02-03 15:03:16 -0500 |
commit | d85dd163e34ebdf963e1299b4ad3387ea713797f (patch) | |
tree | 9f8dfedb0f48902b4fa5a2be7d57145ecf816333 | |
parent | favicon fix / readme (diff) | |
download | xssbook-d85dd163e34ebdf963e1299b4ad3387ea713797f.tar.gz xssbook-d85dd163e34ebdf963e1299b4ad3387ea713797f.tar.bz2 xssbook-d85dd163e34ebdf963e1299b4ad3387ea713797f.zip |
docs is ssr'd
-rw-r--r-- | public/api.html | 542 | ||||
-rw-r--r-- | src/api/admin.rs | 75 | ||||
-rw-r--r-- | src/api/auth.rs | 57 | ||||
-rw-r--r-- | src/api/mod.rs | 8 | ||||
-rw-r--r-- | src/api/posts.rs | 97 | ||||
-rw-r--r-- | src/api/users.rs | 82 | ||||
-rw-r--r-- | src/database/posts.rs | 3 | ||||
-rw-r--r-- | src/main.rs | 4 | ||||
-rw-r--r-- | src/public/docs.rs | 181 | ||||
-rw-r--r-- | src/public/mod.rs | 3 | ||||
-rw-r--r-- | src/public/pages.rs | 4 |
11 files changed, 500 insertions, 556 deletions
diff --git a/public/api.html b/public/api.html deleted file mode 100644 index 6e6086a..0000000 --- a/public/api.html +++ /dev/null @@ -1,542 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="UTF-8"> - <link rel="stylesheet" href="/css/main.css"> - <link rel="stylesheet" href="/css/header.css"> - <link rel="stylesheet" href="/css/console.css"> - <link rel="stylesheet" href="/css/api.css"> - <title>XSSBook - API Documentation</title> -</head> -<body> - <div id="header"> - <span class="logo"><a href="/">xssbook</a></span> - <span class="gtext desc" style="margin-left: 6em; font-size: 2em; color: #606770">API Documentation</span> - </div> - <div id="docs"> - <div> - <div class="endpoint"> - <span class="method post">POST</span> - <span class="uri">/api/auth/register</span> - <span class="desc">Registeres a new account</span> - </div> - <div class="info"> - <h2>Body</h2> - <div class="body"> - <span>{</span><br> - <span class="key">"firstname"</span> : <span class="string">"[Object"</span><br> - <span class="key">"lastname"</span> : <span class="string">"object]"</span><br> - <span class="key">"email"</span> : <span class="string">"object@object.object"</span><br> - <span class="key">"password"</span> : <span class="string">"i love js"</span><br> - <span class="key">"gender"</span> : <span class="string">"lettuce"</span><br> - <span class="key">"day"</span> : <span class="number">1</span><br> - <span class="key">"month"</span> : <span class="number">1</span><br> - <span class="key">"year"</span> : <span class="number">1970</span><br> - <span>}</span><br> - </div> - <h2>Responses</h2> - <div> - <span class="ptype">201</span> - <span class="pdesc">Successfully created new user, auth cookie is returned</span> - </div> - <div> - <span class="ptype">400</span> - <span class="pdesc">Body does not match paramaters</span> - </div> - </div> - </div> - <div> - <div class="endpoint"> - <span class="method post">POST</span> - <span class="uri">/api/auth/login</span> - <span class="desc">Logs into an existing account</span> - </div> - <div class="info"> - <h2>Body</h2> - <div class="body"> - <span>{</span><br> - <span class="key">"email"</span> : <span class="string">"object@object.object"</span><br> - <span class="key">"password"</span> : <span class="string">"i love js"</span><br> - <span>}</span><br> - </div> - <h2>Responses</h2> - <div> - <span class="ptype">200</span> - <span class="pdesc">Successfully logged in, auth cookie is returned</span> - </div> - <div> - <span class="ptype">400</span> - <span class="pdesc">Body does not match paramaters, or email/password is already in use</span> - </div> - </div> - </div> - <div> - <div class="endpoint"> - <span class="method post">POST</span> - <span class="uri">/api/auth/logout</span> - <span class="desc">Logs out of an logged in account</span> - <span class="auth"><span>auth</span> cookie is required for authentication</span> - </div> - <div class="info"> - <h2>Responses</h2> - <div> - <span class="ptype">200</span> - <span class="pdesc">Successfully logged out</span> - </div> - <div> - <span class="ptype">401</span> - <span class="pdesc">Unauthorized</span> - </div> - <div> - <span class="ptype">500</span> - <span class="pdesc">Failed to log out user</span> - </div> - </div> - </div> - <div> - <div class="endpoint"> - <span class="method post">POST</span> - <span class="uri">/api/posts/create</span> - <span class="desc">Creates a new post</span> - <span class="auth"><span>auth</span> cookie is required for authentication</span> - </div> - <div class="info"> - <h2>Body</h2> - <div class="body"> - <span>{</span><br> - <span class="key">"content"</span> : <span class="string">"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."</span><br> - <span>}</span><br> - </div> - <h2>Responses</h2> - <div> - <span class="ptype">201</span> - <span class="pdesc">Successfully created post</span> - </div> - <div> - <span class="ptype">400</span> - <span class="pdesc">Body does not match paramaters</span> - </div> - <div> - <span class="ptype">401</span> - <span class="pdesc">Unauthorized</span> - </div> - <div> - <span class="ptype">500</span> - <span class="pdesc">Failed to create post</span> - </div> - </div> - </div> - <div> - <div class="endpoint"> - <span class="method post">POST</span> - <span class="uri">/api/posts/page</span> - <span class="desc">Load a section of posts from newest to oldest</span> - <span class="auth"><span>auth</span> cookie is required for authentication</span> - </div> - <div class="info"> - <h2>Body</h2> - <div class="body"> - <span>{</span><br> - <span class="key">"page"</span> : <span class="number">0</span><br> - <span>}</span><br> - </div> - <h2>Responses</h2> - <div> - <span class="ptype">200</span> - <span class="pdesc">Returns posts in <span>application/json</span></span> - </div> - <div> - <span class="ptype">400</span> - <span class="pdesc">Body does not match paramaters</span> - </div> - <div> - <span class="ptype">401</span> - <span class="pdesc">Unauthorized</span> - </div> - <div> - <span class="ptype">500</span> - <span class="pdesc">Failed to fetch posts</span> - </div> - </div> - </div> - <div> - <div class="endpoint"> - <span class="method post">POST</span> - <span class="uri">/api/posts/user</span> - <span class="desc">Load a section of posts from newest to oldest from a specific user</span> - <span class="auth"><span>auth</span> cookie is required for authentication</span> - </div> - <div class="info"> - <h2>Body</h2> - <div class="body"> - <span>{</span><br> - <span class="key">"user_id"</span> : <span class="number">3</span><br> - <span class="key">"page"</span> : <span class="number">0</span><br> - <span>}</span><br> - </div> - <h2>Responses</h2> - <div> - <span class="ptype">200</span> - <span class="pdesc">Returns posts in <span>application/json</span></span> - </div> - <div> - <span class="ptype">400</span> - <span class="pdesc">Body does not match paramaters</span> - </div> - <div> - <span class="ptype">401</span> - <span class="pdesc">Unauthorized</span> - </div> - <div> - <span class="ptype">500</span> - <span class="pdesc">Failed to fetch posts</span> - </div> - </div> - </div> - <div> - <div class="endpoint"> - <span class="method patch">PATCH</span> - <span class="uri">/api/posts/comment</span> - <span class="desc">Adds a comment to a post</span> - <span class="auth"><span>auth</span> cookie is required for authentication</span> - </div> - <div class="info"> - <h2>Body</h2> - <div class="body"> - <span>{</span><br> - <span class="key">"content"</span> : <span class="string">"This is a very good post"</span><br> - <span class="key">"post_id"</span> : <span class="number">0</span><br> - <span>}</span><br> - </div> - <h2>Responses</h2> - <div> - <span class="ptype">200</span> - <span class="pdesc">Successfully added comment</span> - </div> - <div> - <span class="ptype">400</span> - <span class="pdesc">Body does not match paramaters</span> - </div> - <div> - <span class="ptype">401</span> - <span class="pdesc">Unauthorized</span> - </div> - <div> - <span class="ptype">500</span> - <span class="pdesc">Failed to add comment</span> - </div> - </div> - </div> - <div> - <div class="endpoint"> - <span class="method patch">PATCH</span> - <span class="uri">/api/posts/like</span> - <span class="desc">Set like status on a post</span> - <span class="auth"><span>auth</span> cookie is required for authentication</span> - </div> - <div class="info"> - <h2>Body</h2> - <div class="body"> - <span>{</span><br> - <span class="key">"state"</span> : <span class="bool">true</span><br> - <span class="key">"post_id"</span> : <span class="number">0</span><br> - <span>}</span><br> - </div> - <h2>Responses</h2> - <div> - <span class="ptype">200</span> - <span class="pdesc">Successfully set like status</span> - </div> - <div> - <span class="ptype">400</span> - <span class="pdesc">Body does not match paramaters</span> - </div> - <div> - <span class="ptype">401</span> - <span class="pdesc">Unauthorized</span> - </div> - <div> - <span class="ptype">500</span> - <span class="pdesc">Failed to set like status</span> - </div> - </div> - </div> - <div> - <div class="endpoint"> - <span class="method post">POST</span> - <span class="uri">/api/users/load</span> - <span class="desc">Load a requested set of users</span> - <span class="auth"><span>auth</span> cookie is required for authentication</span> - </div> - <div class="info"> - <h2>Body</h2> - <div class="body"> - <span>{</span><br> - <span class="key">"ids"</span> : [<span class="number">0</span>,<span class="number">3</span>,<span class="number">7</span>]<br> - <span>}</span><br> - </div> - <h2>Responses</h2> - <div> - <span class="ptype">200</span> - <span class="pdesc">Returns users in <span>application/json</span></span> - </div> - <div> - <span class="ptype">400</span> - <span class="pdesc">Body does not match paramaters</span> - </div> - <div> - <span class="ptype">401</span> - <span class="pdesc">Unauthorized</span> - </div> - <div> - <span class="ptype">500</span> - <span class="pdesc">Failed to fetch users</span> - </div> - </div> - </div> - <div> - <div class="endpoint"> - <span class="method post">POST</span> - <span class="uri">/api/users/page</span> - <span class="desc">Load a section of users from newest to oldest</span> - <span class="auth"><span>auth</span> cookie is required for authentication</span> - </div> - <div class="info"> - <h2>Body</h2> - <div class="body"> - <span>{</span><br> - <span class="key">"page"</span> : <span class="number">0</span><br> - <span>}</span><br> - </div> - <h2>Responses</h2> - <div> - <span class="ptype">200</span> - <span class="pdesc">Returns users in <span>application/json</span></span> - </div> - <div> - <span class="ptype">400</span> - <span class="pdesc">Body does not match paramaters</span> - </div> - <div> - <span class="ptype">401</span> - <span class="pdesc">Unauthorized</span> - </div> - <div> - <span class="ptype">500</span> - <span class="pdesc">Failed to fetch users</span> - </div> - </div> - </div> - <div> - <div class="endpoint"> - <span class="method post">POST</span> - <span class="uri">/api/users/self</span> - <span class="desc">Returns current authenticated user (whoami)</span> - <span class="auth"><span>auth</span> cookie is required for authentication</span> - </div> - <div class="info"> - <h2>Responses</h2> - <div> - <span class="ptype">200</span> - <span class="pdesc">Returns authed user in <span>application/json</span></span> - </div> - <div> - <span class="ptype">401</span> - <span class="pdesc">Unauthorized</span> - </div> - <div> - <span class="ptype">500</span> - <span class="pdesc">Failed to fetch user</span> - </div> - </div> - </div> - <div> - <div class="endpoint"> - <span class="method put">PUT</span> - <span class="uri">/api/users/avatar</span> - <span class="desc">Set your current profile avatar</span> - <span class="auth"><span>auth</span> cookie is required for authentication</span> - </div> - <div class="info"> - <h2>Body</h2> - <div class="body"> - PNG sent as a binary blob - </div> - <h2>Responses</h2> - <div> - <span class="ptype">200</span> - <span class="pdesc">Successfully updated avatar</span> - </div> - <div> - <span class="ptype">400</span> - <span class="pdesc">Invalid PNG or disallowed size</span> - </div> - <div> - <span class="ptype">401</span> - <span class="pdesc">Unauthorized</span> - </div> - <div> - <span class="ptype">500</span> - <span class="pdesc">Failed to update avatar</span> - </div> - </div> - </div> - <div> - <div class="endpoint"> - <span class="method put">PUT</span> - <span class="uri">/api/users/banner</span> - <span class="desc">Set your current profile banner</span> - <span class="auth"><span>auth</span> cookie is required for authentication</span> - </div> - <div class="info"> - <h2>Body</h2> - <div class="body"> - PNG sent as a binary blob - </div> - <h2>Responses</h2> - <div> - <span class="ptype">200</span> - <span class="pdesc">Successfully updated banner</span> - </div> - <div> - <span class="ptype">400</span> - <span class="pdesc">Invalid PNG or disallowed size</span> - </div> - <div> - <span class="ptype">401</span> - <span class="pdesc">Unauthorized</span> - </div> - <div> - <span class="ptype">500</span> - <span class="pdesc">Failed to update banner</span> - </div> - </div> - </div> - <div> - <div class="endpoint"> - <span class="method post">POST</span> - <span class="uri">/api/admin/auth</span> - <span class="desc">Authenticates on the admin panel</span> - </div> - <div class="info"> - <h2>Body</h2> - <div class="body"> - <span>{</span><br> - <span class="key">"secret"</span> : <span class="string">"admin"</span><br> - <span>}</span><br> - </div> - <h2>Responses</h2> - <div> - <span class="ptype">200</span> - <span class="pdesc">Successfully authed, admin cookie returned</span> - </div> - <div> - <span class="ptype">400</span> - <span class="pdesc">Body does match parameters, or invalid admin scret</span> - </div> - </div> - </div> - <div> - <div class="endpoint"> - <span class="method post">POST</span> - <span class="uri">/api/admin/query</span> - <span class="desc">Run a SQL query on the database</span> - <span class="auth"><span>admin</span> cookie is required for authentication</span> - </div> - <div class="info"> - <h2>Body</h2> - <div class="body"> - <span>{</span><br> - <span class="key">"query"</span> : <span class="string">"DROP TABLE users;"</span><br> - <span>}</span><br> - </div> - <h2>Responses</h2> - <div> - <span class="ptype">200</span> - <span class="pdesc">Successfully ran SQL query</span> - </div> - <div> - <span class="ptype">400</span> - <span class="pdesc">Body does match parameters</span> - </div> - <div> - <span class="ptype">401</span> - <span class="pdesc">Unauthorized</span> - </div> - <div> - <span class="ptype">500</span> - <span class="pdesc">SQL query error</span> - </div> - </div> - </div> - <div> - <div class="endpoint"> - <span class="method post">POST</span> - <span class="uri">/api/admin/posts</span> - <span class="desc">Returns the entire posts table</span> - <span class="auth"><span>admin</span> cookie is required for authentication</span> - </div> - <div class="info"> - <h2>Responses</h2> - <div> - <span class="ptype">200</span> - <span class="pdesc">Returns sql table in <span>text/html</span></span> - </div> - <div> - <span class="ptype">401</span> - <span class="pdesc">Unauthorized</span> - </div> - <div> - <span class="ptype">500</span> - <span class="pdesc">Failed to fetch data</span> - </div> - </div> - </div> - <div> - <div class="endpoint"> - <span class="method post">POST</span> - <span class="uri">/api/admin/users</span> - <span class="desc">Returns the entire users table</span> - <span class="auth"><span>admin</span> cookie is required for authentication</span> - </div> - <div class="info"> - <h2>Responses</h2> - <div> - <span class="ptype">200</span> - <span class="pdesc">Returns sql table in <span>text/html</span></span> - </div> - <div> - <span class="ptype">401</span> - <span class="pdesc">Unauthorized</span> - </div> - <div> - <span class="ptype">500</span> - <span class="pdesc">Failed to fetch data</span> - </div> - </div> - </div> - <div> - <div class="endpoint"> - <span class="method post">POST</span> - <span class="uri">/api/admin/sessions</span> - <span class="desc">Returns the entire posts sessions</span> - <span class="auth"><span>admin</span> cookie is required for authentication</span> - </div> - <div class="info"> - <h2>Responses</h2> - <div> - <span class="ptype">200</span> - <span class="pdesc">Returns sql table in <span>text/html</span></span> - </div> - <div> - <span class="ptype">401</span> - <span class="pdesc">Unauthorized</span> - </div> - <div> - <span class="ptype">500</span> - <span class="pdesc">Failed to fetch data</span> - </div> - </div> - </div> - </div> -</body>
\ No newline at end of file diff --git a/src/api/admin.rs b/src/api/admin.rs index a23d20f..8db3032 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -6,13 +6,29 @@ use tower_cookies::{Cookie, Cookies}; use crate::{ database, - public::admin, + public::{admin, docs::{EndpointDocumentation, EndpointMethod}}, types::{ extract::{AdminUser, Check, CheckResult, Json}, http::ResponseCode, }, }; +pub const ADMIN_AUTH: EndpointDocumentation = EndpointDocumentation { + uri: "/api/admin/auth", + method: EndpointMethod::Post, + description: "Authenticates on the admin panel", + body: Some(r#" + { + "secret" : "admin" + } + "#), + responses: &[ + (200, "Successfully executed SQL query"), + (400, " Successfully authed, admin cookie returned") + ], + cookie: None, +}; + #[derive(Deserialize)] struct AdminAuthRequest { secret: String, @@ -40,6 +56,24 @@ async fn auth(cookies: Cookies, Json(body): Json<AdminAuthRequest>) -> Response ResponseCode::Success.text("Successfully logged in") } +pub const ADMIN_QUERY: EndpointDocumentation = EndpointDocumentation { + uri: "/api/admin/query", + method: EndpointMethod::Post, + description: "Run a SQL query on the database", + body: Some(r#" + { + "query" : "DROP TABLE users;" + } + "#), + responses: &[ + (200, "Successfully executed SQL query"), + (400, "Body does not match parameters"), + (401, "Unauthorized"), + (500, "SQL query ran into an error") + ], + cookie: Some("admin"), +}; + #[derive(Deserialize)] struct QueryRequest { query: String, @@ -60,14 +94,53 @@ async fn query(_: AdminUser, Json(body): Json<QueryRequest>) -> Response { } } +pub const ADMIN_POSTS: EndpointDocumentation = EndpointDocumentation { + uri: "/api/admin/posts", + method: EndpointMethod::Post, + description: "Returns the entire posts table", + body: None, + responses: &[ + (200, "Returns sql table in <span>text/html</span>"), + (401, "Unauthorized"), + (500, "Failed to fetch data") + ], + cookie: Some("admin"), +}; + async fn posts(_: AdminUser) -> Response { admin::generate_posts() } +pub const ADMIN_USERS: EndpointDocumentation = EndpointDocumentation { + uri: "/api/admin/users", + method: EndpointMethod::Post, + description: "Returns the entire users table", + body: None, + responses: &[ + (200, "Returns sql table in <span>text/html</span>"), + (401, "Unauthorized"), + (500, "Failed to fetch data") + ], + cookie: Some("admin"), +}; + async fn users(_: AdminUser) -> Response { admin::generate_users() } +pub const ADMIN_SESSIONS: EndpointDocumentation = EndpointDocumentation { + uri: "/api/admin/sessions", + method: EndpointMethod::Post, + description: "Returns the entire sessions table", + body: None, + responses: &[ + (200, "Returns sql table in <span>text/html</span>"), + (401, "Unauthorized"), + (500, "Failed to fetch data") + ], + cookie: Some("admin"), +}; + async fn sessions(_: AdminUser) -> Response { admin::generate_sessions() } diff --git a/src/api/auth.rs b/src/api/auth.rs index 7f7cf9e..0ff180e 100644 --- a/src/api/auth.rs +++ b/src/api/auth.rs @@ -3,11 +3,34 @@ use serde::Deserialize; use time::{Duration, OffsetDateTime}; use tower_cookies::{Cookie, Cookies}; -use crate::types::{ +use crate::{types::{ extract::{AuthorizedUser, Check, CheckResult, Json, Log}, http::ResponseCode, session::Session, user::User, +}, public::docs::{EndpointDocumentation, EndpointMethod}}; + +pub const AUTH_REGISTER: EndpointDocumentation = EndpointDocumentation { + uri: "/api/auth/register", + method: EndpointMethod::Post, + description: "Registeres a new account", + body: Some(r#" + { + "firstname": "[Object]", + "lastname": "object]", + "email": "object@object.object", + "password": "i love js", + "gender": "object", + "day": 1, + "month": 1, + "year": 1970 + } + "#), + responses: &[ + (201, "Successfully registered new user"), + (400, "Body does not match parameters"), + ], + cookie: None, }; #[derive(Deserialize, Debug)] @@ -93,9 +116,26 @@ async fn register(cookies: Cookies, Json(body): Json<RegistrationRequet>) -> Res cookies.add(cookie); - ResponseCode::Created.text("Successfully created new user") + ResponseCode::Created.text("Successfully created new user, auth cookie is returned") } +pub const AUTH_LOGIN: EndpointDocumentation = EndpointDocumentation { + uri: "/api/auth/login", + method: EndpointMethod::Post, + description: "Logs into an existing account", + body: Some(r#" + { + "email": "object@object.object", + "password": "i love js" + } + "#), + responses: &[ + (200, "Successfully logged in, auth cookie is returned"), + (400, "Body does not match parameters, or invalid email password combination"), + ], + cookie: None, +}; + #[derive(Deserialize)] struct LoginRequest { email: String, @@ -136,6 +176,19 @@ async fn login(cookies: Cookies, Json(body): Json<LoginRequest>) -> Response { ResponseCode::Success.text("Successfully logged in") } +pub const AUTH_LOGOUT: EndpointDocumentation = EndpointDocumentation { + uri: "/api/auth/logout", + method: EndpointMethod::Post, + description: "Logs out of a logged in account", + body: None, + responses: &[ + (200, "Successfully logged out"), + (401, "Unauthorized"), + (500, "Failed to log out user") + ], + cookie: None, +}; + async fn logout(cookies: Cookies, AuthorizedUser(user): AuthorizedUser, _: Log) -> Response { cookies.remove(Cookie::new("auth", "")); diff --git a/src/api/mod.rs b/src/api/mod.rs index 9efcefc..12563e3 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -6,10 +6,10 @@ use tower_governor::{ GovernorLayer, }; -mod admin; -mod auth; -mod posts; -mod users; +pub mod admin; +pub mod auth; +pub mod posts; +pub mod users; pub use auth::RegistrationRequet; diff --git a/src/api/posts.rs b/src/api/posts.rs index 6aa074f..f1cdab3 100644 --- a/src/api/posts.rs +++ b/src/api/posts.rs @@ -5,10 +5,28 @@ use axum::{ }; use serde::Deserialize; -use crate::types::{ +use crate::{types::{ extract::{AuthorizedUser, Check, CheckResult, Json}, http::ResponseCode, post::Post, +}, public::docs::{EndpointDocumentation, EndpointMethod}}; + +pub const POSTS_CREATE: EndpointDocumentation = EndpointDocumentation { + uri: "/api/posts/create", + method: EndpointMethod::Post, + description: "Creates a new post", + body: Some(r#" + { + "content" : "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." + } + "#), + responses: &[ + (201, "Successfully created post"), + (400, "Body does not match parameters"), + (401, "Unauthorized"), + (500, "Failed to create post") + ], + cookie: Some("auth"), }; #[derive(Deserialize)] @@ -43,6 +61,24 @@ async fn create( ResponseCode::Created.json(&json) } +pub const POSTS_PAGE: EndpointDocumentation = EndpointDocumentation { + uri: "/api/posts/page", + method: EndpointMethod::Post, + description: "Load a section of posts from newest to oldest", + body: Some(r#" + { + "page": 0 + } + "#), + responses: &[ + (200, "Returns posts in <span>application/json<span>"), + (400, "Body does not match parameters"), + (401, "Unauthorized"), + (500, "Failed to fetch posts") + ], + cookie: Some("auth"), +}; + #[derive(Deserialize)] struct PostPageRequest { page: u64, @@ -69,10 +105,29 @@ async fn page( ResponseCode::Success.json(&json) } +pub const POSTS_USER: EndpointDocumentation = EndpointDocumentation { + uri: "/api/posts/user", + method: EndpointMethod::Post, + description: "Load a section of posts from newest to oldest from a specific user", + body: Some(r#" + { + "user_id": 3, + "page": 0 + } + "#), + responses: &[ + (200, "Returns posts in <span>application/json<span>"), + (400, "Body does not match parameters"), + (401, "Unauthorized"), + (500, "Failed to fetch posts") + ], + cookie: Some("auth"), +}; + #[derive(Deserialize)] struct UsersPostsRequest { user_id: u64, - page: u64 + page: u64, } impl Check for UsersPostsRequest { @@ -96,6 +151,25 @@ async fn user( ResponseCode::Success.json(&json) } +pub const POSTS_COMMENT: EndpointDocumentation = EndpointDocumentation { + uri: "/api/posts/comment", + method: EndpointMethod::Patch, + description: "Add a comment to a post", + body: Some(r#" + { + "content": "This is a very cool comment", + "post_id": 0 + } + "#), + responses: &[ + (200, "Successfully added comment"), + (400, "Body does not match parameters"), + (401, "Unauthorized"), + (500, "Failed to add comment") + ], + cookie: Some("auth"), +}; + #[derive(Deserialize)] struct PostCommentRequest { content: String, @@ -129,6 +203,25 @@ async fn comment( ResponseCode::Success.text("Successfully commented on post") } +pub const POSTS_LIKE: EndpointDocumentation = EndpointDocumentation { + uri: "/api/posts/like", + method: EndpointMethod::Patch, + description: "Set like status on a post", + body: Some(r#" + { + "post_id" : 0, + "status" : true + } + "#), + responses: &[ + (200, "Successfully set like status"), + (400, "Body does not match parameters"), + (401, "Unauthorized"), + (500, "Failed to set like status") + ], + cookie: Some("auth"), +}; + #[derive(Deserialize)] struct PostLikeRequest { state: bool, diff --git a/src/api/users.rs b/src/api/users.rs index e3c992b..7d1f006 100644 --- a/src/api/users.rs +++ b/src/api/users.rs @@ -1,8 +1,8 @@ -use crate::types::{ +use crate::{types::{ extract::{AuthorizedUser, Check, CheckResult, Json, Png}, http::ResponseCode, user::User, -}; +}, public::docs::{EndpointDocumentation, EndpointMethod}}; use axum::{ response::Response, routing::{post, put}, @@ -10,6 +10,24 @@ use axum::{ }; use serde::Deserialize; +pub const USERS_LOAD: EndpointDocumentation = EndpointDocumentation { + uri: "/api/users/load", + method: EndpointMethod::Post, + description: "Loads a requested set of users", + body: Some(r#" + { + "ids": [0, 3, 7] + } + "#), + responses: &[ + (200, "Returns users in <span>application/json</span>"), + (400, "Body does not match parameters"), + (401, "Unauthorized"), + (500, "Failed to fetch users") + ], + cookie: Some("auth"), +}; + #[derive(Deserialize)] struct UserLoadRequest { ids: Vec<u64>, @@ -33,6 +51,25 @@ async fn load_batch( ResponseCode::Success.json(&json) } +pub const USERS_PAGE: EndpointDocumentation = EndpointDocumentation { + uri: "/api/users/page", + method: EndpointMethod::Post, + description: "Load a section of users from newest to oldest", + body: Some(r#" + { + "user_id": 3, + "page": 0 + } + "#), + responses: &[ + (200, "Returns users in <span>application/json</span>"), + (400, "Body does not match parameters"), + (401, "Unauthorized"), + (500, "Failed to fetch users") + ], + cookie: Some("auth"), +}; + #[derive(Deserialize)] struct UserPageReqiest { page: u64, @@ -59,6 +96,19 @@ async fn load_page( ResponseCode::Success.json(&json) } +pub const USERS_SELF: EndpointDocumentation = EndpointDocumentation { + uri: "/api/users/self", + method: EndpointMethod::Post, + description: "Returns current authenticated user (whoami)", + body: None, + responses: &[ + (200, "Successfully executed SQL query"), + (401, "Unauthorized"), + (500, "Failed to fetch user") + ], + cookie: Some("auth"), +}; + async fn load_self(AuthorizedUser(user): AuthorizedUser) -> Response { let Ok(json) = serde_json::to_string(&user) else { return ResponseCode::InternalServerError.text("Failed to fetch user") @@ -67,6 +117,20 @@ async fn load_self(AuthorizedUser(user): AuthorizedUser) -> Response { ResponseCode::Success.json(&json) } +pub const USERS_AVATAR: EndpointDocumentation = EndpointDocumentation { + uri: "/api/users/avatar", + method: EndpointMethod::Put, + description: "Set your current profile avatar", + body: Some("PNG sent as a binary blob"), + responses: &[ + (200, "Successfully updated avatar"), + (400, "Invalid PNG or disallowed size"), + (401, "Unauthorized"), + (500, "Failed to update avatar") + ], + cookie: Some("auth"), +}; + async fn avatar(AuthorizedUser(user): AuthorizedUser, Png(img): Png) -> Response { let path = format!("./public/image/custom/avatar/{}.png", user.user_id); @@ -77,6 +141,20 @@ async fn avatar(AuthorizedUser(user): AuthorizedUser, Png(img): Png) -> Response ResponseCode::Success.text("Successfully updated avatar") } +pub const USERS_BANNER: EndpointDocumentation = EndpointDocumentation { + uri: "/api/users/banner", + method: EndpointMethod::Put, + description: "Set your current profile banner", + body: Some("PNG sent as a binary blob"), + responses: &[ + (200, "Successfully updated banner"), + (400, "Invalid PNG or disallowed size"), + (401, "Unauthorized"), + (500, "Failed to update banner") + ], + cookie: Some("auth"), +}; + async fn banner(AuthorizedUser(user): AuthorizedUser, Png(img): Png) -> Response { let path = format!("./public/image/custom/banner/{}.png", user.user_id); diff --git a/src/database/posts.rs b/src/database/posts.rs index a81d6a7..8ca9b2d 100644 --- a/src/database/posts.rs +++ b/src/database/posts.rs @@ -94,7 +94,8 @@ pub fn get_users_post_page(user_id: u64, page: u64) -> Result<Vec<Post>, rusqlit tracing::trace!("Retrieving users posts"); let page_size = 10; let conn = database::connect()?; - let mut stmt = conn.prepare("SELECT * FROM posts WHERE user_id = ? ORDER BY post_id DESC LIMIT ? OFFSET ?")?; + let mut stmt = conn + .prepare("SELECT * FROM posts WHERE user_id = ? ORDER BY post_id DESC LIMIT ? OFFSET ?")?; let row = stmt.query_map([user_id, page_size, page_size * page], |row| { let row = post_from_row(row)?; Ok(row) diff --git a/src/main.rs b/src/main.rs index 8a8e9d8..ace9968 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,8 @@ use tracing_subscriber::{ }; use types::extract::RequestIp; +use crate::public::docs; + mod api; mod database; mod public; @@ -55,6 +57,8 @@ async fn main() { exit(1) }; + docs::init().await; + fs::create_dir_all("./public/image/custom").expect("Coudn't make custom data directory"); fs::create_dir_all("./public/image/custom/avatar") .expect("Coudn't make custom avatar directory"); diff --git a/src/public/docs.rs b/src/public/docs.rs new file mode 100644 index 0000000..1f1448b --- /dev/null +++ b/src/public/docs.rs @@ -0,0 +1,181 @@ +use axum::response::Response; +use lazy_static::lazy_static; +use tokio::sync::Mutex; + +use crate::{api::{admin, users, posts, auth}, types::http::ResponseCode}; + +use super::console::beautify; + +pub enum EndpointMethod { + Post, + Put, + Patch, +} + +impl ToString for EndpointMethod { + fn to_string(&self) -> String { + match self { + Self::Post => "POST".to_owned(), + Self::Put => "PUT".to_owned(), + Self::Patch => "PATCH".to_owned(), + } + } +} + +pub struct EndpointDocumentation<'a> { + pub uri: &'static str, + pub method: EndpointMethod, + pub description: &'static str, + pub body: Option<&'static str>, + pub responses: &'a [(u16, &'static str)], + pub cookie: Option<&'static str>, +} + +lazy_static! { + static ref ENDPOINTS: Mutex<Vec<String>> = Mutex::new(Vec::new()); +} + +fn generate_body(body: Option<&'static str>) -> String { + let Some(body) = body else { + return String::new() + }; + let html = r#" + <h2>Body</h2> + <div class="body"> + $body + </div> + "# + .to_string(); + let body = body.trim(); + if body.starts_with('{') { + return html.replace( + "$body", + &beautify(body) + .replace("<span class='key'", "<br><span class='key'") + .replace('}', "<br>}"), + ); + } + html.replace("$body", body) +} + +fn generate_responses(responses: &[(u16, &'static str)]) -> String { + let mut html = r#" + <h2>Responses</h2> + $responses + "# + .to_string(); + + for response in responses { + let res = format!( + r#" + <div> + <span class="ptype">{}</span> + <span class="pdesc">{}</span> + </div> + $responses + "#, + response.0, response.1 + ); + html = html.replace("$responses", &res); + } + + html.replace("$responses", "") +} + +fn generate_cookie(cookie: Option<&'static str>) -> String { + let Some(cookie) = cookie else { + return String::new() + }; + format!( + r#"<span class="auth"><span>{cookie}</span> cookie is required for authentication</span>"# + ) +} + +fn generate_endpoint(doc: &EndpointDocumentation) -> String { + let html = r#" + <div> + <div class="endpoint"> + <span class="method $method_class">$method</span> + <span class="uri">$uri</span> + <span class="desc">$description</span> + $cookie + </div> + <div class="info"> + $body + $responses + </div> + </div> + "#; + + html.replace("$method_class", &doc.method.to_string().to_lowercase()) + .replace("$method", &doc.method.to_string()) + .replace("$uri", doc.uri) + .replace("$description", doc.description) + .replace("$cookie", &generate_cookie(doc.cookie)) + .replace("$body", &generate_body(doc.body)) + .replace("$responses", &generate_responses(doc.responses)) +} + +pub async fn init() { + let docs = vec![ + auth::AUTH_REGISTER, + auth::AUTH_LOGIN, + auth::AUTH_LOGOUT, + posts::POSTS_CREATE, + posts::POSTS_PAGE, + posts::POSTS_USER, + posts::POSTS_COMMENT, + posts::POSTS_LIKE, + users::USERS_LOAD, + users::USERS_PAGE, + users::USERS_SELF, + users::USERS_AVATAR, + users::USERS_BANNER, + admin::ADMIN_AUTH, + admin::ADMIN_QUERY, + admin::ADMIN_POSTS, + admin::ADMIN_USERS, + admin::ADMIN_SESSIONS, + ]; + let mut endpoints = ENDPOINTS.lock().await; + for doc in docs { + endpoints.push(generate_endpoint(&doc)); + } +} + +pub async fn generate() -> Response { + let mut data = String::new(); + { + let endpoints = ENDPOINTS.lock().await; + endpoints.iter().for_each(|endpoint| { + data.push_str(endpoint); + }); + } + + let html = format!( + r#" + <!DOCTYPE html> + <html lang="en"> + <head> + <meta charset="UTF-8"> + <link rel="stylesheet" href="/css/main.css"> + <link rel="stylesheet" href="/css/header.css"> + <link rel="stylesheet" href="/css/console.css"> + <link rel="stylesheet" href="/css/api.css"> + <title>XSSBook - API Documentation</title> + </head> + <body> + <div id="header"> + <span class="logo"><a href="/">xssbook</a></span> + <span class="gtext desc" style="margin-left: 6em; font-size: 2em; color: #606770">API Documentation</span> + </div> + <div id="docs"> + {data} + </div> + </body> + </html> + "# + ); + + ResponseCode::Success.html(&html) +} diff --git a/src/public/mod.rs b/src/public/mod.rs index ebd5fc5..82c994d 100644 --- a/src/public/mod.rs +++ b/src/public/mod.rs @@ -18,6 +18,7 @@ use crate::types::http::ResponseCode; pub mod admin; pub mod console; +pub mod docs; pub mod file; pub mod pages; @@ -33,7 +34,7 @@ pub fn router() -> Router { Router::new() .nest("/", pages::router()) - .route("/favicon.ico",get(file::favicon)) + .route("/favicon.ico", get(file::favicon)) .route("/js/*path", get(file::js)) .route("/css/*path", get(file::css)) .route("/fonts/*path", get(file::fonts)) diff --git a/src/public/pages.rs b/src/public/pages.rs index 196a441..32056b7 100644 --- a/src/public/pages.rs +++ b/src/public/pages.rs @@ -12,6 +12,8 @@ use crate::{ }, }; +use super::docs; + async fn root(user: Option<AuthorizedUser>, _: Log) -> Response { if user.is_some() { Redirect::to("/home").into_response() @@ -49,7 +51,7 @@ async fn admin() -> Response { } async fn api() -> Response { - super::serve("/api.html").await + docs::generate().await } async fn wordpress(_: Log) -> Response { |