docs is ssr'd

This commit is contained in:
Tyler Murphy 2023-02-03 15:03:16 -05:00
parent 429da306ee
commit d85dd163e3
11 changed files with 500 additions and 556 deletions

View file

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

View file

@ -6,13 +6,29 @@ use tower_cookies::{Cookie, Cookies};
use crate::{ use crate::{
database, database,
public::admin, public::{admin, docs::{EndpointDocumentation, EndpointMethod}},
types::{ types::{
extract::{AdminUser, Check, CheckResult, Json}, extract::{AdminUser, Check, CheckResult, Json},
http::ResponseCode, 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)] #[derive(Deserialize)]
struct AdminAuthRequest { struct AdminAuthRequest {
secret: String, secret: String,
@ -40,6 +56,24 @@ async fn auth(cookies: Cookies, Json(body): Json<AdminAuthRequest>) -> Response
ResponseCode::Success.text("Successfully logged in") 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)] #[derive(Deserialize)]
struct QueryRequest { struct QueryRequest {
query: String, 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 { async fn posts(_: AdminUser) -> Response {
admin::generate_posts() 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 { async fn users(_: AdminUser) -> Response {
admin::generate_users() 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 { async fn sessions(_: AdminUser) -> Response {
admin::generate_sessions() admin::generate_sessions()
} }

View file

@ -3,11 +3,34 @@ use serde::Deserialize;
use time::{Duration, OffsetDateTime}; use time::{Duration, OffsetDateTime};
use tower_cookies::{Cookie, Cookies}; use tower_cookies::{Cookie, Cookies};
use crate::types::{ use crate::{types::{
extract::{AuthorizedUser, Check, CheckResult, Json, Log}, extract::{AuthorizedUser, Check, CheckResult, Json, Log},
http::ResponseCode, http::ResponseCode,
session::Session, session::Session,
user::User, 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)] #[derive(Deserialize, Debug)]
@ -93,9 +116,26 @@ async fn register(cookies: Cookies, Json(body): Json<RegistrationRequet>) -> Res
cookies.add(cookie); 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)] #[derive(Deserialize)]
struct LoginRequest { struct LoginRequest {
email: String, email: String,
@ -136,6 +176,19 @@ async fn login(cookies: Cookies, Json(body): Json<LoginRequest>) -> Response {
ResponseCode::Success.text("Successfully logged in") 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 { async fn logout(cookies: Cookies, AuthorizedUser(user): AuthorizedUser, _: Log) -> Response {
cookies.remove(Cookie::new("auth", "")); cookies.remove(Cookie::new("auth", ""));

View file

@ -6,10 +6,10 @@ use tower_governor::{
GovernorLayer, GovernorLayer,
}; };
mod admin; pub mod admin;
mod auth; pub mod auth;
mod posts; pub mod posts;
mod users; pub mod users;
pub use auth::RegistrationRequet; pub use auth::RegistrationRequet;

View file

@ -5,10 +5,28 @@ use axum::{
}; };
use serde::Deserialize; use serde::Deserialize;
use crate::types::{ use crate::{types::{
extract::{AuthorizedUser, Check, CheckResult, Json}, extract::{AuthorizedUser, Check, CheckResult, Json},
http::ResponseCode, http::ResponseCode,
post::Post, 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)] #[derive(Deserialize)]
@ -43,6 +61,24 @@ async fn create(
ResponseCode::Created.json(&json) 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)] #[derive(Deserialize)]
struct PostPageRequest { struct PostPageRequest {
page: u64, page: u64,
@ -69,10 +105,29 @@ async fn page(
ResponseCode::Success.json(&json) 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)] #[derive(Deserialize)]
struct UsersPostsRequest { struct UsersPostsRequest {
user_id: u64, user_id: u64,
page: u64 page: u64,
} }
impl Check for UsersPostsRequest { impl Check for UsersPostsRequest {
@ -96,6 +151,25 @@ async fn user(
ResponseCode::Success.json(&json) 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)] #[derive(Deserialize)]
struct PostCommentRequest { struct PostCommentRequest {
content: String, content: String,
@ -129,6 +203,25 @@ async fn comment(
ResponseCode::Success.text("Successfully commented on post") 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)] #[derive(Deserialize)]
struct PostLikeRequest { struct PostLikeRequest {
state: bool, state: bool,

View file

@ -1,8 +1,8 @@
use crate::types::{ use crate::{types::{
extract::{AuthorizedUser, Check, CheckResult, Json, Png}, extract::{AuthorizedUser, Check, CheckResult, Json, Png},
http::ResponseCode, http::ResponseCode,
user::User, user::User,
}; }, public::docs::{EndpointDocumentation, EndpointMethod}};
use axum::{ use axum::{
response::Response, response::Response,
routing::{post, put}, routing::{post, put},
@ -10,6 +10,24 @@ use axum::{
}; };
use serde::Deserialize; 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)] #[derive(Deserialize)]
struct UserLoadRequest { struct UserLoadRequest {
ids: Vec<u64>, ids: Vec<u64>,
@ -33,6 +51,25 @@ async fn load_batch(
ResponseCode::Success.json(&json) 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)] #[derive(Deserialize)]
struct UserPageReqiest { struct UserPageReqiest {
page: u64, page: u64,
@ -59,6 +96,19 @@ async fn load_page(
ResponseCode::Success.json(&json) 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 { async fn load_self(AuthorizedUser(user): AuthorizedUser) -> Response {
let Ok(json) = serde_json::to_string(&user) else { let Ok(json) = serde_json::to_string(&user) else {
return ResponseCode::InternalServerError.text("Failed to fetch user") return ResponseCode::InternalServerError.text("Failed to fetch user")
@ -67,6 +117,20 @@ async fn load_self(AuthorizedUser(user): AuthorizedUser) -> Response {
ResponseCode::Success.json(&json) 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 { async fn avatar(AuthorizedUser(user): AuthorizedUser, Png(img): Png) -> Response {
let path = format!("./public/image/custom/avatar/{}.png", user.user_id); 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") 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 { async fn banner(AuthorizedUser(user): AuthorizedUser, Png(img): Png) -> Response {
let path = format!("./public/image/custom/banner/{}.png", user.user_id); let path = format!("./public/image/custom/banner/{}.png", user.user_id);

View file

@ -94,7 +94,8 @@ pub fn get_users_post_page(user_id: u64, page: u64) -> Result<Vec<Post>, rusqlit
tracing::trace!("Retrieving users posts"); tracing::trace!("Retrieving users posts");
let page_size = 10; let page_size = 10;
let conn = database::connect()?; 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 = stmt.query_map([user_id, page_size, page_size * page], |row| {
let row = post_from_row(row)?; let row = post_from_row(row)?;
Ok(row) Ok(row)

View file

@ -15,6 +15,8 @@ use tracing_subscriber::{
}; };
use types::extract::RequestIp; use types::extract::RequestIp;
use crate::public::docs;
mod api; mod api;
mod database; mod database;
mod public; mod public;
@ -55,6 +57,8 @@ async fn main() {
exit(1) 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").expect("Coudn't make custom data directory");
fs::create_dir_all("./public/image/custom/avatar") fs::create_dir_all("./public/image/custom/avatar")
.expect("Coudn't make custom avatar directory"); .expect("Coudn't make custom avatar directory");

181
src/public/docs.rs Normal file
View file

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

View file

@ -18,6 +18,7 @@ use crate::types::http::ResponseCode;
pub mod admin; pub mod admin;
pub mod console; pub mod console;
pub mod docs;
pub mod file; pub mod file;
pub mod pages; pub mod pages;

View file

@ -12,6 +12,8 @@ use crate::{
}, },
}; };
use super::docs;
async fn root(user: Option<AuthorizedUser>, _: Log) -> Response { async fn root(user: Option<AuthorizedUser>, _: Log) -> Response {
if user.is_some() { if user.is_some() {
Redirect::to("/home").into_response() Redirect::to("/home").into_response()
@ -49,7 +51,7 @@ async fn admin() -> Response {
} }
async fn api() -> Response { async fn api() -> Response {
super::serve("/api.html").await docs::generate().await
} }
async fn wordpress(_: Log) -> Response { async fn wordpress(_: Log) -> Response {