docs is ssr'd
This commit is contained in:
parent
429da306ee
commit
d85dd163e3
11 changed files with 500 additions and 556 deletions
542
public/api.html
542
public/api.html
|
@ -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>
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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", ""));
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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");
|
||||
|
|
181
src/public/docs.rs
Normal file
181
src/public/docs.rs
Normal 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)
|
||||
}
|
|
@ -18,6 +18,7 @@ use crate::types::http::ResponseCode;
|
|||
|
||||
pub mod admin;
|
||||
pub mod console;
|
||||
pub mod docs;
|
||||
pub mod file;
|
||||
pub mod pages;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue