add: user ads

This commit is contained in:
trisua 2025-08-11 20:21:05 -04:00
parent 46b3e66cd4
commit 2cb7d08ddc
41 changed files with 1095 additions and 29 deletions

View file

@ -0,0 +1,132 @@
use crate::{
cookie::CookieJar,
get_user_from_token,
image::{save_webp_buffer, JsonMultipart},
State,
};
use axum::{
extract::Path,
response::{IntoResponse, Redirect},
Extension, Json,
};
use tetratto_core::model::{
economy::UserAd,
oauth,
uploads::{MediaType, MediaUpload},
ApiReturn, Error,
};
use super::{CreateAd, UpdateAdIsRunning};
const MAXIMUM_AD_FILE_SIZE: usize = 4_194_304;
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()),
};
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);
match data.ad_click(host, id, user).await {
Ok(t) => Redirect::to(&t),
Err(_) => Redirect::to(&data.0.0.host),
}
}

View file

@ -1,3 +1,4 @@
pub mod ads;
pub mod app_data;
pub mod apps;
pub mod auth;
@ -31,7 +32,7 @@ use tetratto_core::model::{
PollOption, PostContext,
},
communities_permissions::CommunityPermission,
economy::ProductFulfillmentMethod,
economy::{ProductFulfillmentMethod, UserAdSize},
journals::JournalPrivacyPermission,
littleweb::{DomainData, DomainTld, ServiceFsEntry},
oauth::AppScope,
@ -767,6 +768,11 @@ pub fn routes() -> Router {
"/products/{id}/uploads/thumbnails",
delete(products::remove_thumbnail_request),
)
// ads
.route("/ads", post(ads::create_request))
.route("/ads/{id}", delete(ads::delete_request))
.route("/ads/{id}/running", post(ads::update_is_running_request))
.route("/ads/host/{host}/{id}/click", get(ads::click_request))
}
pub fn lw_routes() -> Router {
@ -1355,3 +1361,14 @@ pub struct UpdateProductUploads {
pub struct RemoveProductThumbnail {
pub idx: usize,
}
#[derive(Deserialize)]
pub struct CreateAd {
pub target: String,
pub size: UserAdSize,
}
#[derive(Deserialize)]
pub struct UpdateAdIsRunning {
pub is_running: bool,
}

View file

@ -48,7 +48,7 @@ pub async fn delete_request(
Path(id): Path<usize>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data, oauth::AppScope::UserCreateProducts) {
let user = match get_user_from_token!(jar, data, oauth::AppScope::UserManageProducts) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};

View file

@ -92,7 +92,7 @@ pub async fn create_refund_request(
Err(e) => return Json(e.into()),
};
if user.id != other_transfer.receiver {
if user.id != other_transfer.receiver || other_transfer.source != CoinTransferSource::Sale {
// only the receiver of the funds can issue a refund (atm)
return Json(Error::NotAllowed.into());
}

View file

@ -20,7 +20,7 @@ pub async fn get_request(
Body::from(read_image(PathBufD::current().extend(&[
data.0.0.dirs.media.as_str(),
"images",
"default-avatar.svg",
"default-banner.svg",
]))),
));
}
@ -34,7 +34,7 @@ pub async fn get_request(
Body::from(read_image(PathBufD::current().extend(&[
data.0.0.dirs.media.as_str(),
"images",
"default-avatar.svg",
"default-banner.svg",
]))),
));
}