add: apps api

This commit is contained in:
trisua 2025-06-14 14:45:52 -04:00
parent 2a99d49c8a
commit ebded00fd3
33 changed files with 698 additions and 31 deletions

View file

@ -0,0 +1,151 @@
use crate::{
get_user_from_token,
routes::api::v1::{UpdateAppHomepage, UpdateAppQuotaStatus, UpdateAppRedirect, UpdateAppTitle},
State,
};
use axum::{Extension, Json, extract::Path, response::IntoResponse};
use axum_extra::extract::CookieJar;
use tetratto_core::model::{apps::ThirdPartyApp, permissions::FinePermission, ApiReturn, Error};
use super::CreateApp;
pub async fn create_request(
jar: CookieJar,
Extension(data): Extension<State>,
Json(req): Json<CreateApp>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
match data
.create_app(ThirdPartyApp::new(
req.title,
user.id,
req.homepage,
req.redirect,
))
.await
{
Ok(s) => Json(ApiReturn {
ok: true,
message: "App created".to_string(),
payload: s.id.to_string(),
}),
Err(e) => Json(e.into()),
}
}
pub async fn update_title_request(
jar: CookieJar,
Extension(data): Extension<State>,
Path(id): Path<usize>,
Json(req): Json<UpdateAppTitle>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
match data.update_app_title(id, &user, &req.title).await {
Ok(_) => Json(ApiReturn {
ok: true,
message: "App updated".to_string(),
payload: (),
}),
Err(e) => Json(e.into()),
}
}
pub async fn update_homepage_request(
jar: CookieJar,
Extension(data): Extension<State>,
Path(id): Path<usize>,
Json(req): Json<UpdateAppHomepage>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
match data.update_app_homepage(id, &user, &req.homepage).await {
Ok(_) => Json(ApiReturn {
ok: true,
message: "App updated".to_string(),
payload: (),
}),
Err(e) => Json(e.into()),
}
}
pub async fn update_redirect_request(
jar: CookieJar,
Extension(data): Extension<State>,
Path(id): Path<usize>,
Json(req): Json<UpdateAppRedirect>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
match data.update_app_redirect(id, &user, &req.redirect).await {
Ok(_) => Json(ApiReturn {
ok: true,
message: "App updated".to_string(),
payload: (),
}),
Err(e) => Json(e.into()),
}
}
pub async fn update_quota_status_request(
jar: CookieJar,
Extension(data): Extension<State>,
Path(id): Path<usize>,
Json(req): Json<UpdateAppQuotaStatus>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
if !user.permissions.check(FinePermission::MANAGE_APPS) {
return Json(Error::NotAllowed.into());
}
match data.update_app_quota_status(id, req.quota_status).await {
Ok(_) => Json(ApiReturn {
ok: true,
message: "App updated".to_string(),
payload: (),
}),
Err(e) => Json(e.into()),
}
}
pub async fn delete_request(
jar: CookieJar,
Extension(data): Extension<State>,
Path(id): Path<usize>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
match data.delete_app(id, &user).await {
Ok(_) => Json(ApiReturn {
ok: true,
message: "App deleted".to_string(),
payload: (),
}),
Err(e) => Json(e.into()),
}
}

View file

@ -164,8 +164,8 @@ pub async fn banner_request(
))
}
pub static MAXIMUM_FILE_SIZE: usize = 8388608;
pub static MAXIMUM_GIF_FILE_SIZE: usize = 2097152;
pub const MAXIMUM_FILE_SIZE: usize = 8388608;
pub const MAXIMUM_GIF_FILE_SIZE: usize = 2097152;
/// Upload avatar
pub async fn upload_avatar_request(

View file

@ -1,3 +1,4 @@
pub mod apps;
pub mod auth;
pub mod communities;
pub mod notifications;
@ -17,6 +18,7 @@ use axum::{
};
use serde::Deserialize;
use tetratto_core::model::{
apps::AppQuota,
communities::{
CommunityContext, CommunityJoinAccess, CommunityReadAccess, CommunityWriteAccess,
PollOption, PostContext,
@ -321,6 +323,10 @@ pub fn routes() -> Router {
"/auth/user/find_by_ip/{ip}",
get(auth::profile::redirect_from_ip),
)
.route(
"/auth/user/find_by_stripe_id/{id}",
get(auth::profile::redirect_from_stripe_id),
)
.route("/auth/ip/{ip}/block", post(auth::social::ip_block_request))
.route(
"/auth/user/{id}/gpa",
@ -342,6 +348,16 @@ pub fn routes() -> Router {
"/auth/user/{id}/followers",
get(auth::social::followers_request),
)
// apps
.route("/apps", post(apps::create_request))
.route("/apps/{id}/title", post(apps::update_title_request))
.route("/apps/{id}/homepage", post(apps::update_homepage_request))
.route("/apps/{id}/redirect", post(apps::update_redirect_request))
.route(
"/apps/{id}/quota_status",
post(apps::update_quota_status_request),
)
.route("/apps/{id}", delete(apps::delete_request))
// warnings
.route("/warnings/{id}", get(auth::user_warnings::get_request))
.route("/warnings/{id}", post(auth::user_warnings::create_request))
@ -773,3 +789,30 @@ pub struct AppendAssociations {
pub struct UpdatePostIsOpen {
pub open: bool,
}
#[derive(Deserialize)]
pub struct CreateApp {
pub title: String,
pub homepage: String,
pub redirect: String,
}
#[derive(Deserialize)]
pub struct UpdateAppTitle {
pub title: String,
}
#[derive(Deserialize)]
pub struct UpdateAppHomepage {
pub homepage: String,
}
#[derive(Deserialize)]
pub struct UpdateAppRedirect {
pub redirect: String,
}
#[derive(Deserialize)]
pub struct UpdateAppQuotaStatus {
pub quota_status: AppQuota,
}

View file

@ -22,6 +22,10 @@ pub fn routes(config: &Config) -> Router {
"/public",
get_service(tower_http::services::ServeDir::new(&config.dirs.assets)),
)
.nest_service(
"/reference",
get_service(tower_http::services::ServeDir::new(&config.dirs.rustdoc)),
)
.route("/public/favicon.svg", get(assets::favicon_request))
.route_service(
"/robots.txt",

View file

@ -0,0 +1,35 @@
use super::render_error;
use crate::{assets::initial_context, get_lang, get_user_from_token, State};
use axum::{
response::{Html, IntoResponse},
Extension,
};
use axum_extra::extract::CookieJar;
use tetratto_core::model::Error;
/// `/developer`
pub async fn home_request(jar: CookieJar, Extension(data): Extension<State>) -> impl IntoResponse {
let data = data.read().await;
let user = match get_user_from_token!(jar, data.0) {
Some(ua) => ua,
None => {
return Err(Html(
render_error(Error::NotAllowed, &jar, &data, &None).await,
));
}
};
let list = match data.0.get_apps_by_owner(user.id).await {
Ok(p) => p,
Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)),
};
let lang = get_lang!(jar, data.0);
let mut context = initial_context(&data.0.0.0, lang, &Some(user)).await;
context.insert("list", &list);
// return
Ok(Html(
data.1.render("developer/home.html", &context).unwrap(),
))
}

View file

@ -1,5 +1,6 @@
pub mod auth;
pub mod communities;
pub mod developer;
pub mod forge;
pub mod misc;
pub mod mod_panel;
@ -116,6 +117,8 @@ pub fn routes() -> Router {
.route("/forge/{title}", get(forge::info_request))
.route("/forge/{title}/tickets", get(forge::tickets_request))
.route("/forge/{title}/members", get(communities::members_request))
// developer
.route("/developer", get(developer::home_request))
// stacks
.route("/stacks", get(stacks::list_request))
.route("/stacks/{id}", get(stacks::posts_request))