tetratto/crates/app/src/routes/api/v1/ads.rs

195 lines
5.2 KiB
Rust

use crate::{
cookie::CookieJar,
get_user_from_token,
image::{save_webp_buffer, JsonMultipart},
State,
};
use axum::{
extract::Path,
response::{Html, IntoResponse},
Extension, Json,
http::StatusCode,
};
use tetratto_core::model::{
economy::UserAd,
oauth,
uploads::{MediaType, MediaUpload},
ApiReturn, Error,
};
use super::{CreateAd, UpdateAdIsRunning};
const MAXIMUM_AD_FILE_SIZE: usize = 2_097_152;
pub async fn create_request(
jar: CookieJar,
Extension(data): Extension<State>,
JsonMultipart(bytes_parts, req): JsonMultipart<CreateAd>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data, oauth::AppScope::UserCreateProducts) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
// get file
let file = match bytes_parts.get(0) {
Some(x) => x,
None => return Json(Error::Unknown.into()),
};
if file.len() > MAXIMUM_AD_FILE_SIZE {
return Json(Error::FileTooLarge.into());
}
let upload = match data
.create_upload(MediaUpload::new(MediaType::Webp, user.id))
.await
{
Ok(x) => x,
Err(e) => return Json(e.into()),
};
match data
.create_ad(UserAd::new(user.id, upload.id, req.target, req.size))
.await
{
Ok(_) => {
// write image
if let Err(e) =
save_webp_buffer(&upload.path(&data.0.0).to_string(), file.to_vec(), None)
{
return Json(Error::MiscError(e.to_string()).into());
}
// ...
Json(ApiReturn {
ok: true,
message: "Ad created".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, oauth::AppScope::UserManageProducts) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
match data.delete_ad(id, &user).await {
Ok(_) => Json(ApiReturn {
ok: true,
message: "Ad deleted".to_string(),
payload: (),
}),
Err(e) => Json(e.into()),
}
}
pub async fn update_is_running_request(
jar: CookieJar,
Extension(data): Extension<State>,
Path(id): Path<usize>,
Json(req): Json<UpdateAdIsRunning>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data, oauth::AppScope::UserManageProducts) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
let ad = match data.get_ad_by_id(id).await {
Ok(x) => x,
Err(e) => return Json(e.into()),
};
if !ad.is_running && user.coins < 50 {
return Json(
Error::MiscError(
"You must have a minimum of 50 coins in your balance to run ads".to_string(),
)
.into(),
);
}
match data
.update_ad_is_running(id, &user, if req.is_running { 1 } else { 0 })
.await
{
Ok(_) => Json(ApiReturn {
ok: true,
message: "Ad updated".to_string(),
payload: (),
}),
Err(e) => Json(e.into()),
}
}
pub async fn click_request(
jar: CookieJar,
Extension(data): Extension<State>,
Path((host, id)): Path<(usize, usize)>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = get_user_from_token!(jar, data);
let bad_return = (
StatusCode::FOUND,
[
(
"Set-Cookie".to_string(),
format!(
"Atto-Clicked=true; Path=/api/v1/ads/host/{host}/{id}/click; Max-Age=86400"
),
),
("Location".to_string(), data.0.0.host.clone()),
],
Html(""),
);
if jar.get("Atto-Clicked").is_some() {
// we've already clicked this ad, don't charge
match data.get_ad_by_id(id).await {
Ok(x) => {
return (
StatusCode::FOUND,
[
(
"Set-Cookie".to_string(),
format!(
"Atto-Clicked=true; Path=/api/v1/ads/host/{host}/{id}/click; Max-Age=86400"
),
),
("Location".to_string(), x.target),
],
Html(""),
);
}
Err(_) => return bad_return,
}
}
match data.ad_click(host, id, user).await {
Ok(t) => (
StatusCode::FOUND,
[
(
"Set-Cookie".to_string(),
format!(
"Atto-Clicked=true; Path=/api/v1/ads/host/{host}/{id}/click; Max-Age=86400"
),
),
("Location".to_string(), t),
],
Html(""),
),
Err(_) => bad_return,
}
}