add: upload alt text
This commit is contained in:
parent
3b5b0ce1a1
commit
e0e38b2b32
13 changed files with 224 additions and 59 deletions
|
@ -183,6 +183,7 @@ version = "1.0.0"
|
||||||
"settings:label.ips" = "IPs"
|
"settings:label.ips" = "IPs"
|
||||||
"settings:label.generate_invites" = "Generate invites"
|
"settings:label.generate_invites" = "Generate invites"
|
||||||
"settings:label.add_to_stack" = "Add to stack"
|
"settings:label.add_to_stack" = "Add to stack"
|
||||||
|
"settings:label.alt_text" = "Alt text"
|
||||||
"settings:tab.security" = "Security"
|
"settings:tab.security" = "Security"
|
||||||
"settings:tab.blocks" = "Blocks"
|
"settings:tab.blocks" = "Blocks"
|
||||||
"settings:tab.billing" = "Billing"
|
"settings:tab.billing" = "Billing"
|
||||||
|
|
|
@ -442,7 +442,6 @@
|
||||||
("alt" "Image upload")
|
("alt" "Image upload")
|
||||||
("onclick" "trigger('ui::lightbox_open', ['/api/v1/uploads/{{ upload }}'])"))
|
("onclick" "trigger('ui::lightbox_open', ['/api/v1/uploads/{{ upload }}'])"))
|
||||||
(text "{% endfor %}"))
|
(text "{% endfor %}"))
|
||||||
|
|
||||||
(text "{%- endif %} {%- endmacro %} {% macro notification(notification) -%}")
|
(text "{%- endif %} {%- endmacro %} {% macro notification(notification) -%}")
|
||||||
(div
|
(div
|
||||||
("class" "w-full card-nest")
|
("class" "w-full card-nest")
|
||||||
|
|
|
@ -574,32 +574,51 @@
|
||||||
(div
|
(div
|
||||||
("class" "card flex flex-col gap-2 secondary")
|
("class" "card flex flex-col gap-2 secondary")
|
||||||
(text "{{ components::supporter_ad(body=\"Become a supporter to upload images directly to posts!\") }} {% for upload in uploads %}")
|
(text "{{ components::supporter_ad(body=\"Become a supporter to upload images directly to posts!\") }} {% for upload in uploads %}")
|
||||||
(div
|
(details
|
||||||
("class" "card flex flex-wrap gap-2 items-center justify-between")
|
("class" "accordion w-full")
|
||||||
|
(summary
|
||||||
|
("class" "card flex flex-wrap gap-2 items-center justify-between")
|
||||||
|
(div
|
||||||
|
("class" "flex gap-2 items-center")
|
||||||
|
(icon_class (text "chevron-down") (text "dropdown_arrow"))
|
||||||
|
(b
|
||||||
|
(span
|
||||||
|
("class" "date")
|
||||||
|
(text "{{ upload.created }}"))
|
||||||
|
(text " ({{ upload.what }})")))
|
||||||
|
(div
|
||||||
|
("class" "flex gap-2")
|
||||||
|
(button
|
||||||
|
("class" "raised small")
|
||||||
|
("onclick" "trigger('ui::lightbox_open', ['/api/v1/uploads/{{ upload.id }}'])")
|
||||||
|
(text "{{ icon \"view\" }}")
|
||||||
|
(span
|
||||||
|
(text "{{ text \"general:action.view\" }}")))
|
||||||
|
(button
|
||||||
|
("class" "raised small red")
|
||||||
|
("onclick" "remove_upload('{{ upload.id }}')")
|
||||||
|
(text "{{ icon \"x\" }}")
|
||||||
|
(span
|
||||||
|
(text "{{ text \"stacks:label.remove\" }}")))))
|
||||||
|
|
||||||
(div
|
(div
|
||||||
("class" "flex gap-2 items-center")
|
("class" "inner flex flex-col gap-2")
|
||||||
("onclick" "trigger('ui::lightbox_open', ['/api/v1/uploads/{{ upload.id }}'])")
|
(form
|
||||||
("style" "cursor: pointer")
|
("class" "card lowered flex flex-col gap-2")
|
||||||
(text "{{ icon \"file-image\" }}")
|
("onsubmit" "update_upload_alt(event, '{{ upload.id }}')")
|
||||||
(b
|
(div
|
||||||
(span
|
("class" "flex flex-col gap-1")
|
||||||
("class" "date")
|
(label ("for" "alt_{{ upload.id }}") (b (str (text "settings:label.alt_text"))))
|
||||||
(text "{{ upload.created }}"))
|
(textarea
|
||||||
(text "({{ upload.what }})")))
|
("id" "alt_{{ upload.id }}")
|
||||||
(div
|
("name" "alt")
|
||||||
("class" "flex gap-2")
|
("class" "w-full")
|
||||||
(button
|
("placeholder" "Alternative text")
|
||||||
("class" "lowered small")
|
(text "{{ upload.alt|safe }}")))
|
||||||
("onclick" "trigger('ui::lightbox_open', ['/api/v1/uploads/{{ upload.id }}'])")
|
|
||||||
(text "{{ icon \"view\" }}")
|
(button
|
||||||
(span
|
(icon (text "check"))
|
||||||
(text "{{ text \"general:action.view\" }}")))
|
(str (text "general:action.save"))))))
|
||||||
(button
|
|
||||||
("class" "lowered small red")
|
|
||||||
("onclick" "remove_upload('{{ upload.id }}')")
|
|
||||||
(text "{{ icon \"x\" }}")
|
|
||||||
(span
|
|
||||||
(text "{{ text \"stacks:label.remove\" }}")))))
|
|
||||||
(text "{% endfor %} {{ components::pagination(page=page, items=uploads|length, key=\"#/account/uploads\") }}")
|
(text "{% endfor %} {{ components::pagination(page=page, items=uploads|length, key=\"#/account/uploads\") }}")
|
||||||
(script
|
(script
|
||||||
(text "globalThis.remove_upload = async (id) => {
|
(text "globalThis.remove_upload = async (id) => {
|
||||||
|
@ -621,6 +640,26 @@
|
||||||
res.message,
|
res.message,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
globalThis.update_upload_alt = async (e, id) => {
|
||||||
|
e.preventDefault();
|
||||||
|
fetch(`/api/v1/uploads/${id}/alt`, {
|
||||||
|
method: \"POST\",
|
||||||
|
headers: {
|
||||||
|
\"Content-Type\": \"application/json\",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
alt: e.target.alt.value,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((res) => {
|
||||||
|
trigger(\"atto::toast\", [
|
||||||
|
res.ok ? \"success\" : \"error\",
|
||||||
|
res.message,
|
||||||
|
]);
|
||||||
|
});
|
||||||
};"))))))
|
};"))))))
|
||||||
|
|
||||||
(text "{% if config.security.enable_invite_codes -%}")
|
(text "{% if config.security.enable_invite_codes -%}")
|
||||||
|
@ -1508,7 +1547,6 @@
|
||||||
|
|
||||||
globalThis.render_preset_lis = (preset, id) => {
|
globalThis.render_preset_lis = (preset, id) => {
|
||||||
for (const x of preset) {
|
for (const x of preset) {
|
||||||
console.log(id);
|
|
||||||
document.getElementById(id).innerHTML += `<li><b>${x[0]}:</b> ${x[1]}</li>`;
|
document.getElementById(id).innerHTML += `<li><b>${x[0]}:</b> ${x[1]}</li>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -156,9 +156,7 @@ media_theme_pref();
|
||||||
.replaceAll(" year ago", "y");
|
.replaceAll(" year ago", "y");
|
||||||
}
|
}
|
||||||
|
|
||||||
element.innerText =
|
element.innerText = !pretty ? then.toLocaleDateString() : pretty;
|
||||||
pretty === undefined ? then.toLocaleDateString() : pretty;
|
|
||||||
|
|
||||||
element.style.display = "inline-block";
|
element.style.display = "inline-block";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -198,9 +196,7 @@ media_theme_pref();
|
||||||
.replaceAll(" year ago", "y")
|
.replaceAll(" year ago", "y")
|
||||||
.replaceAll("Yesterday", "1d") || "";
|
.replaceAll("Yesterday", "1d") || "";
|
||||||
|
|
||||||
element.innerText =
|
element.innerText = !pretty ? then.toLocaleDateString() : pretty;
|
||||||
pretty === undefined ? then.toLocaleDateString() : pretty;
|
|
||||||
|
|
||||||
element.style.display = "inline-block";
|
element.style.display = "inline-block";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1145,8 +1141,15 @@ ${option.input_element_type === "textarea" ? `${option.value}</textarea>` : ""}
|
||||||
);
|
);
|
||||||
|
|
||||||
// lightbox
|
// lightbox
|
||||||
self.define("lightbox_open", (_, src) => {
|
self.define("lightbox_open", async (_, src) => {
|
||||||
document.getElementById("lightbox_img").src = src;
|
document.getElementById("lightbox_img").src = src;
|
||||||
|
|
||||||
|
const data = await (await fetch(`${src}/data`)).json();
|
||||||
|
document
|
||||||
|
.getElementById("lightbox_img")
|
||||||
|
.setAttribute("alt", data.payload.alt);
|
||||||
|
document.getElementById("lightbox_img").title = data.payload.alt;
|
||||||
|
|
||||||
document.getElementById("lightbox_img_a").href = src;
|
document.getElementById("lightbox_img_a").href = src;
|
||||||
document.getElementById("lightbox").classList.remove("hidden");
|
document.getElementById("lightbox").classList.remove("hidden");
|
||||||
});
|
});
|
||||||
|
|
|
@ -152,10 +152,11 @@ pub async fn create_request(
|
||||||
}
|
}
|
||||||
|
|
||||||
// ...
|
// ...
|
||||||
match data.create_post(props.clone()).await {
|
let uploads = props.uploads.clone();
|
||||||
|
match data.create_post(props).await {
|
||||||
Ok(id) => {
|
Ok(id) => {
|
||||||
// write to uploads
|
// write to uploads
|
||||||
for (i, upload_id) in props.uploads.iter().enumerate() {
|
for (i, upload_id) in uploads.iter().enumerate() {
|
||||||
let image = match images.get(i) {
|
let image = match images.get(i) {
|
||||||
Some(img) => img,
|
Some(img) => img,
|
||||||
None => {
|
None => {
|
||||||
|
|
|
@ -637,6 +637,8 @@ pub fn routes() -> Router {
|
||||||
// uploads
|
// uploads
|
||||||
.route("/uploads/{id}", get(uploads::get_request))
|
.route("/uploads/{id}", get(uploads::get_request))
|
||||||
.route("/uploads/{id}", delete(uploads::delete_request))
|
.route("/uploads/{id}", delete(uploads::delete_request))
|
||||||
|
.route("/uploads/{id}/data", get(uploads::get_json_request))
|
||||||
|
.route("/uploads/{id}/alt", post(uploads::update_alt_request))
|
||||||
// services
|
// services
|
||||||
.route("/services", get(services::list_request))
|
.route("/services", get(services::list_request))
|
||||||
.route("/services", post(services::create_request))
|
.route("/services", post(services::create_request))
|
||||||
|
@ -1124,3 +1126,8 @@ pub struct UpdateProductDescription {
|
||||||
pub struct UpdateProductPrice {
|
pub struct UpdateProductPrice {
|
||||||
pub price: ProductPrice,
|
pub price: ProductPrice,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct UpdateUploadAlt {
|
||||||
|
pub alt: String,
|
||||||
|
}
|
||||||
|
|
|
@ -1,13 +1,20 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
get_user_from_token,
|
get_user_from_token,
|
||||||
|
image::{save_webp_buffer, JsonMultipart},
|
||||||
routes::api::v1::{
|
routes::api::v1::{
|
||||||
CreateProduct, UpdateProductDescription, UpdateProductName, UpdateProductPrice,
|
communities::posts::MAXIMUM_FILE_SIZE, CreateProduct, UpdateProductDescription,
|
||||||
|
UpdateProductName, UpdateProductPrice,
|
||||||
},
|
},
|
||||||
State,
|
State,
|
||||||
};
|
};
|
||||||
use axum::{extract::Path, response::IntoResponse, Extension, Json};
|
use axum::{extract::Path, response::IntoResponse, Extension, Json};
|
||||||
use axum_extra::extract::CookieJar;
|
use axum_extra::extract::CookieJar;
|
||||||
use tetratto_core::model::{products::Product, oauth, ApiReturn, Error};
|
use tetratto_core::model::{
|
||||||
|
oauth,
|
||||||
|
products::Product,
|
||||||
|
uploads::{MediaType, MediaUpload},
|
||||||
|
ApiReturn, Error,
|
||||||
|
};
|
||||||
|
|
||||||
pub async fn get_request(
|
pub async fn get_request(
|
||||||
Path(id): Path<usize>,
|
Path(id): Path<usize>,
|
||||||
|
@ -44,7 +51,7 @@ pub async fn list_request(jar: CookieJar, Extension(data): Extension<State>) ->
|
||||||
pub async fn create_request(
|
pub async fn create_request(
|
||||||
jar: CookieJar,
|
jar: CookieJar,
|
||||||
Extension(data): Extension<State>,
|
Extension(data): Extension<State>,
|
||||||
Json(req): Json<CreateProduct>,
|
JsonMultipart(uploads, req): JsonMultipart<CreateProduct>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let data = &(data.read().await).0;
|
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::UserCreateProducts) {
|
||||||
|
@ -52,21 +59,75 @@ pub async fn create_request(
|
||||||
None => return Json(Error::NotAllowed.into()),
|
None => return Json(Error::NotAllowed.into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
match data
|
if uploads.len() > 4 {
|
||||||
.create_product(Product::new(
|
return Json(
|
||||||
user.id,
|
Error::MiscError("Too many uploads. Please use a maximum of 4".to_string()).into(),
|
||||||
req.name,
|
);
|
||||||
req.description,
|
}
|
||||||
req.price,
|
|
||||||
req.product_type,
|
let mut product = Product::new(
|
||||||
))
|
user.id,
|
||||||
.await
|
req.name,
|
||||||
{
|
req.description,
|
||||||
Ok(x) => Json(ApiReturn {
|
req.price,
|
||||||
ok: true,
|
req.product_type,
|
||||||
message: "Product created".to_string(),
|
);
|
||||||
payload: x.id.to_string(),
|
|
||||||
}),
|
// check sizes
|
||||||
|
for img in &uploads {
|
||||||
|
if img.len() > MAXIMUM_FILE_SIZE {
|
||||||
|
return Json(Error::FileTooLarge.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create uploads
|
||||||
|
for _ in 0..uploads.len() {
|
||||||
|
product.uploads.push(
|
||||||
|
match data
|
||||||
|
.create_upload(MediaUpload::new(MediaType::Webp, product.owner))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(u) => u.id,
|
||||||
|
Err(e) => return Json(e.into()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let product_uploads = product.uploads.clone();
|
||||||
|
match data.create_product(product).await {
|
||||||
|
Ok(x) => {
|
||||||
|
// store uploads
|
||||||
|
for (i, upload_id) in product_uploads.iter().enumerate() {
|
||||||
|
let image = match uploads.get(i) {
|
||||||
|
Some(img) => img,
|
||||||
|
None => {
|
||||||
|
if let Err(e) = data.delete_upload(*upload_id).await {
|
||||||
|
return Json(e.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let upload = match data.get_upload_by_id(*upload_id).await {
|
||||||
|
Ok(u) => u,
|
||||||
|
Err(e) => return Json(e.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) =
|
||||||
|
save_webp_buffer(&upload.path(&data.0.0).to_string(), image.to_vec(), None)
|
||||||
|
{
|
||||||
|
return Json(Error::MiscError(e.to_string()).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...
|
||||||
|
Json(ApiReturn {
|
||||||
|
ok: true,
|
||||||
|
message: "Product created".to_string(),
|
||||||
|
payload: x.id.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
Err(e) => Json(e.into()),
|
Err(e) => Json(e.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::fs::exists;
|
||||||
use axum::{body::Body, extract::Path, response::IntoResponse, Extension, Json};
|
use axum::{body::Body, extract::Path, response::IntoResponse, Extension, Json};
|
||||||
use axum_extra::extract::CookieJar;
|
use axum_extra::extract::CookieJar;
|
||||||
use pathbufd::PathBufD;
|
use pathbufd::PathBufD;
|
||||||
use crate::{get_user_from_token, State};
|
use crate::{get_user_from_token, routes::api::v1::UpdateUploadAlt, State};
|
||||||
use super::auth::images::read_image;
|
use super::auth::images::read_image;
|
||||||
use tetratto_core::model::{carp::CarpGraph, oauth, uploads::MediaType, ApiReturn, Error};
|
use tetratto_core::model::{carp::CarpGraph, oauth, uploads::MediaType, ApiReturn, Error};
|
||||||
|
|
||||||
|
@ -52,6 +52,24 @@ pub async fn get_request(
|
||||||
Ok(([("Content-Type", upload.what.mime())], Body::from(bytes)))
|
Ok(([("Content-Type", upload.what.mime())], Body::from(bytes)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_json_request(
|
||||||
|
Path(id): Path<usize>,
|
||||||
|
Extension(data): Extension<State>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let data = &(data.read().await).0;
|
||||||
|
|
||||||
|
let upload = match data.get_upload_by_id(id).await {
|
||||||
|
Ok(u) => u,
|
||||||
|
Err(e) => return Json(e.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
Json(ApiReturn {
|
||||||
|
ok: true,
|
||||||
|
message: "Success".to_string(),
|
||||||
|
payload: Some(upload),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn delete_request(
|
pub async fn delete_request(
|
||||||
jar: CookieJar,
|
jar: CookieJar,
|
||||||
Extension(data): Extension<State>,
|
Extension(data): Extension<State>,
|
||||||
|
@ -72,3 +90,25 @@ pub async fn delete_request(
|
||||||
Err(e) => Json(e.into()),
|
Err(e) => Json(e.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn update_alt_request(
|
||||||
|
jar: CookieJar,
|
||||||
|
Extension(data): Extension<State>,
|
||||||
|
Path(id): Path<usize>,
|
||||||
|
Json(props): Json<UpdateUploadAlt>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let data = &(data.read().await).0;
|
||||||
|
let user = match get_user_from_token!(jar, data, oauth::AppScope::UserManageUploads) {
|
||||||
|
Some(ua) => ua,
|
||||||
|
None => return Json(Error::NotAllowed.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
match data.update_upload_alt(id, &user, &props.alt).await {
|
||||||
|
Ok(_) => Json(ApiReturn {
|
||||||
|
ok: true,
|
||||||
|
message: "Upload updated".to_string(),
|
||||||
|
payload: (),
|
||||||
|
}),
|
||||||
|
Err(e) => Json(e.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -22,8 +22,14 @@ pub async fn seller_settings_request(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let products = match data.0.get_products_by_user(user.id).await {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => return Err(Html(render_error(e, &jar, &data, &None).await)),
|
||||||
|
};
|
||||||
|
|
||||||
let lang = get_lang!(jar, data.0);
|
let lang = get_lang!(jar, data.0);
|
||||||
let context = initial_context(&data.0.0.0, lang, &Some(user)).await;
|
let mut context = initial_context(&data.0.0.0, lang, &Some(user)).await;
|
||||||
|
context.insert("list", &products);
|
||||||
|
|
||||||
// return
|
// return
|
||||||
Ok(Html(
|
Ok(Html(
|
||||||
|
|
|
@ -2,5 +2,6 @@ CREATE TABLE IF NOT EXISTS uploads (
|
||||||
id BIGINT NOT NULL PRIMARY KEY,
|
id BIGINT NOT NULL PRIMARY KEY,
|
||||||
created BIGINT NOT NULL,
|
created BIGINT NOT NULL,
|
||||||
owner BIGINT NOT NULL,
|
owner BIGINT NOT NULL,
|
||||||
what TEXT NOT NULL
|
what TEXT NOT NULL,
|
||||||
|
alt TEXT NOT NULL
|
||||||
)
|
)
|
||||||
|
|
|
@ -16,10 +16,11 @@ impl DataManager {
|
||||||
created: get!(x->1(i64)) as usize,
|
created: get!(x->1(i64)) as usize,
|
||||||
owner: get!(x->2(i64)) as usize,
|
owner: get!(x->2(i64)) as usize,
|
||||||
what: serde_json::from_str(&get!(x->3(String))).unwrap(),
|
what: serde_json::from_str(&get!(x->3(String))).unwrap(),
|
||||||
|
alt: get!(x->4(String)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto_method!(get_upload_by_id(usize as i64)@get_upload_from_row -> "SELECT * FROM uploads WHERE id = $1" --name="upload" --returns=MediaUpload --cache-key-tmpl="atto.uploads:{}");
|
auto_method!(get_upload_by_id(usize as i64)@get_upload_from_row -> "SELECT * FROM uploads WHERE id = $1" --name="upload" --returns=MediaUpload --cache-key-tmpl="atto.upload:{}");
|
||||||
|
|
||||||
/// Get all uploads (paginated).
|
/// Get all uploads (paginated).
|
||||||
///
|
///
|
||||||
|
@ -113,12 +114,13 @@ impl DataManager {
|
||||||
|
|
||||||
let res = execute!(
|
let res = execute!(
|
||||||
&conn,
|
&conn,
|
||||||
"INSERT INTO uploads VALUES ($1, $2, $3, $4)",
|
"INSERT INTO uploads VALUES ($1, $2, $3, $4, $5)",
|
||||||
params![
|
params![
|
||||||
&(data.id as i64),
|
&(data.id as i64),
|
||||||
&(data.created as i64),
|
&(data.created as i64),
|
||||||
&(data.owner as i64),
|
&(data.owner as i64),
|
||||||
&serde_json::to_string(&data.what).unwrap().as_str(),
|
&serde_json::to_string(&data.what).unwrap().as_str(),
|
||||||
|
&data.alt,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -187,4 +189,6 @@ impl DataManager {
|
||||||
self.0.1.remove(format!("atto.upload:{}", id)).await;
|
self.0.1.remove(format!("atto.upload:{}", id)).await;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto_method!(update_upload_alt(&str)@get_upload_by_id:FinePermission::MANAGE_UPLOADS; -> "UPDATE uploads SET alt = $1 WHERE id = $2" --cache-key-tmpl="atto.upload:{}");
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@ pub struct MediaUpload {
|
||||||
pub created: usize,
|
pub created: usize,
|
||||||
pub owner: usize,
|
pub owner: usize,
|
||||||
pub what: MediaType,
|
pub what: MediaType,
|
||||||
|
pub alt: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MediaUpload {
|
impl MediaUpload {
|
||||||
|
@ -54,6 +55,7 @@ impl MediaUpload {
|
||||||
created: unix_epoch_timestamp(),
|
created: unix_epoch_timestamp(),
|
||||||
owner,
|
owner,
|
||||||
what,
|
what,
|
||||||
|
alt: String::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
2
sql_changes/uploads_alt.sql
Normal file
2
sql_changes/uploads_alt.sql
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE uploads
|
||||||
|
ADD COLUMN alt TEXT NOT NULL DEFAULT '';
|
Loading…
Add table
Add a link
Reference in a new issue