summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTyler Murphy <tylermurphy534@gmail.com>2023-02-03 15:03:16 -0500
committerTyler Murphy <tylermurphy534@gmail.com>2023-02-03 15:03:16 -0500
commitd85dd163e34ebdf963e1299b4ad3387ea713797f (patch)
tree9f8dfedb0f48902b4fa5a2be7d57145ecf816333
parentfavicon fix / readme (diff)
downloadxssbook-d85dd163e34ebdf963e1299b4ad3387ea713797f.tar.gz
xssbook-d85dd163e34ebdf963e1299b4ad3387ea713797f.tar.bz2
xssbook-d85dd163e34ebdf963e1299b4ad3387ea713797f.zip
docs is ssr'd
-rw-r--r--public/api.html542
-rw-r--r--src/api/admin.rs75
-rw-r--r--src/api/auth.rs57
-rw-r--r--src/api/mod.rs8
-rw-r--r--src/api/posts.rs97
-rw-r--r--src/api/users.rs82
-rw-r--r--src/database/posts.rs3
-rw-r--r--src/main.rs4
-rw-r--r--src/public/docs.rs181
-rw-r--r--src/public/mod.rs3
-rw-r--r--src/public/pages.rs4
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 {