mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-11-29 07:41:20 +00:00
Adding listMedia endpoint, to view all your local image uploads.
- Fixes #4445
This commit is contained in:
parent
6778279bb6
commit
3c51eaeb88
10 changed files with 109 additions and 26 deletions
|
@ -27,7 +27,7 @@
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-plugin-prettier": "^5.0.1",
|
"eslint-plugin-prettier": "^5.0.1",
|
||||||
"jest": "^29.5.0",
|
"jest": "^29.5.0",
|
||||||
"lemmy-js-client": "0.19.4-alpha.6",
|
"lemmy-js-client": "0.19.4-alpha.7",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"ts-jest": "^29.1.0",
|
"ts-jest": "^29.1.0",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3"
|
||||||
|
|
|
@ -30,8 +30,8 @@ devDependencies:
|
||||||
specifier: ^29.5.0
|
specifier: ^29.5.0
|
||||||
version: 29.7.0(@types/node@20.11.22)
|
version: 29.7.0(@types/node@20.11.22)
|
||||||
lemmy-js-client:
|
lemmy-js-client:
|
||||||
specifier: 0.19.4-alpha.6
|
specifier: 0.19.4-alpha.7
|
||||||
version: 0.19.4-alpha.6
|
version: 0.19.4-alpha.7
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.2.5
|
specifier: ^3.2.5
|
||||||
version: 3.2.5
|
version: 3.2.5
|
||||||
|
@ -2390,8 +2390,8 @@ packages:
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/lemmy-js-client@0.19.4-alpha.6:
|
/lemmy-js-client@0.19.4-alpha.7:
|
||||||
resolution: {integrity: sha512-x4htMlpoZ7hzrhrIk82aompVxbpu2ZDWtmWNGraM0+27nUCDf6gYxJH5nb5R/o39BQe5KSHq6zoBdliBwAY40w==}
|
resolution: {integrity: sha512-1xvSDlhJmU3IzhT2+pvqPWKHo0P/aYTlpObL3hLy1RgaZLapvn3W7XC48cOydas+MAm2WBFsiFX9bi5X+5FWFA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
cross-fetch: 4.0.0
|
cross-fetch: 4.0.0
|
||||||
form-data: 4.0.0
|
form-data: 4.0.0
|
||||||
|
|
|
@ -48,6 +48,15 @@ test("Upload image and delete it", async () => {
|
||||||
const content = downloadFileSync(upload.url);
|
const content = downloadFileSync(upload.url);
|
||||||
expect(content.length).toBeGreaterThan(0);
|
expect(content.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
// Ensure that it comes back with the list_media endpoint
|
||||||
|
const listMediaRes = await alphaImage.listMedia({});
|
||||||
|
expect(listMediaRes.images.length).toBe(1);
|
||||||
|
|
||||||
|
// The deleteUrl is a combination of the endpoint, delete token, and alias
|
||||||
|
let firstImage = listMediaRes.images[0];
|
||||||
|
let deleteUrl = `${alphaUrl}/pictrs/image/delete/${firstImage.pictrs_delete_token}/${firstImage.pictrs_alias}`;
|
||||||
|
expect(deleteUrl).toBe(upload.delete_url);
|
||||||
|
|
||||||
// delete image
|
// delete image
|
||||||
const delete_form: DeleteImage = {
|
const delete_form: DeleteImage = {
|
||||||
token: upload.files![0].delete_token,
|
token: upload.files![0].delete_token,
|
||||||
|
|
28
crates/api/src/local_user/list_media.rs
Normal file
28
crates/api/src/local_user/list_media.rs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
use actix_web::web::{Data, Json, Query};
|
||||||
|
use lemmy_api_common::{
|
||||||
|
context::LemmyContext,
|
||||||
|
person::{ListMedia, ListMediaResponse},
|
||||||
|
};
|
||||||
|
use lemmy_db_schema::source::images::LocalImage;
|
||||||
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
|
use lemmy_utils::error::LemmyError;
|
||||||
|
|
||||||
|
/// Lists comment reports for a community if an id is supplied
|
||||||
|
/// or returns all comment reports for communities a user moderates
|
||||||
|
#[tracing::instrument(skip(context))]
|
||||||
|
pub async fn list_media(
|
||||||
|
data: Query<ListMedia>,
|
||||||
|
context: Data<LemmyContext>,
|
||||||
|
local_user_view: LocalUserView,
|
||||||
|
) -> Result<Json<ListMediaResponse>, LemmyError> {
|
||||||
|
let page = data.page;
|
||||||
|
let limit = data.limit;
|
||||||
|
let images = LocalImage::get_all_paged_by_local_user_id(
|
||||||
|
&mut context.pool(),
|
||||||
|
local_user_view.local_user.id,
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(Json(ListMediaResponse { images }))
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ pub mod generate_totp_secret;
|
||||||
pub mod get_captcha;
|
pub mod get_captcha;
|
||||||
pub mod list_banned;
|
pub mod list_banned;
|
||||||
pub mod list_logins;
|
pub mod list_logins;
|
||||||
|
pub mod list_media;
|
||||||
pub mod login;
|
pub mod login;
|
||||||
pub mod logout;
|
pub mod logout;
|
||||||
pub mod notifications;
|
pub mod notifications;
|
||||||
|
|
|
@ -32,7 +32,7 @@ pub async fn purge_person(
|
||||||
// Read the local user to get their images, and delete them
|
// Read the local user to get their images, and delete them
|
||||||
if let Ok(local_user) = LocalUserView::read_person(&mut context.pool(), data.person_id).await {
|
if let Ok(local_user) = LocalUserView::read_person(&mut context.pool(), data.person_id).await {
|
||||||
let pictrs_uploads =
|
let pictrs_uploads =
|
||||||
LocalImage::get_all_by_local_user_id(&mut context.pool(), &local_user.local_user.id).await?;
|
LocalImage::get_all_by_local_user_id(&mut context.pool(), local_user.local_user.id).await?;
|
||||||
|
|
||||||
for upload in pictrs_uploads {
|
for upload in pictrs_uploads {
|
||||||
delete_image_from_pictrs(&upload.pictrs_alias, &upload.pictrs_delete_token, &context)
|
delete_image_from_pictrs(&upload.pictrs_alias, &upload.pictrs_delete_token, &context)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::sensitive::Sensitive;
|
use crate::sensitive::Sensitive;
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::{CommentReplyId, CommunityId, LanguageId, PersonId, PersonMentionId},
|
newtypes::{CommentReplyId, CommunityId, LanguageId, PersonId, PersonMentionId},
|
||||||
source::site::Site,
|
source::{images::LocalImage, site::Site},
|
||||||
CommentSortType,
|
CommentSortType,
|
||||||
ListingType,
|
ListingType,
|
||||||
PostListingMode,
|
PostListingMode,
|
||||||
|
@ -418,3 +418,20 @@ pub struct UpdateTotp {
|
||||||
pub struct UpdateTotpResponse {
|
pub struct UpdateTotpResponse {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
/// Get your user's image / media uploads.
|
||||||
|
pub struct ListMedia {
|
||||||
|
pub page: Option<i64>,
|
||||||
|
pub limit: Option<i64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
pub struct ListMediaResponse {
|
||||||
|
pub images: Vec<LocalImage>,
|
||||||
|
}
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
newtypes::{DbUrl, LocalUserId},
|
newtypes::{DbUrl, LocalUserId},
|
||||||
schema::{
|
schema::{local_image, remote_image},
|
||||||
local_image::dsl::{local_image, local_user_id, pictrs_alias},
|
|
||||||
remote_image::dsl::{link, remote_image},
|
|
||||||
},
|
|
||||||
source::images::{LocalImage, LocalImageForm, RemoteImage, RemoteImageForm},
|
source::images::{LocalImage, LocalImageForm, RemoteImage, RemoteImageForm},
|
||||||
utils::{get_conn, DbPool},
|
utils::{get_conn, limit_and_offset, DbPool},
|
||||||
};
|
};
|
||||||
use diesel::{
|
use diesel::{
|
||||||
dsl::exists,
|
dsl::exists,
|
||||||
|
@ -15,7 +12,6 @@ use diesel::{
|
||||||
ExpressionMethods,
|
ExpressionMethods,
|
||||||
NotFound,
|
NotFound,
|
||||||
QueryDsl,
|
QueryDsl,
|
||||||
Table,
|
|
||||||
};
|
};
|
||||||
use diesel_async::RunQueryDsl;
|
use diesel_async::RunQueryDsl;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -23,27 +19,47 @@ use url::Url;
|
||||||
impl LocalImage {
|
impl LocalImage {
|
||||||
pub async fn create(pool: &mut DbPool<'_>, form: &LocalImageForm) -> Result<Self, Error> {
|
pub async fn create(pool: &mut DbPool<'_>, form: &LocalImageForm) -> Result<Self, Error> {
|
||||||
let conn = &mut get_conn(pool).await?;
|
let conn = &mut get_conn(pool).await?;
|
||||||
insert_into(local_image)
|
insert_into(local_image::table)
|
||||||
.values(form)
|
.values(form)
|
||||||
.get_result::<Self>(conn)
|
.get_result::<Self>(conn)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This should only be used in the internal API, since it has no page and limit
|
||||||
pub async fn get_all_by_local_user_id(
|
pub async fn get_all_by_local_user_id(
|
||||||
pool: &mut DbPool<'_>,
|
pool: &mut DbPool<'_>,
|
||||||
user_id: &LocalUserId,
|
user_id: LocalUserId,
|
||||||
) -> Result<Vec<Self>, Error> {
|
) -> Result<Vec<Self>, Error> {
|
||||||
let conn = &mut get_conn(pool).await?;
|
let conn = &mut get_conn(pool).await?;
|
||||||
local_image
|
local_image::table
|
||||||
.filter(local_user_id.eq(user_id))
|
.filter(local_image::local_user_id.eq(user_id))
|
||||||
.select(local_image::all_columns())
|
.select(local_image::all_columns)
|
||||||
|
.load::<LocalImage>(conn)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is okay for API use.
|
||||||
|
pub async fn get_all_paged_by_local_user_id(
|
||||||
|
pool: &mut DbPool<'_>,
|
||||||
|
user_id: LocalUserId,
|
||||||
|
page: Option<i64>,
|
||||||
|
limit: Option<i64>,
|
||||||
|
) -> Result<Vec<Self>, Error> {
|
||||||
|
let conn = &mut get_conn(pool).await?;
|
||||||
|
let (limit, offset) = limit_and_offset(page, limit)?;
|
||||||
|
|
||||||
|
local_image::table
|
||||||
|
.filter(local_image::local_user_id.eq(user_id))
|
||||||
|
.select(local_image::all_columns)
|
||||||
|
.limit(limit)
|
||||||
|
.offset(offset)
|
||||||
.load::<LocalImage>(conn)
|
.load::<LocalImage>(conn)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_by_alias(pool: &mut DbPool<'_>, alias: &str) -> Result<usize, Error> {
|
pub async fn delete_by_alias(pool: &mut DbPool<'_>, alias: &str) -> Result<usize, Error> {
|
||||||
let conn = &mut get_conn(pool).await?;
|
let conn = &mut get_conn(pool).await?;
|
||||||
diesel::delete(local_image.filter(pictrs_alias.eq(alias)))
|
diesel::delete(local_image::table.filter(local_image::pictrs_alias.eq(alias)))
|
||||||
.execute(conn)
|
.execute(conn)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
@ -56,7 +72,7 @@ impl RemoteImage {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|url| RemoteImageForm { link: url.into() })
|
.map(|url| RemoteImageForm { link: url.into() })
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
insert_into(remote_image)
|
insert_into(remote_image::table)
|
||||||
.values(forms)
|
.values(forms)
|
||||||
.on_conflict_do_nothing()
|
.on_conflict_do_nothing()
|
||||||
.execute(conn)
|
.execute(conn)
|
||||||
|
@ -66,9 +82,11 @@ impl RemoteImage {
|
||||||
pub async fn validate(pool: &mut DbPool<'_>, link_: DbUrl) -> Result<(), Error> {
|
pub async fn validate(pool: &mut DbPool<'_>, link_: DbUrl) -> Result<(), Error> {
|
||||||
let conn = &mut get_conn(pool).await?;
|
let conn = &mut get_conn(pool).await?;
|
||||||
|
|
||||||
let exists = select(exists(remote_image.filter((link).eq(link_))))
|
let exists = select(exists(
|
||||||
.get_result::<bool>(conn)
|
remote_image::table.filter(remote_image::link.eq(link_)),
|
||||||
.await?;
|
))
|
||||||
|
.get_result::<bool>(conn)
|
||||||
|
.await?;
|
||||||
if exists {
|
if exists {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -2,18 +2,26 @@ use crate::newtypes::{DbUrl, LocalUserId};
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
use crate::schema::{local_image, remote_image};
|
use crate::schema::{local_image, remote_image};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_with::skip_serializing_none;
|
use serde_with::skip_serializing_none;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
use ts_rs::TS;
|
||||||
use typed_builder::TypedBuilder;
|
use typed_builder::TypedBuilder;
|
||||||
|
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "full", derive(Queryable, Associations))]
|
#[cfg_attr(
|
||||||
|
feature = "full",
|
||||||
|
derive(Queryable, Selectable, Identifiable, Associations, TS)
|
||||||
|
)]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
#[cfg_attr(feature = "full", diesel(table_name = local_image))]
|
#[cfg_attr(feature = "full", diesel(table_name = local_image))]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "full",
|
feature = "full",
|
||||||
diesel(belongs_to(crate::source::local_user::LocalUser))
|
diesel(belongs_to(crate::source::local_user::LocalUser))
|
||||||
)]
|
)]
|
||||||
|
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(primary_key(local_user_id)))]
|
||||||
pub struct LocalImage {
|
pub struct LocalImage {
|
||||||
pub local_user_id: LocalUserId,
|
pub local_user_id: LocalUserId,
|
||||||
pub pictrs_alias: String,
|
pub pictrs_alias: String,
|
||||||
|
|
|
@ -29,6 +29,7 @@ use lemmy_api::{
|
||||||
get_captcha::get_captcha,
|
get_captcha::get_captcha,
|
||||||
list_banned::list_banned_users,
|
list_banned::list_banned_users,
|
||||||
list_logins::list_logins,
|
list_logins::list_logins,
|
||||||
|
list_media::list_media,
|
||||||
login::login,
|
login::login,
|
||||||
logout::logout,
|
logout::logout,
|
||||||
notifications::{
|
notifications::{
|
||||||
|
@ -320,7 +321,8 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) {
|
||||||
.route("/totp/generate", web::post().to(generate_totp_secret))
|
.route("/totp/generate", web::post().to(generate_totp_secret))
|
||||||
.route("/totp/update", web::post().to(update_totp))
|
.route("/totp/update", web::post().to(update_totp))
|
||||||
.route("/list_logins", web::get().to(list_logins))
|
.route("/list_logins", web::get().to(list_logins))
|
||||||
.route("/validate_auth", web::get().to(validate_auth)),
|
.route("/validate_auth", web::get().to(validate_auth))
|
||||||
|
.route("/list_media", web::get().to(list_media)),
|
||||||
)
|
)
|
||||||
// Admin Actions
|
// Admin Actions
|
||||||
.service(
|
.service(
|
||||||
|
|
Loading…
Reference in a new issue